Kommo + Encharge: email-автоматизация для B2B SaaS из воронки продаж

Почему нативный подход недостаточен

Encharge - email automation платформа, созданная специально для B2B SaaS. Её главное отличие от Mailchimp или Brevo - поведенческие триггеры на основе продуктовых событий: пользователь не зашёл в продукт 7 дней, завершил onboarding, активировал функцию. В связке с Kommo это открывает интересные возможности: email-сиквенс должен знать, на каком этапе воронки находится лид.

Готовой интеграции Encharge + Kommo нет. Encharge имеет нативные интеграции с HubSpot и Intercom, но не с Kommo. Zapier-вариант существует, но не закрывает ключевую задачу: передачу события из Encharge обратно в Kommo при взаимодействии с письмом.

Для B2B SaaS важна двусторонняя синхронизация: Kommo знает, что лид открыл proposal email - значит пора звонить. Encharge знает, что лид перешёл на этап «Демо» - значит пора запустить последовательность pre-demo emails. Ни Zapier, ни нативные коннекторы этого не дают.

Если вы работаете с кастомными интеграциями для Kommo, то такой двусторонний sync - стандартная задача.

Что реализуется - архитектура решения

Два независимых потока данных:

Поток 1: Kommo -> Encharge
Kommo: смена этапа сделки
    --> Webhook --> Python сервис
        --> Encharge API: POST /people (upsert контакта)
        --> Encharge API: POST /events (track event "deal_moved_to_X")

Поток 2: Encharge -> Kommo
Encharge: email_opened / link_clicked (важный триггер)
    --> Webhook --> Python сервис
        --> Kommo API: создать Task для SDR
        --> Kommo API: добавить Note в сделку

Технические детали

Encharge API Auth. Bearer token. Получается в Encharge Settings -> API & Webhooks -> API Key. Используется как Bearer в заголовке Authorization.

Encharge API эндпоинты:

  • POST /people - создать или обновить профиль (upsert по email). Тело: { "email": "...", "fields": { "kommo_stage": "...", "kommo_lead_id": 123 } }
  • POST /events - отправить событие для пользователя. Тело: { "name": "deal_moved_to_proposal", "email": "user@company.com", "properties": { "lead_id": 123 } }
  • GET /broadcasts/{id}/activity - статистика конкретного broadcast
  • Webhooks: настраиваются в Encharge UI -> Settings -> Webhooks

Encharge Webhook Events:

  • email_opened - письмо открыто
  • email_link_clicked - клик по ссылке в письме
  • user_subscribed / user_unsubscribed - изменение статуса подписки

Kommo Webhooks. Событие lead.status_changed при смене этапа сделки.

Пошаговая реализация

Шаг 1. Kommo -> Encharge при смене этапа

import os
import requests
from flask import Flask, request

app = Flask(__name__)

KOMMO_DOMAIN = os.environ["KOMMO_DOMAIN"]
KOMMO_TOKEN = os.environ["KOMMO_ACCESS_TOKEN"]
ENCHARGE_TOKEN = os.environ["ENCHARGE_API_KEY"]
ENCHARGE_BASE = "https://api.encharge.io/v1"

# Маппинг ID этапов Kommo на имена событий Encharge
STAGE_TO_EVENT = {
    12345: "deal_moved_to_qualified",
    12346: "deal_moved_to_demo_scheduled",
    12347: "deal_moved_to_proposal_sent",
    12348: "deal_moved_to_negotiation",
    12349: "deal_moved_to_won",
}


def get_lead_contact_email(lead_id: int) -> tuple[str, str]:
    """Получаем email и имя основного контакта сделки."""
    url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, params={"with": "contacts"}, headers=headers, timeout=10)
    if not r.ok:
        return "", ""

    lead_data = r.json()
    contacts = lead_data.get("_embedded", {}).get("contacts", [])
    if not contacts:
        return "", ""

    contact_id = contacts[0]["id"]
    cr = requests.get(
        f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}",
        headers=headers,
        timeout=10,
    )
    if not cr.ok:
        return "", ""

    contact = cr.json()
    name = contact.get("name", "")
    email = ""
    for cf in contact.get("custom_fields_values", []) or []:
        if cf.get("field_code") == "EMAIL":
            email = cf["values"][0]["value"]
            break
    return email, name


