Kommo + SendGrid: транзакционные email из воронки продаж
Twilio SendGrid - стандарт для транзакционных email в B2B SaaS: Dynamic Templates с Handlebars, Event Webhook для отслеживания, delivery rate SLA 99.9%. Нативной интеграции с Kommo нет. Строим через SendGrid Mail Send API v3.
Что строим
- Won deal -> отправить onboarding email через SendGrid Dynamic Template
- SendGrid Event Webhook -> открытие/клик/bounce -> Note в Kommo
- Комплексный сценарий: серия email по этапам воронки без внешнего ESP-автоматизатора
Аутентификация
SendGrid использует Bearer token (API Key):
import requests
SENDGRID_API_KEY = "SG.your_api_key"
sg_session = requests.Session()
sg_session.headers.update({
"Authorization": f"Bearer {SENDGRID_API_KEY}",
"Content-Type": "application/json",
})
SEND_URL = "https://api.sendgrid.com/v3/mail/send"
API Key создаётся в SendGrid Dashboard -> Settings -> API Keys. Минимальные права для отправки: Mail Send (Full Access).
Отправка через Dynamic Template
def send_onboarding_email(to_email: str, to_name: str, template_data: dict, deal_id: int) -> str:
"""Send email via SendGrid Dynamic Template. Returns message ID."""
payload = {
"from": {
"email": "hello@exceltic.dev",
"name": "Exceltic",
},
"personalizations": [
{
"to": [{"email": to_email, "name": to_name}],
"dynamic_template_data": {
**template_data,
"kommo_deal_id": str(deal_id), # для трекинга в webhook
},
"custom_args": {
"kommo_deal_id": str(deal_id), # передаётся в каждом Event
},
}
],
"template_id": "d-your_template_id", # Template ID из SendGrid
"tracking_settings": {
"click_tracking": {"enable": True},
"open_tracking": {"enable": True},
},
"mail_settings": {
"bypass_list_management": {"enable": False},
},
}
r = sg_session.post(SEND_URL, json=payload)
r.raise_for_status()
# SendGrid возвращает 202 без тела. Message ID в заголовке X-Message-Id
return r.headers.get("X-Message-Id", "")
custom_args - ключевой инструмент: эти поля возвращаются в каждом Event от Event Webhook. Так вы знаете, к какой сделке Kommo относится событие без внешней БД.
Event Webhook: отслеживание открытий
SendGrid отправляет массив событий на ваш URL при каждом delivered, opened, clicked, bounced, unsubscribed.
Верификация подписи ECDSA:
SendGrid подписывает webhook через ECDSA (не HMAC). Это нестандартно - большинство webhook используют HMAC-SHA256.
from ecdsa import VerifyingKey, NIST256p
from ecdsa.util import sigdecode_der
import base64, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
# Публичный ключ из SendGrid Dashboard -> Settings -> Mail Settings -> Event Webhook
SG_VERIFICATION_KEY = "MFkwEwYHKoZIzj0CAQY..." # base64 DER-encoded public key
def verify_sendgrid_signature(payload: bytes, timestamp: str, signature: str) -> bool:
"""Verify SendGrid Event Webhook ECDSA signature."""
vk = VerifyingKey.from_der(base64.b64decode(SG_VERIFICATION_KEY))
signed_payload = timestamp.encode() + payload
digest = hashlib.sha256(signed_payload).digest()
try:
return vk.verify_digest(
base64.b64decode(signature),
digest,
sigdecode=sigdecode_der,
)
except Exception:
return False
@app.route("/sendgrid/events", methods=["POST"])
def sendgrid_events():
timestamp = request.headers.get("X-Twilio-Email-Event-Webhook-Timestamp", "")
signature = request.headers.get("X-Twilio-Email-Event-Webhook-Signature", "")
if not verify_sendgrid_signature(request.get_data(), timestamp, signature):
abort(401)
events = request.json # массив событий
for event in events:
process_event(event)
return "ok", 200
def process_event(event: dict):
event_type = event.get("event", "") # "delivered", "open", "click", "bounce"
deal_id_str = event.get("kommo_deal_id", "") # из custom_args
email = event.get("email", "")
url = event.get("url", "") # только для "click"
reason = event.get("reason", "") # только для "bounce"
if not deal_id_str:
return
deal_id = int(deal_id_str)
if event_type == "open":
add_kommo_note(deal_id, f"Email открыт: {email}")
elif event_type == "click":
add_kommo_note(deal_id, f"Клик по ссылке: {url}")
elif event_type in ("bounce", "dropped", "blocked"):
add_kommo_note(deal_id, f"Email не доставлен ({event_type}): {reason}")
create_kommo_task(deal_id, f"Проверить email клиента: {email}")
ecdsa библиотека: pip install ecdsa. Без верификации - webhook доступен любому, кто знает URL.
Серия email по этапам воронки
Вместо внешнего ESP-автоматизатора (дополнительные затраты) - логика на стороне вашего сервиса:
STAGE_TEMPLATES = {
PROPOSAL_STAGE_ID: "d-template_proposal",
NEGOTIATION_STAGE_ID:"d-template_followup",
WON_STAGE_ID: "d-template_onboarding",
}
@app.route("/kommo/stage-webhook", methods=["POST"])
def kommo_stage_webhook():
data = request.json
for lead in data.get("leads", {}).get("update", []):
new_status = lead.get("status_id")
deal_id = lead["id"]
if new_status in STAGE_TEMPLATES:
contact = get_kommo_deal_contact(deal_id)
send_onboarding_email(
to_email=contact["email"],
to_name=contact["name"],
template_data={"deal_name": lead.get("name", "")},
deal_id=deal_id,
)
return "ok", 200
Одна точка интеграции - все email-триггеры управляются через конфиг STAGE_TEMPLATES.
Реальный кейс
EU SaaS-компания с 60-70 сделок в месяц отправляла onboarding email вручную через SendGrid UI. 12-15% сделок теряли email из-за человеческой ошибки (забыли отправить, неверный шаблон).
После интеграции:
- Onboarding email отправляется автоматически при переходе в Won (задержка < 5 секунд)
- Bounce-события создают задачи менеджеру немедленно
- Open tracking обновляет поле в Kommo - менеджер видит, читал ли клиент письмо
За первые 3 месяца: 0 пропущенных onboarding email, 23% рост response rate.
Для кого подходит
SaaS и B2B-команды, которые уже используют SendGrid для транзакционных email и хотят замкнуть петлю обратной связи в CRM. Особенно актуально когда нужен event-tracking (открытия, клики) в контексте конкретной сделки.
Для полноценной email-автоматизации с визуальным редактором - см. Kommo + Customer.io или Kommo + Loops.
Часто задаваемые вопросы
Чем Dynamic Templates отличаются от Legacy Templates?
Dynamic Templates используют Handlebars {{variable}} синтаксис и управляются через Design Editor в SendGrid. Legacy Templates устарели и используют {{%variable%}}. Для всех новых интеграций - только Dynamic Templates. Template ID начинается с d-.
Как настроить отдельный webhook для tracking vs transactional events?
SendGrid позволяет один Event Webhook URL на аккаунт. Разделение через custom_args: добавьте "event_category": "tracking" или "transactional" и фильтруйте в обработчике. Или используйте subusers - у каждого свой Event Webhook.
Нужна ли IP Warmup для отправки?
Если отправляете с выделенного IP - да, нужна warmup. Если через shared IP pool (по умолчанию) - нет. Для объёмов до 100K писем в месяц shared IP достаточно. Выделенный IP рекомендуется от 200K+ или если нужна полная изоляция репутации.
Как обработать unsubscribe в Kommo?
Event unsubscribe от SendGrid содержит email. Обработчик находит контакт в Kommo по email и ставит тег email_unsubscribed или обновляет кастомное поле. При следующей отправке сервис проверяет этот тег и пропускает контакт.
Итог
Интеграция Kommo + SendGrid даёт замкнутый цикл email-коммуникации в CRM:
- Bearer token для отправки,
custom_argsдля передачи deal_id в события - Event Webhook: ECDSA-верификация через
X-Twilio-Email-Event-Webhook-Signature - Open/click/bounce -> Note в Kommo без ручной работы
- Серия email по этапам воронки без внешнего автоматизатора
Если SendGrid уже в вашем стеке и нужно замкнуть события на Kommo - опишите задачу команде Exceltic.dev.