Kommo + Lemlist: холодные email-кампании из воронки без дублей контактов

Kommo + Lemlist: холодные email-кампании из воронки без дублей контактов

При интеграции Kommo и Lemlist лид, попавший на нужный этап воронки, автоматически добавляется в Lemlist-кампанию. Ответ или клик по письму обновляет статус в Kommo. Отписка удаляет контакт из всех активных кампаний. Менеджер не экспортирует CSV и не проверяет вручную, кому уже написали.

Lemlist - платформа для персонализированных холодных email-кампаний с функционалом автоматических follow-up, персонализированных изображений и multi-channel outreach (email + LinkedIn). Популярна среди SDR-команд и агентств как замена массовым рассылкам. Проблема в том, что Lemlist и Kommo существуют в параллельных вселенных: без интеграции SDR добавляет контакты в Lemlist вручную, а потом вручную переносит ответы обратно в CRM.

Нативной интеграции между Kommo и Lemlist нет. По данным Lemlist, средний reply rate в B2B cold email составляет 3-8% - это означает, что SDR обрабатывает сотни контактов, прежде чем получит десяток ответов. При таком объёме ручное добавление лидов в последовательности становится основной потерей времени. В проектах на Kommo мы видим, что лиды добавляются в Lemlist-кампании пакетами раз в несколько дней: за это время часть контактов успевает пройти несколько этапов воронки или вообще стать неактуальной. В этой статье разберём, как кастомная интеграция синхронизирует переход по воронке Kommo с запуском персонализированной последовательности в Lemlist в реальном времени.

Почему нативная интеграция не работает

Lemlist предлагает Zapier-интеграцию, но она решает только простейший случай: «когда контакт добавлен в Lemlist - создать deal в Kommo». Обратное направление (из Kommo в Lemlist) работает ограниченно.

Главная проблема - дедупликация. Если один и тот же контакт есть в нескольких сделках Kommo, Zapier создаст его в Lemlist несколько раз, и человек получит одно и то же письмо дважды с разным контекстом. Это уничтожает репутацию домена.

Вторая проблема - управление отписками. Когда контакт нажимает «Unsubscribe» в Lemlist, это должно заблокировать все будущие кампании на этот email. В Kommo это поле не обновляется автоматически - у менеджера нет информации, что контакт отписался, и он может запустить следующую кампанию вручную.

Третья проблема - вариабельность. Lemlist позволяет персонализировать письма через переменные (имя, компания, кейс релевантный их индустрии). Эти данные живут в Kommo, но Zapier не умеет динамически собирать их из кастомных полей и передавать в правильном формате.

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

Kommo (сделка перешла на этап «Outreach»)
        |
        v
  Сервер-оркестратор
  - проверка: нет ли контакта уже в активной кампании
  - проверка: не отписан ли контакт
  - сборка переменных персонализации из полей сделки
        |
        v
  Lemlist API (добавить в кампанию с переменными)
        |
        v
  Kommo: обновить поле lemlist_campaign_id

  ---

Lemlist (событие: ответ / клик / отписка)
        |
        v
  Lemlist Webhook -> Ваш сервер
        |
        v
  Kommo API:
  - если ответ -> добавить заметку + задача «Ответить»
  - если клик -> добавить заметку
  - если отписка -> обновить поле + тег «unsubscribed»

Ключевой компонент - реестр активных кампаний. Перед добавлением контакта в Lemlist сервер проверяет через Lemlist API, не находится ли этот email уже в другой активной кампании. Это предотвращает дубли при работе нескольких SDR с пересекающимися сегментами.

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

Lemlist API v2 использует API ключ в заголовке Authorization: Bearer <key>. Ключ генерируется в Settings -> Integrations -> API. Webhook настраивается в Settings -> Integrations -> Webhooks и поддерживает события: emailSent, emailOpened, emailClicked, emailReplied, emailBounced, emailUnsubscribed.

import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

LEMLIST_API_BASE = "https://api.lemlist.com/api"
LEMLIST_API_KEY = "your_lemlist_api_key"

def check_contact_in_campaign(email: str) -> bool:
    """Проверяет, есть ли контакт в активной кампании Lemlist"""
    headers = {"Authorization": f"Bearer {LEMLIST_API_KEY}"}
    resp = requests.get(
        f"{LEMLIST_API_BASE}/leads/{email}",
        headers=headers
    )
    if resp.status_code == 404:
        return False
    lead_data = resp.json()
    # Проверяем статус в кампаниях
    for campaign in lead_data.get("campaigns", []):
        if campaign.get("status") in ["active", "paused"]:
            return True
    return False

