Kommo + Campaign Monitor: email-кампании с сегментацией по стадии воронки

Campaign Monitor - платформа email-маркетинга с простым API и надёжной доставкой. Kommo - CRM с воронкой продаж. Без интеграции список рассылки живёт отдельно от воронки: маркетолог не знает, на какой стадии находится каждый подписчик, и не может сегментировать кампании по CRM-статусу. С интеграцией переход сделки в новый этап автоматически обновляет теги подписчика в Campaign Monitor - рассылки становятся точечными.

Ключевое отличие Campaign Monitor от Mailchimp в контексте B2B: CM работает с подписчиками как с customer journey, позволяет триггерные автоматизации на основе тегов и хорошо интегрируется в стеки с несколькими CRM. Для компаний с русскоязычной аудиторией вне СНГ CM часто предпочтительнее как инструмент с высокой доставляемостью в европейские почтовые системы.

Campaign Monitor API использует Basic Auth с API-ключом в качестве username и произвольной строкой в качестве password. Все запросы к https://api.createsend.com/api/v3.3/.

Направления интеграции

Двусторонняя синхронизация:

  1. Kommo -> Campaign Monitor: смена стадии сделки обновляет теги подписчика в CM
  2. Campaign Monitor -> Kommo: открытие/клик письма логируется как заметка в карточке сделки
Kommo: сделка -> стадия "КП отправлено"
  -> Найти подписчика в CM по email
  -> Добавить тег "stage_proposal_sent"
  -> Убрать тег предыдущей стадии

Campaign Monitor: подписчик открыл письмо
  -> POST /your-server/webhooks/cm {event: "Open", EmailAddress: "..."}
  -> Найти сделку в Kommo по email
  -> POST /api/v4/leads/{id}/notes {text: "Открыл письмо: subject"}

Реализация: Kommo -> Campaign Monitor

import requests, os
from flask import Flask, request, jsonify
import base64

app = Flask(__name__)

KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN  = os.environ["KOMMO_TOKEN"]
CM_API_KEY   = os.environ["CM_API_KEY"]
CM_LIST_ID   = os.environ["CM_LIST_ID"]

KOMMO_BASE = f"https://{KOMMO_DOMAIN}/api/v4"
KOMMO_HDR  = {"Authorization": f"Bearer {KOMMO_TOKEN}"}

CM_BASE    = "https://api.createsend.com/api/v3.3"
CM_HDR     = {
    "Authorization": "Basic " + base64.b64encode(f"{CM_API_KEY}:x".encode()).decode(),
    "Content-Type": "application/json",
}

# Маппинг стадий Kommo -> теги Campaign Monitor
STAGE_TAGS = {
    11111: "stage_new_lead",
    22222: "stage_qualified",
    33333: "stage_proposal_sent",
    44444: "stage_negotiation",
    142:   "stage_won",
    143:   "stage_lost",
}

@app.route("/webhooks/kommo", methods=["POST"])
def kommo_event():
    data = request.json or {}
    for lead in data.get("leads", {}).get("status", []):
        handle_stage_change(lead["id"], lead.get("status_id"))
    return "ok", 200

def get_contact_email(lead_id: int) -> str | None:
    r = requests.get(
        f"{KOMMO_BASE}/leads/{lead_id}",
        headers=KOMMO_HDR,
        params={"with": "contacts"},
    )
    if not r.ok:
        return None
    contacts = r.json().get("_embedded", {}).get("contacts", [])
    if not contacts:
        return None
    cr = requests.get(f"{KOMMO_BASE}/contacts/{contacts[0]['id']}", headers=KOMMO_HDR)
    if not cr.ok:
        return None
    for f in cr.json().get("custom_fields_values") or []:
        if f.get("field_code") == "EMAIL":
            vals = f.get("values", [])
            if vals:
                return str(vals[0]["value"])
    return None

def update_subscriber_tags(email: str, new_stage_id: int):
    # Получить текущего подписчика
    r = requests.get(
        f"{CM_BASE}/subscribers/{CM_LIST_ID}.json",
        headers=CM_HDR,
        params={"email": email},
    )

    if r.status_code == 404:
        # Подписчик не найден - создать
        requests.post(
            f"{CM_BASE}/subscribers/{CM_LIST_ID}.json",
            headers=CM_HDR,
            json={
                "EmailAddress": email,
                "Resubscribe": True,
                "Tags": [STAGE_TAGS.get(new_stage_id, "stage_unknown")],
            },
        )
        return

    if not r.ok:
        return

    # Текущие теги подписчика
    current_tags = r.json().get("Tags", [])

    # Убрать все stage_ теги, добавить новый
    stage_tag_values = list(STAGE_TAGS.values())
    clean_tags = [t for t in current_tags if t not in stage_tag_values]
    new_tag = STAGE_TAGS.get(new_stage_id)
    if new_tag:
        clean_tags.append(new_tag)

    # Обновить через /updateemail или пересоздать с новыми тегами
    requests.put(
        f"{CM_BASE}/subscribers/{CM_LIST_ID}.json",
        headers=CM_HDR,
        params={"email": email},
        json={
            "EmailAddress": email,
            "Tags": clean_tags,
            "Resubscribe": False,
        },
    )

