Kommo + Loops: современный B2B email для SaaS-команд из воронки продаж
Loops — email-платформа построенная специально для SaaS-продуктов: event-driven рассылки на основе действий пользователей, транзакционные письма, onboarding-цепочки. Отличается от Mailchimp или GetResponse упором на product-led growth: вместо «списков контактов» Loops работает с событиями (user.signed_up, trial.started, plan.upgraded). Нативной интеграции с Kommo нет — разбираем архитектуру event-driven связки: действие в Kommo -> событие в Loops -> email-серия.
Loops vs Mailchimp vs Customer.io для SaaS B2B
| Параметр | Loops | Mailchimp | Customer.io |
|---|---|---|---|
| Модель | Event-driven (SaaS-first) | Список-ориентированная | Event-driven (enterprise) |
| Транзакционные письма | Да (нативно) | Через Mandrill (доп. плата) | Да |
| Onboarding sequences | Да | С ограничениями | Да |
| API-first | Да (REST v1) | Да, но сложнее | Да |
| Цена | от $49/мес | от $13/мес | от $100/мес |
| UI простота | Высокая | Средняя | Сложная |
| Нативная Kommo | Нет | Нет | Нет |
Loops выбирают SaaS-команды до 5000 контактов где нужна простая event-модель без overhead Enterprise-инструментов.
Архитектура: что синхронизируется
Kommo -> Loops:
— Новый контакт (лид) -> POST /contacts/create в Loops
— Изменение email/имени -> PUT /contacts/update
— Won -> POST /events/send с событием deal_won -> триггер Welcome Series
— Смена этапа -> POST /events/send -> соответствующая email-серия
Loops -> Kommo:
— Webhook email.bounced -> Note + Task: «Email недоставлен»
— Webhook contact.unsubscribed -> Note: «Отписался от рассылки»
Loops REST API v1: базовые запросы
Base URL: https://app.loops.so/api/v1. Аутентификация: Authorization: Bearer {api_key} (из Loops -> Settings -> API).
import requests
LOOPS_API_KEY = "your_loops_api_key"
LOOPS_BASE = "https://app.loops.so/api/v1"
LOOPS_HEADERS = {
"Authorization": f"Bearer {LOOPS_API_KEY}",
"Content-Type": "application/json",
}
def create_loops_contact(email: str, name: str = "",
company: str = "", user_group: str = "") -> dict:
# Создать или обновить контакт (upsert по email)
first_name = name.split()[0] if name else ""
last_name = " ".join(name.split()[1:]) if len(name.split()) > 1 else ""
payload = {
"email": email,
"firstName": first_name,
"lastName": last_name,
"companyName": company,
"userGroup": user_group,
"source": "kommo_crm",
}
resp = requests.post(
f"{LOOPS_BASE}/contacts/create",
headers=LOOPS_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
def update_loops_contact(email: str, properties: dict) -> dict:
payload = {"email": email, **properties}
resp = requests.put(
f"{LOOPS_BASE}/contacts/update",
headers=LOOPS_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
def send_loops_event(email: str, event_name: str,
properties: dict = None) -> dict:
# Отправить событие -> триггер email-серии
payload = {
"email": email,
"eventName": event_name,
"eventProperties": properties or {},
}
resp = requests.post(
f"{LOOPS_BASE}/events/send",
headers=LOOPS_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
def send_loops_transactional(email: str,
transactional_id: str,
data_variables: dict = None) -> dict:
# Транзакционное письмо (contract, invoice, welcome)
payload = {
"email": email,
"transactionalId": transactional_id,
"dataVariables": data_variables or {},
}
resp = requests.post(
f"{LOOPS_BASE}/transactional",
headers=LOOPS_HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
Kommo webhook -> Loops event
Kommo -> Settings -> Webhooks -> Add выдаёт события сделок. При смене этапа или создании лида запускаем соответствующее Loops событие:
@app.route("/webhooks/kommo", methods=["POST"])
def kommo_webhook():
payload = request.json
event = list(payload.keys())[0] # "leads[status]", "leads[add]" и т.д.
lead_data = payload.get(event, [{}])[0]
lead_id = lead_data.get("id")
pipeline_id = lead_data.get("pipeline_id")
status_id = lead_data.get("status_id")
contact = get_kommo_contact_for_lead(lead_id)
if not contact:
return "", 200
email = get_contact_email(contact)
if not email:
return "", 200
name = contact.get("name", "")
company = get_contact_company(contact)
if "add" in event:
# Новый лид - создать контакт в Loops
create_loops_contact(email, name, company, user_group="leads")
send_loops_event(email, "lead_created", {
"lead_id": lead_id,
"lead_name": lead_data.get("name", ""),
})
elif "status" in event:
# Смена этапа
if status_id == WON_STATUS_ID:
send_loops_event(email, "deal_won", {
"deal_value": lead_data.get("price", 0),
"company": company,
})
# Транзакционное Welcome письмо
send_loops_transactional(
email,
transactional_id=WELCOME_TEMPLATE_ID,
data_variables={"name": name, "company": company},
)
elif status_id in NURTURE_STAGE_IDS:
send_loops_event(email, "entered_nurture", {
"stage": get_stage_name(status_id),
})
return "", 200
Loops -> Kommo: обработка отписок и bounce
Loops поддерживает webhooks через Settings -> Webhooks. Важно обрабатывать отписки — иначе будете слать email тем кто не хочет их получать.
@app.route("/webhooks/loops", methods=["POST"])
def loops_webhook():
payload = request.json
event_type = payload.get("type", "")
contact = payload.get("contact", {})
email = contact.get("email", "")
lead_id = find_kommo_lead_by_email(email)
if not lead_id:
return "", 200
if event_type == "email.bounced":
bounce_type = payload.get("bounceType", "") # "hard" | "soft"
create_kommo_note(lead_id,
f"Loops: email недоставлен ({bounce_type} bounce) - {email}")
if bounce_type == "hard":
create_kommo_task(lead_id,
f"Loops: обновить email контакта - hard bounce на {email}")
elif event_type == "contact.unsubscribed":
create_kommo_note(lead_id,
f"Loops: контакт {email} отписался от рассылки")
# Добавить тег в Kommo чтобы не слать повторно через другие каналы
add_kommo_tag(lead_id, "email_unsubscribed")
return "", 200
SaaS onboarding: цепочка из Kommo
Loops лучше всего показывает себя в product-led модели: trial -> onboarding emails -> upgrade. Если Kommo — CRM рядом с SaaS-продуктом, цепочка строится так:
ONBOARDING_EVENTS = {
TRIAL_STAGE_ID: "trial_started",
ACTIVE_STAGE_ID: "account_activated",
UPGRADE_STAGE_ID: "upgrade_initiated",
WON_STATUS_ID: "deal_won",
}
def on_stage_change(lead_id: int, new_status_id: int):
contact = get_kommo_contact_for_lead(lead_id)
email = get_contact_email(contact)
if not email:
return
event_name = ONBOARDING_EVENTS.get(new_status_id)
if event_name:
lead = get_kommo_lead(lead_id)
send_loops_event(email, event_name, {
"plan": get_custom_field(lead, PLAN_FIELD_ID),
"trial_days": get_custom_field(lead, TRIAL_DAYS_FIELD_ID),
})
Реальный кейс
B2B SaaS (EU, 3 менеджера по продажам, Kommo + Loops):
- До: после Won менеджер вручную добавлял email в Mailchimp-список, запускал onboarding вручную. 20–30% новых клиентов не получали onboarding-серию в первый день.
- После: Won ->
deal_wonсобытие в Loops -> автоматический старт 5-письменной onboarding-серии. Bounce -> Note в Kommo + Task менеджеру. Отписка -> тег в Kommo чтобы не трогать по email. - Дополнительно: Trial ->
trial_started-> Loops запускает 3-дневную urgency-серию. Upgrade ->upgrade_initiated-> отдельная цепочка с инструкциями.
Для кого актуально
- SaaS-компании где Kommo CRM работает параллельно с SaaS-продуктом и нужна event-driven email-автоматизация
- Команды до 5000 контактов которым Mailchimp неудобен из-за списочной модели
- Product-led growth компании где trial -> onboarding -> upgrade проходит через CRM
- Стартапы которым нужна простая email-платформа без сложности Customer.io
Часто задаваемые вопросы
Loops vs Mailchimp — в чём принципиальная разница для CRM-интеграции?
Mailchimp работает со списками: вы добавляете контакт в список -> он попадает в цепочку. Loops работает с событиями: вы отправляете событие (deal_won, trial_started) -> Loops запускает нужную серию. Для CRM-интеграции event-модель Loops значительно чище — не нужно управлять подписками на списки, достаточно слать события из Kommo при каждом изменении этапа.
Loops поддерживает транзакционные письма (счёт, договор)?
Да. POST /transactional с transactionalId (ID шаблона из Loops UI) и dataVariables (динамические поля). Транзакционные письма отправляются независимо от подписки контакта — они не являются маркетинговыми. Для интеграции с Kommo: Won -> отправить транзакционный welcome + invoice email с данными из сделки.
Как Loops обрабатывает дубли контактов?
Loops использует email как первичный ключ. POST /contacts/create с уже существующим email — это upsert (обновление свойств). Дубли по email невозможны. Если контакт в Kommo меняет email — нужно создать новый контакт в Loops и удалить старый через DELETE /contacts с параметром email.
Loops отслеживает открытия и клики — можно ли это видеть в Kommo?
Loops не предоставляет webhook на уровне отдельных открытий (только aggregate в дашборде). Для email engagement в Kommo лучше использовать Loops -> Zapier/Make (если нужны клики -> Note) или Customer.io где per-event webhooks богаче. Loops даёт webhooks: email.bounced, contact.unsubscribed, email.spam_complaint — этих достаточно для гигиены базы.
Итого
- API: Bearer token, base URL
https://app.loops.so/api/v1 - Контакты:
POST /contacts/create(upsert по email),PUT /contacts/update - События:
POST /events/sendсeventName-> триггер email-серии в Loops - Транзакционные:
POST /transactionalсtransactionalId+dataVariables - Webhook Loops -> Kommo:
email.bounced-> Note/Task,contact.unsubscribed-> Note + тег
Если у вас SaaS-продукт с Kommo и вы хотите event-driven onboarding email без Mailchimp — опишите текущую воронку и количество контактов. Exceltic.dev настроит интеграцию за 1–2 рабочих дня.