def add_lead_to_campaign(campaign_id: str, lead_data: dict) -> dict:
    """Добавляет лида в кампанию Lemlist с персонализацией из Kommo"""
    headers = {"Authorization": f"Bearer {LEMLIST_API_KEY}"}
    payload = {
        "firstName": lead_data["first_name"],
        "lastName": lead_data["last_name"],
        "email": lead_data["email"],
        "companyName": lead_data.get("company", ""),
        "icebreaker": lead_data.get("personalization_note", ""),
        "industry": lead_data.get("industry", ""),
        "dealSize": str(lead_data.get("deal_amount", "")),
        "kommoLeadId": str(lead_data["kommo_lead_id"])
    }
    resp = requests.post(
        f"{LEMLIST_API_BASE}/campaigns/{campaign_id}/leads/{lead_data['email']}",
        json=payload,
        headers=headers
    )
    resp.raise_for_status()
    return resp.json()

@app.route("/kommo/webhook", methods=["POST"])
def kommo_webhook():
    data = request.json
    for lead_update in data.get("leads", {}).get("status", []):
        lead_id = lead_update["id"]
        new_stage = lead_update["status_id"]
        if new_stage == OUTREACH_STAGE_ID:
            handle_outreach_stage(lead_id)
    return jsonify({"ok": True})

def handle_outreach_stage(lead_id: int):
    lead = kommo_api.get_lead(lead_id)
    contact = kommo_api.get_lead_contact(lead_id)
    email = contact.get("email")
    
    if not email:
        kommo_api.add_note(lead_id, "Lemlist: нет email у контакта, пропускаем")
        return
    
    # Проверка на отписку
    if contact.get("custom_fields", {}).get("email_unsubscribed"):
        kommo_api.add_note(lead_id, "Lemlist: контакт отписался от рассылок, пропускаем")
        return
    
    # Проверка на дубль
    if check_contact_in_campaign(email):
        kommo_api.add_note(lead_id, f"Lemlist: {email} уже в активной кампании")
        return
    
    # Определить кампанию по типу сделки
    campaign_id = get_campaign_for_deal(lead)
    
    lead_data = {
        "first_name": contact.get("name", "").split()[0],
        "last_name": " ".join(contact.get("name", "").split()[1:]),
        "email": email,
        "company": lead.get("company_name", ""),
        "deal_amount": lead.get("price", 0),
        "industry": lead.get("custom_fields", {}).get("industry", ""),
        "personalization_note": lead.get("custom_fields", {}).get("icebreaker", ""),
        "kommo_lead_id": lead_id
    }
    
    result = add_lead_to_campaign(campaign_id, lead_data)
    kommo_api.update_lead(lead_id, {"lemlist_campaign_id": campaign_id})
    kommo_api.add_note(lead_id, f"Добавлен в Lemlist кампанию {campaign_id}")

@app.route("/lemlist/webhook", methods=["POST"])
def lemlist_webhook():
    data = request.json
    event = data.get("type")
    email = data.get("email")
    kommo_lead_id = data.get("metaData", {}).get("kommoLeadId")
    
    if not kommo_lead_id:
        # Попытка найти по email
        contact = kommo_api.find_contact_by_email(email)
        if contact:
            deals = kommo_api.get_contact_deals(contact["id"])
            kommo_lead_id = deals[0]["id"] if deals else None
    
    if not kommo_lead_id:
        return jsonify({"ok": True})
    
    if event == "emailReplied":
        kommo_api.add_note(kommo_lead_id, f"Lemlist: получен ответ от {email}")
        kommo_api.create_task(
            kommo_lead_id,
            text=f"Ответить на письмо от {email}",
            deadline_hours=4
        )
    elif event == "emailClicked":
        kommo_api.add_note(kommo_lead_id, f"Lemlist: {email} кликнул по ссылке")
    elif event == "emailUnsubscribed":
        kommo_api.update_contact_field(email, "email_unsubscribed", True)
        kommo_api.add_tag_to_contact(email, "unsubscribed")
        kommo_api.add_note(kommo_lead_id, f"Lemlist: {email} отписался от рассылок")
    elif event == "emailBounced":
        kommo_api.add_note(kommo_lead_id, f"Lemlist: email {email} недоступен (bounce)")
    
    return jsonify({"ok": True})

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

Шаг 1. Создайте кампании в Lemlist

Подготовьте кампании под разные сегменты: по индустрии, размеру компании, источнику лида. В каждой кампании настройте переменные: {{firstName}}, {{companyName}}, {{icebreaker}}. Запишите Campaign ID каждой кампании - он нужен для маппинга.