def handle_stage_change(lead_id: int, status_id: int | None):
    if status_id is None:
        return
    email = get_contact_email(lead_id)
    if not email:
        return
    update_subscriber_tags(email, status_id)

Реализация: Campaign Monitor -> Kommo

Campaign Monitor отправляет webhook-события при открытии писем, кликах и отписках. Настройте Webhook в CM Dashboard > Transactional > Webhooks:

@app.route("/webhooks/cm", methods=["POST"])
def cm_event():
    # CM webhooks не подписываются HMAC - проверяем secret token в URL
    # Настройте URL как /webhooks/cm?secret=YOUR_SECRET
    secret = request.args.get("secret", "")
    if secret != os.environ.get("CM_WEBHOOK_SECRET", ""):
        return "unauthorized", 401

    events = request.json or []
    if not isinstance(events, list):
        events = [events]

    for event in events:
        event_type = event.get("Type", "")
        email = event.get("EmailAddress", "")
        subject = event.get("Subject", "")

        if event_type in ("Open", "Click") and email:
            log_cm_event_to_kommo(email, event_type, subject)

    return "ok", 200

def find_lead_by_email(email: str) -> int | None:
    r = requests.get(
        f"{KOMMO_BASE}/contacts",
        headers=KOMMO_HDR,
        params={"query": email, "limit": 1},
    )
    if not r.ok:
        return None
    contacts = r.json().get("_embedded", {}).get("contacts", [])
    if not contacts:
        return None
    contact_id = contacts[0]["id"]
    lr = requests.get(f"{KOMMO_BASE}/contacts/{contact_id}/links", headers=KOMMO_HDR)
    if not lr.ok:
        return None
    links = lr.json().get("_embedded", {}).get("links", [])
    lead_links = [l for l in links if l.get("to_entity_type") == "leads"]
    return lead_links[0]["to_entity_id"] if lead_links else None

def log_cm_event_to_kommo(email: str, event_type: str, subject: str):
    lead_id = find_lead_by_email(email)
    if not lead_id:
        return
    label = "Открыл письмо" if event_type == "Open" else "Кликнул в письме"
    requests.post(
        f"{KOMMO_BASE}/leads/{lead_id}/notes",
        headers=KOMMO_HDR,
        json=[{"note_type": "common", "params": {"text": f"Campaign Monitor: {label} - {subject}"}}],
    )

Горячие лиды из активности в письмах

Добавьте логику: если лид открыл 3+ письма за последнюю неделю - создать задачу менеджеру. Это реализуется через счётчик в Redis или PostgreSQL: при каждом Open-событии инкрементировать счётчик для email. При достижении порога - задача в Kommo.

Реальный кейс

Компания с 500 лидами в Kommo и регулярными email-рассылками в Campaign Monitor. До интеграции: маркетолог сегментировал список вручную раз в неделю. После: теги обновляются в реальном времени при смене стадии. CTR по сегменту “stage_negotiation” вырос на 34% по сравнению с несегментированными рассылками.

Для кого актуально

Компании с активным email-маркетингом и параллельной CRM-воронкой продаж. Особенно если у вас длинный цикл сделки (30+ дней) - нурчуринг по email в этот период должен соответствовать стадии переговоров.

Смежные интеграции: Kommo + ClickUp для задач при смене стадии, кастомные интеграции в Kommo CRM для обзора архитектурных подходов.

Часто задаваемые вопросы

Как Campaign Monitor обрабатывает одинаковые email-адреса в разных списках?

Каждый список в CM независим. Один subscriber может быть в нескольких списках с разными тегами. Для интеграции с Kommo рекомендуется один мастер-список “CRM Leads” - все CRM-контакты в одном месте с тегами по стадиям.

Поддерживает ли Campaign Monitor двойное согласие (double opt-in) при создании подписчика через API?

Да, но при создании через API с параметром "ConsentToTrack": "Yes" и "Resubscribe": true можно добавить без double opt-in. Убедитесь что у вас есть основание для рассылки согласно GDPR/CAN-SPAM.

Как обрабатывать отписки в Campaign Monitor - убирать из Kommo?

При событии Unsubscribe в CM webhook добавьте тег “unsubscribed” в кастомное поле контакта в Kommo. Не удаляйте контакт - сохраните историю. Менеджер по продажам может связаться по другому каналу.

Итог

Kommo + Campaign Monitor интеграция:

  • Kommo webhook (статус сделки) -> обновить теги подписчика в CM
  • Campaign Monitor webhook (Open/Click) -> заметка в карточке сделки
  • Сегментация рассылок по стадии воронки в реальном времени
  • Basic Auth: base64(api_key:x) в заголовке Authorization

Если хотите настроить email-кампании с точной сегментацией по воронке - обратитесь в Exceltic.dev. Реализуем интеграцию под ваши стадии и CМ-шаблоны.

Ещё статьи

Все →