def upsert_encharge_person(email: str, name: str, lead_id: int, stage_name: str):
    """Создаём или обновляем профиль пользователя в Encharge."""
    url = f"{ENCHARGE_BASE}/people"
    headers = {
        "Authorization": f"Bearer {ENCHARGE_TOKEN}",
        "Content-Type": "application/json",
    }
    first, *last_parts = name.split(" ", 1)
    payload = {
        "email": email,
        "firstName": first,
        "lastName": last_parts[0] if last_parts else "",
        "fields": {
            "kommo_lead_id": str(lead_id),
            "kommo_current_stage": stage_name,
        },
    }
    r = requests.post(url, json=payload, headers=headers, timeout=10)
    return r.ok


def track_encharge_event(email: str, event_name: str, properties: dict):
    """Отправляем событие в Encharge для триггера email flow."""
    url = f"{ENCHARGE_BASE}/events"
    headers = {
        "Authorization": f"Bearer {ENCHARGE_TOKEN}",
        "Content-Type": "application/json",
    }
    payload = {
        "name": event_name,
        "email": email,
        "properties": properties,
    }
    r = requests.post(url, json=payload, headers=headers, timeout=10)
    return r.ok


@app.route("/webhooks/kommo/stage", methods=["POST"])
def kommo_stage_webhook():
    """Обрабатываем смену этапа сделки в Kommo."""
    data = request.form
    lead_id = int(data.get("leads[status][0][id]", 0))
    new_status_id = int(data.get("leads[status][0][status_id]", 0))

    if not lead_id or new_status_id not in STAGE_TO_EVENT:
        return {"ok": True}

    event_name = STAGE_TO_EVENT[new_status_id]
    email, name = get_lead_contact_email(lead_id)

    if not email:
        return {"ok": True}

    stage_name = event_name.replace("deal_moved_to_", "")
    upsert_encharge_person(email, name, lead_id, stage_name)
    track_encharge_event(email, event_name, {
        "lead_id": lead_id,
        "stage": stage_name,
    })

    return {"ok": True}

Шаг 2. Encharge -> Kommo при взаимодействии с письмом

@app.route("/webhooks/encharge", methods=["POST"])
def encharge_webhook():
    """Обрабатываем события из Encharge."""
    event = request.json
    event_type = event.get("event")
    user_email = event.get("email", "")

    # Важные события, при которых нужно действие SDR
    high_intent_events = ["email_link_clicked", "email_opened"]

    # Для link_clicked - всегда создаём задачу
    # Для email_opened - только для определённых broadcast (например, proposal)
    broadcast_name = event.get("broadcastName", "")
    important_broadcasts = ["proposal", "pricing", "demo_followup"]

    should_create_task = (
        event_type == "email_link_clicked"
        or (
            event_type == "email_opened"
            and any(b in broadcast_name.lower() for b in important_broadcasts)
        )
    )

    if not should_create_task or not user_email:
        return {"ok": True}

    # Находим контакт и сделку в Kommo
    contact = find_kommo_contact_by_email(user_email)
    if not contact:
        return {"ok": True}

    lead_id = get_active_lead_for_contact(contact["id"])
    if not lead_id:
        return {"ok": True}

    # Создаём задачу для SDR
    import time
    event_label = "кликнул по ссылке" if event_type == "email_link_clicked" else "открыл письмо"
    task_text = (
        f"Encharge: {user_email} {event_label} в письме '{broadcast_name}'\n"
        f"Рекомендуется связаться в течение 1-2 часов"
    )

    url = f"https://{KOMMO_DOMAIN}/api/v4/tasks"
    headers = {
        "Authorization": f"Bearer {KOMMO_TOKEN}",
        "Content-Type": "application/json",
    }
    payload = [{
        "task_type_id": 1,
        "text": task_text,
        "complete_till": int(time.time()) + 2 * 3600,
        "entity_id": lead_id,
        "entity_type": "leads",
    }]
    requests.post(url, json=payload, headers=headers, timeout=10)

    # Добавляем Note в сделку для истории
    note_url = f"https://{KOMMO_DOMAIN}/api/v4/leads/{lead_id}/notes"
    note_payload = [{"note_type": "common", "params": {
        "text": f"Encharge signal: {user_email} {event_label} в '{broadcast_name}'"
    }}]
    requests.post(note_url, json=note_payload, headers=headers, timeout=10)

    return {"ok": True}