Шаг 2. Настройте кастомные поля в Kommo

Добавьте поля: lemlist_campaign_id (текст), email_unsubscribed (чекбокс), icebreaker (текст - поле для персонализированного первого предложения). Поле icebreaker заполняет SDR перед добавлением лида на этап «Outreach».

Шаг 3. Настройте маппинг кампаний

Определите логику выбора кампании: например, если industry = SaaS -> кампания A, если deal_amount > 10000 -> кампания B. Это делается в серверной логике, не в Zapier.

Шаг 4. Создайте webhook в Kommo

Подпишитесь на событие leads.status. При переходе на этап «Outreach» сервер запускает процесс добавления в Lemlist.

Шаг 5. Настройте webhook в Lemlist

В Lemlist Settings -> Integrations -> Webhooks добавьте URL сервера и подпишитесь на события: emailReplied, emailClicked, emailUnsubscribed, emailBounced.

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

AGency-сегмент: B2B-агентство из Варшавы, 4 SDR, 150-200 новых лидов в месяц. До интеграции процесс выглядел так:

  • SDR квалифицировал лида в Kommo
  • Экспортировал CSV с нужного этапа
  • Импортировал в Lemlist вручную
  • После получения ответа вручную создавал заметку в Kommo
  • 2 раза в неделю проверял, кто ответил и кто отписался

Время на обслуживание процесса: 3-4 часа в неделю на SDR. При 4 SDR - 12-16 часов в неделю административной работы.

Частота дублей: 1-2 контакта в неделю получали одинаковые письма из разных кампаний из-за человеческой ошибки при проверке.

После интеграции Kommo + Lemlist через Exceltic.dev:

  • Добавление в кампанию: автоматически при смене этапа
  • Дубли: 0 (проверка через API)
  • Ответы в Kommo: появляются автоматически как заметки
  • Административное время: сократилось до 30 минут в неделю на всю команду

О функциях Kommo CRM для работы с outreach-командами - подробнее в обзоре платформы.

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

Интеграция Kommo + Lemlist актуальна для:

  • SDR-команд, которые совмещают outbound prospecting с управлением воронкой в Kommo
  • Агентств, ведущих несколько кампаний параллельно для разных сегментов
  • Компаний с 50+ новыми лидами в месяц, где ручной импорт в рассылки стал узким местом
  • Команд с compliance-требованиями к управлению отписками (GDPR, CAN-SPAM)

Если вы работаете преимущественно с входящими лидами - Lemlist может быть избыточен. Для нагрева базы через email достаточно Kommo + ActiveCampaign или Brevo.

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

Lemlist поддерживает API для добавления лидов в кампанию?

Да. Lemlist API v2 поддерживает добавление лидов в кампанию, удаление, получение статусов и настройку webhook. API ключ генерируется в Settings -> Integrations. Документация доступна на developer.lemlist.com.

Как интеграция обрабатывает GDPR-требования к отпискам?

Когда Lemlist присылает событие emailUnsubscribed, интеграция выполняет три действия: ставит флаг в кастомном поле контакта Kommo, добавляет тег «unsubscribed» и записывает заметку с датой отписки. Перед добавлением любого контакта в новую кампанию сервер проверяет этот флаг и блокирует добавление. Это соответствует требованиям GDPR об уважении opt-out.

Можно ли запускать разные кампании для разных типов сделок?

Да. Логика выбора кампании настраивается на стороне сервера: в зависимости от кастомных полей Kommo (индустрия, размер компании, источник лида) выбирается нужный campaign_id. Можно реализовать сколько угодно сложную логику маппинга.

Что происходит если Lemlist-кампания завершилась без ответа?

Lemlist присылает событие leadExhausted когда все шаги последовательности выполнены и ответа не получено. Интеграция обновляет поле в Kommo («Outreach завершён, без ответа») и может автоматически перевести сделку на следующий этап воронки или создать задачу для ручного follow-up.

Сколько занимает разработка интеграции?

Базовый вариант (добавление в кампанию + обработка ответов/отписок) - 2-3 недели. Сложная логика маппинга кампаний, multi-channel outreach (email + LinkedIn) и интеграция с дополнительными системами - 4-5 недель. О кастомных интеграциях в Kommo для outreach-автоматизации - подробнее на сайте.


Если ваша SDR-команда работает в Kommo и использует Lemlist для outreach - опишите задачу команде Exceltic.dev. Разберём архитектуру под ваш процесс и оценим объём работ.

Ещё статьи

Все →