Интеграция Mailgun и n8n: transactional email с шаблонами, tracking и idempotency ¶
Обновлено: 2026-05-30
Импортируйте JSON в n8n, замените credentials, домены, sender IDs, лимиты, callback URL и production-политики под вашу инфраструктуру.
Проблема: письмо из CRM или формы кажется простым HTTP-запросом, пока retry не отправляет клиенту два одинаковых invoice, шаблон не ломается из-за пустого поля, а delivery status остаётся только в логах Mailgun.
Решение: интеграция Mailgun и n8n должна валидировать получателя, выбрать approved template, собрать idempotency key, отправить письмо через Sending API и записать delivery/audit status обратно в CRM или таблицу. Такой подход закрывает не демонстрационный happy path, а реальную production-боль: повторы, consent, delivery status, API-ошибки, ограничения провайдера и понятный audit trail.
Проблема: почему простая отправка ломает коммуникации ¶
Коммуникационный workflow нельзя оценивать только по ответу API. Важны consent, формат адреса или телефона, повторная отправка, связь с CRM, callback-статусы и способ отката, если провайдер принял запрос, но сообщение не доставлено.
Для этой страницы основной объект — transactional email message. Входной контракт должен явно фиксировать recipient_email, template_name, variables, campaign_id, idempotency_key, message_id. Если эти поля приходят нестабильно, автоматизация начинает угадывать и может отправить сообщение не тому получателю, не тем каналом или повторно.
Поэтому workflow строится вокруг детерминированных проверок: сначала validation, consent и idempotency, затем отправка, затем callback/audit и только потом бизнес-действия вроде смены статуса или уведомления менеджера.
Архитектура workflow для n8n ¶
| Блок | Задача | Production-проверка |
|---|---|---|
| Webhook / CRM trigger | получает событие для отправки transactional email | есть order_id/deal_id и email |
| Validate recipient | проверяет email, шаблон и обязательные variables | нет мусорных адресов и пустого template |
| Idempotency storage | не отправляет одно письмо дважды | unique key до Mailgun API |
| Mailgun Send API | отправляет template-based email | домен, API key, tracking и tags |
| Delivery tracking | принимает события accepted/delivered/failed | message_id связан с CRM |
| CRM audit | записывает статус и ссылку на письмо | команда видит факт доставки |
Такой workflow удобно сопровождать: mapping, API-запрос, retry, callback и human-readable audit не смешиваются в одной ноде.
Контракт входных данных ¶
{
"event": "send_invoice_email",
"recipient_email": "client@example.com",
"recipient_name": "Ирина",
"template_name": "invoice_paid_ru",
"subject": "Счёт оплачен — доступ открыт",
"order_id": "ord-10492",
"crm_deal_id": "deal-5581",
"variables": {
"amount": "12900.00",
"currency": "RUB",
"login_url": "https://example.ru/login"
},
"source": "crm",
"locale": "ru"
}Payload можно расширять, но нельзя делать обязательные поля “по настроению”. Если источник не передал внешний ID, получателя, шаблон или consent, workflow должен остановиться с понятной ошибкой до отправки.
Code Node: нормализация, mapping и guard-условия ¶
const src = $json.body ?? $json;
const email = String(src.recipient_email ?? '').trim().toLowerCase();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
throw new Error(`Invalid recipient_email for Mailgun: ${email}`);
}
const allowedTemplates = new Set(['invoice_paid_ru', 'lead_welcome_ru', 'support_reply_ru']);
const template = String(src.template_name ?? '').trim();
if (!allowedTemplates.has(template)) {
throw new Error(`Mailgun template is not allowlisted: ${template}`);
}
const orderId = String(src.order_id ?? '').trim();
const dealId = String(src.crm_deal_id ?? '').trim();
if (!orderId && !dealId) throw new Error('order_id or crm_deal_id is required');
const variables = src.variables ?? {};
const idempotencyKey = `mailgun:${template}:${orderId || dealId}:${email}`;
return [{
json: {
action: 'send_mailgun_template_email',
idempotency_key: idempotencyKey,
mailgun: {
to: `${src.recipient_name ?? ''} <${email}>`.trim(),
subject: String(src.subject ?? '').trim() || 'Уведомление',
template,
'h:X-Mailgun-Variables': JSON.stringify({ ...variables, order_id: orderId, deal_id: dealId }),
'o:tracking': 'yes',
'o:tag': [template, String(src.source ?? 'n8n')]
},
audit: { order_id: orderId, crm_deal_id: dealId, recipient_email: email }
}
}];Этот скрипт n8n не заменяет политику провайдера. Его задача — привести данные к стабильному контракту, сформировать idempotency key и не пропустить опасный payload дальше по цепочке.
Готовый workflow JSON: скачать и импортировать ¶
В архиве страницы есть импортируемый workflow JSON и тестовый payload. После импорта замените credentials, домены, sender IDs, callback URL, лимиты и правила доступа. Не запускайте сценарий на production-данных, пока не проверены повторы, пустые значения и ошибки API.
{
"name": "Nodbot - Mailgun transactional email with idempotency and tracking",
"nodes": [
{
"name": "Webhook / CRM trigger",
"type": "n8n-node",
"purpose": "получает событие для отправки transactional email"
},
{
"name": "Validate recipient",
"type": "n8n-node",
"purpose": "проверяет email, шаблон и обязательные variables"
},
{
"name": "Idempotency storage",
"type": "n8n-node",
"purpose": "не отправляет одно письмо дважды"
},
{
"name": "Mailgun Send API",
"type": "n8n-node",
"purpose": "отправляет template-based email"
},
{
"name": "Delivery tracking",
"type": "n8n-node",
"purpose": "принимает события accepted/delivered/failed"
},
{
"name": "CRM audit",
"type": "n8n-node",
"purpose": "записывает статус и ссылку на письмо"
}
],
"connections": "Webhook / CRM trigger → Validate recipient → Idempotency storage → Mailgun Send API → Delivery tracking → CRM audit"
}Пошаговая настройка связки ¶
- Подтвердите sending domain в Mailgun и не используйте sandbox domain для production-писем клиентам.
- Создайте allowlist template names и не разрешайте workflow отправлять произвольный HTML из входного payload.
- Сохраните Mailgun API key в credentials или ENV, а не в Code Node и не в test payload.
- Добавьте таблицу idempotency: ключ, order_id/deal_id, recipient_email, template, Mailgun message_id и status.
- Подключите webhook delivery events и обновляйте CRM после accepted/delivered/failed, а не только после 200 OK.
Что проверить после импорта workflow
Откройте каждую ноду, замените credentials и IDs, включите dry-run там, где доступно, затем выполните сценарий на тестовом объекте. Для платных или внешних отправок добавьте approval, rate limit и отдельный тестовый получатель.
Тесты перед production ¶
Минимальный smoke test:
curl -X POST "https://YOUR-N8N-DOMAIN/webhook/mailgun-transactional-email-n8n" -H "Content-Type: application/json" --data @integration-mailgun-n8n-transactional-email-payload.json- повторный payload с тем же order_id
- невалидный email
- template не из allowlist
- ошибка Mailgun 401/429
- delivery failed event после успешной отправки
Отдельно проверьте, что retry n8n не создаёт повторную отправку. Для критичных действий используйте durable storage: Postgres, CRM custom field, audit table или другой слой с уникальным ключом.
Production-риски ¶
- 200 OK от Mailgun считается доставкой, хотя это только принятие запроса.
- Retry n8n отправляет одно письмо повторно без durable idempotency.
- HTML письма приходит из формы и открывает риск фишинга или XSS в шаблоне.
- Bounce/failed события не возвращаются в CRM, и менеджер не видит проблему.
- Один API key используется для всех доменов без разделения окружений.
Полезные ссылки и смежные материалы ¶
Внутренняя перелинковка помогает перейти от общего integration-гайда к готовым workflow, а внешние ссылки ведут на официальную документацию API и n8n-нод.
Критерии готовности ¶
- Повторный запуск не отправляет второе письмо.
- Шаблоны ограничены allowlist и версионируются.
- Mailgun message_id сохраняется в CRM или audit table.
- Delivery events обновляют статус письма после отправки.
- Ошибки 401/429/5xx уходят в alert или DLQ.