def find_kommo_contact_by_email(email: str) -> dict | None:
    url = f"https://{KOMMO_DOMAIN}/api/v4/contacts"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, params={"query": email}, headers=headers, timeout=10)
    if r.ok:
        contacts = r.json().get("_embedded", {}).get("contacts", [])
        return contacts[0] if contacts else None
    return None


def get_active_lead_for_contact(contact_id: int) -> int | None:
    url = f"https://{KOMMO_DOMAIN}/api/v4/contacts/{contact_id}/links"
    headers = {"Authorization": f"Bearer {KOMMO_TOKEN}"}
    r = requests.get(url, headers=headers, timeout=10)
    if not r.ok:
        return None
    links = r.json().get("_embedded", {}).get("links", [])
    lead_ids = [l["to_entity_id"] for l in links if l.get("to_entity_type") == "leads"]
    return lead_ids[0] if lead_ids else None

Реальный кейс с цифрами

Для B2B SaaS-компании с MRR от $30k двусторонняя синхронизация Kommo + Encharge создаёт важный эффект: sales-команда и маркетинг работают с единым контекстом.

Типичный сценарий до интеграции: маркетолог запускает nurture-сиквенс «для всех лидов на этапе Qualified» - вручную выгружая список из Kommo в Excel раз в неделю. SDR не знает, что лид получает письма. Лид получает письмо сразу после звонка с SDR - и это выглядит странно.

После интеграции: при переходе сделки на этап «Qualified» в Encharge автоматически запускается сиквенс из 5 писем на 10 дней. При переходе на «Proposal Sent» - сиквенс останавливается, запускается «Proposal Follow-up» на 3 письма. Если лид кликает по ссылке в proposal email - SDR получает задачу в Kommo через 2-5 минут. По данным схожих интеграций, конверсия из «Proposal Sent» в «Won» вырастает на 15-25% за счёт своевременного follow-up.

Для кого подходит

Интеграция актуальна для B2B SaaS-компаний, которые:

  • Используют Encharge для email-автоматизации с поведенческими триггерами
  • Ведут воронку в Kommo с несколькими чёткими этапами
  • Хотят синхронизировать маркетинговые email-sequences с актуальным этапом продаж
  • Имеют SDR, для которых важен сигнал об активности лида в письмах

Если вы используете другие email-инструменты - например, для синхронизации с Kommo + ActiveCampaign - логика схожа, но Encharge лучше подходит именно для SaaS с продуктовыми событиями.

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

Как не получать задачу в Kommo при каждом открытии письма? Добавьте дедупликацию: сохраняйте последнее время создания задачи для каждой сделки в Redis или базе данных. Если прошло менее 24 часов с последней задачи по этой сделке - не создавайте новую.

Encharge отправляет webhook при каждом открытии или только при первом? По умолчанию - при каждом. Для фильтрации используйте поле isFirstEvent: true в payload, если оно есть, или реализуйте собственную дедупликацию как описано выше.

Можно ли синхронизировать кастомные свойства из Kommo в Encharge? Да. В Encharge fields принимает произвольные ключи. Добавьте в запрос upsert_encharge_person любые кастомные поля из Kommo: размер компании, индустрия, должность контакта из кастомных полей сделки.

Что происходит, если лид unsubscribe в Encharge? Encharge отправит webhook user_unsubscribed. Рекомендуется при этом добавлять тег «Email Unsubscribed» в Kommo-контакт, чтобы SDR видели, что email-маркетинг для этого лида недоступен, и могли сосредоточиться на звонках.

Если вам нужна интеграция Kommo с Encharge - опишите ваш стек и сценарий команде Exceltic.dev. Разберём архитектуру за одну встречу.

Ещё статьи

Все →