Транзакційна пошта: SMTP-реле чи HTTP API, і як не поховати доставку 2FA-кодів під маркетингом
SMTP-реле проти HTTP API — це не про «що краще», а про два різні failure-режими. Головне ж рішення взагалі не про транспорт: якщо транзакційний і маркетинговий потоки живуть на одному домені й IP, одна погана кампанія обнуляє доставку твоїх листів скидання пароля. Розбираємо сегментацію піддоменами, DKIM-селекторами й IP-пулами.
EvilMail Team19 липня 2026 р.12 хв читання
Користувач тисне «Забув пароль», чекає лист і не отримує його. За добу до цього маркетинг вивалив 200 000 листів про літній розпродаж — з того самого домену, того самого IP-пулу, тим самим DKIM-ключем. Complaint rate стрибнув, Gmail пересунув репутацію відправника в мінус, і разом із розпродажем у спам поїхали листи скидання пароля, коди 2FA й платіжні чеки. Класика.
Коли таке стається, інженер зазвичай починає з неправильного питання: «Ми надсилаємо через SMTP чи через API — може, треба перемкнутися?». Транспорт тут вторинний. Первинне — те, що два види трафіку з протилежними цілями ділять одну репутацію. Спершу розберемо, чим SMTP-реле реально відрізняється від HTTP API на рівні дроту, а потім — головне: чому транзакційний і маркетинговий потоки мусять фізично не перетинатися.
## Дві моделі доставки: що реально відбувається на дроті
SMTP-реле. Твій застосунок (або локальний MTA) відкриває TCP-з'єднання на порт 587 (submission, STARTTLS, RFC 6409) або 465 (implicit TLS, RFC 8314), проходить діалог і передає лист релею. Виглядає він так:
``text
EHLO app.example.com
STARTTLS
EHLO app.example.com
AUTH LOGIN
MAIL FROM:
RCPT TO:
DATA
... заголовки + тіло ...
.
250 2.0.0 Ok: queued as 4Rk2p13Nzw
Ключове тут — 250 ... queued as. З цього моменту лист у черзі релею, і за доставку, ретраї та backoff відповідає він. Твій код своє відпрацював. Порт 25 у цьому діалозі не бере участі — це MTA-to-MTA транспорт, і вихідний 25-й у AWS, GCP та Azure за замовчуванням закритий, тож ніколи не намагайся слати клієнтські листи напряму через нього.
**HTTP API.** Ти робиш POST JSON або MIME на HTTPS-ендпоінт і одразу отримуєш синхронну відповідь: 202 Accepted з message-id, 429 з Retry-After, або
5xx`. Черги на боці провайдера ти не бачиш — зате ретраї, backoff і дедуплікація тепер повністю на тобі.
Різниця не в тому, «що швидше», а в тому,
де лежить відповідальність за збій
. SMTP тримає з'єднання відкритим десятки секунд і ховає ретраї у своїй черзі. API повертає керування за ~200 мс, але мовчки нічого не повторить.
Два failure-режими: де живуть ретраї
SMTP-реле
HTTP API
застосунок
реле (MTA)
блокуючий сокет
timeout ~30 с
250 queued
черга + ретраї
на боці релею
застосунок
API провайдера
HTTPS POST
синхронно ~200 мс
202 / 429 / 5xx
ретраї + backoff
Idempotency-Key
твій код
вебхук delivered/bounce →
Коротке порівняння того, що ти отримуєш і за що платиш:
- Латентність запиту. SMTP: секунди, воркер тримає сокет. API: ~200 мс, з'єднання закривається одразу.
- Контроль конверта. SMTP: повний — сам задаєш MAIL FROM / Return-Path для VERP. API: обмежений полями, які дає провайдер.
- Ретраї. SMTP: у черзі релею. API: на твоєму боці, руками.
- Спостережуваність. SMTP: парсиш DSN-баунси. API: вебхуки delivered/bounce/complaint/deferred.
## Коли SMTP-реле — правильний вибір
Бери SMTP-реле, коли транспорт уже написаний за тебе. PHPMailer, Nodemailer, Rails ActionMailer, Django, Postfix satellite — усі вони говорять SMTP із коробки. Переписувати робочий ActionMailer під HTTP-клієнт заради «модності» — витрачений спринт.
Другий сценарій — коли тобі потрібен контроль над envelope sender. VERP (Variable Envelope Return Path) кодує адресу отримувача в Return-Path, і баунс сам каже, який саме підписник відвалився — без парсингу тіла DSN. На рівні API це доступно не завжди.
Третій — коли ти хочеш локальний MTA як асинхронний буфер. Синхронний sendmail() блокує воркер на весь SMTP-діалог, а це секунди під навантаженням. Локальний Postfix у ролі satellite приймає лист за мілісекунди в свою чергу й уже сам стукає до релею у фоні. Мінімальний main.cf:
``ini
relayhost = [smtp.provider.com]:587
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
Підводні камені: вихідний порт 25 у хмарах закритий (тому й 587 у relayhost), провайдери тротлять кількість з'єднань, а синхронний виклик у веб-воркері без локального MTA рано чи пізно з'їсть тобі пул під час сплеску.
## Коли HTTP API виграє
**Serverless і edge.** У Lambda, Cloudflare Workers чи Vercel Functions немає постійного SMTP-сокета й локального MTA, зате fetch є завжди. Тут API — єдиний вдумливий варіант.
**Ідемпотентність.** Мережа моргнула, ти не дочекався відповіді, повторив запит — і без захисту користувач отримав два однакові листи. Заголовок Idempotency-Key з UUID вирішує це: провайдер бачить той самий ключ і не дублює відправку.
``bash
curl -X POST https://api.provider.com/v1/send \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: 9f1c8b2e-4d3a-4a1e-9c7f-2b6a1d0e5f34" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": "[email protected]",
"subject": "Код підтвердження: 481920",
"html": "Ваш код: 481920
"
}'
``
Обробка відповіді — не «як вийде», а за протоколом: 202 — прийнято, лог і забув; 429 — поважай Retry-After і чекай саме стільки; 5xx — експоненційний backoff. Стартова затримка 1 с, множник 2, jitter, максимум ~5 спроб (1 с → 2 с → 4 с → 8 с → 16 с). Без jitter усі твої воркери повторять запит синхронно й заб'ють провайдера.
Плюс багаті вебхуки замість парсингу DSN, шаблони на боці провайдера й швидший старт. Але одну ілюзію треба вбити одразу: **API не рятує від поганої репутації**. SPF, DKIM і DMARC — однаково твоя відповідальність, і жоден 202 Accepted не витягне лист з папки «Спам», якщо домен уже підпалений.
## Розділення трафіку: головне рішення статті
Тепер суть. Транзакційна й маркетингова пошта — це два трафіки з протилежною фізикою:
- **Транзакційка** — низький обсяг, high-engagement (листи відкривають майже всі), критична за часом, нульова частка скарг.
- **Маркетинг** — bursty (200k за годину), високий complaint rate, обов'язковий List-Unsubscribe, частина отримувачів давно втратила інтерес.
Змішуєш їх на одному домені/IP — і сплеск скарг від розсилки просідає репутацію, на якій сидять твої 2FA-коди. Gmail і Microsoft тримають репутацію відправника **і на рівні organizational-домену, і на рівні піддомену**. Це і є важіль: винеси маркетинг на окремий піддомен, і удар по репутації локалізується там, не зачепивши транзакційний піддомен.
Reputation firewall: розділення потоків
застосунок
одне джерело
Транзакційна
2FA, reset, чеки
high-priority черга
Маркетингова
розсилки, дайджести
bulk-черга
tx.example.com
селектор s1
IP-пул A
mail.example.com
селектор mktg
IP-пул B
reputation firewall (окремі репутації/DKIM)
Gmail /
Outlook
Gmail /
Outlook
одні inbox-провайдери,
різні репутації
Чотири рівні ізоляції, у порядку важливості:
- **Окремий піддомен.** tx.example.com для транзакційки, mail.example.com для розсилок. Це фундамент, з якого починається все інше.
- **Окремі DKIM-селектори.** s1._domainkey.tx.example.com і mktg._domainkey.mail.example.com — різні ключі, що підписують різні потоки й дозволяють приймачам розрізняти репутацію.
- **Окремі IP-пули або взагалі різні провайдери.** Bursty-маркетинг не має грітися на тих самих IP, що й твої коди.
- **Окремий Return-Path / bounce-домен.** Роздільний облік hard/soft-баунсів по кожному потоку.
DMARC при цьому оголошується один раз на organizational-домені _dmarc.example.com і накриває обидва піддомени; політику для піддоменів за потреби тонко налаштовуєш через тег sp=.
## DNS та автентифікація, які мусять бути для обох потоків
Конкретні записи, які треба виставити на **кожному** піддомені:
dns
; SPF — окремо на кожному піддомені відправки
tx.example.com. TXT "v=spf1 include:_spf.provider.com -all"
mail.example.com. TXT "v=spf1 include:_spf.bulk-provider.com -all"
; DKIM — 2048-bit RSA, різні селектори на потік
s1._domainkey.tx.example.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."
mktg._domainkey.mail.example.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBg..."
; DMARC — один на organizational-домені, накриває піддомени
_dmarc.example.com. TXT "v=DMARC1; p=reject; sp=reject; rua=mailto:[email protected]; adkim=r; aspf=r"
Дрібниці, що ламають доставку:
- **SPF — ліміт 10 DNS-lookups** (RFC 7208). Перевищиш — permerror, і перевірка провалюється. Не пхай усі include в один запис.
- **DKIM 2048-bit.** 1024 вважається застарілим; приймачі дедалі частіше дивляться косо на короткі ключі.
- **PTR/rDNS** для власних IP обов'язковий — без зворотного запису Gmail рубає на вході.
- **List-Unsubscribe тільки на маркетингу.** З лютого 2024 діють bulk-sender rules Google і Yahoo: для відправників понад 5000 листів/день — обов'язкові SPF+DKIM+DMARC, one-click відписка (List-Unsubscribe-Post: List-Unsubscribe=One-Click, RFC 8058) і spam rate у Postmaster Tools **нижче 0.3%**, цільово — нижче 0.1%.
- **Транзакційні листи НЕ мають List-Unsubscribe.** Ніхто не «відписується» від коду 2FA чи платіжного чека — заголовок відписки на такому листі це або баг, або тривожний сигнал для приймача.
## Спостережуваність і деградація
Моніторь потоки **окремо**, інакше здорова транзакційка сховає у середньому проблеми маркетингу і навпаки. Мінімальний набір метрик на кожен потік: deliverability, hard/soft bounce rate, complaint feedback loops, час до доставки. Hard-баунс має **одразу** потрапляти в suppression-список — повторна відправка на мертву адресу псує репутацію швидше за все інше.
Фолбек: якщо основний провайдер віддає 5xx або 429, лист іде в чергу з backoff, а за наявності — на вторинний транспорт. Але **черги не змішуй**: транзакційка сидить у пріоритетній черзі, маркетинг — у своїй bulk-черзі, і сплеск розсилки не має ніколи відтіснити код підтвердження. Наскрізне логування message-id від відправки до вебхука дає трасування «лист → подія доставки» без здогадок.
## Чеклист впровадження
- Окремі піддомени: tx. для транзакційки, mail./mktg. для розсилок.
- Окремі DKIM-селектори, 2048-bit RSA, свій ключ на кожен потік.
- DMARC p=reject на organizational-домені з агрегатними звітами (rua).
- SPF без перевищення 10 DNS-lookups, -all замість ~all.
- PTR/rDNS для всіх власних відправних IP.
- List-Unsubscribe + one-click (RFC 8058) — **тільки** на маркетингу.
- Idempotency-Key на кожному API-запиті, щоб ретрай не дублював лист.
- Експоненційний backoff із jitter на 429/5xx, повага до Retry-After.
- Окремі черги та IP-пули: пріоритетна для транзакційки, bulk для маркетингу.
- Вебхуки bounce/complaint підключені, hard-баунси йдуть у suppression одразу.
- Тестовий прогін через mail-tester.com, Google Postmaster Tools і swaks для ручного SMTP-діалогу (swaks --to [email protected] --server smtp.provider.com:587 -tls -au user -ap pass`).
На evilmail.pro транзакційний потік — підтвердження адрес, скидання пароля, службові сповіщення — тримається на окремому піддомені з власним DKIM-селектором і власним IP-пулом, тому жодна масова розсилка не має важеля, щоб дотягнутися до доставки твоїх критичних листів.