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. Разберём архитектуру под ваш процесс и оценим объём работ.