Email z aplikace bouncuje — SMTP relay host neodpovídá SPF vlastní domény
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Uživatelé hlásí, že jim z interní appky (CRM/back-office) nejdou odesílat emaily. V inboxu se jim hromadí zprávy Mail Delivery System — Undelivered Mail Returned to Sender. Aplikace přitom v UI hlásí success, log v DB zaznamenává odeslání. Mailové queue na vlastním serveru je prázdné (zprávy přes něj vůbec neprošly).
Root cause
Sekce “Root cause”Aplikace měla v lib/email.ts natvrdo zapsaný legacy SMTP relay z původního deploymentu:
const SMTP_HOST = 'mail.legacy-vendor.example' // IP 116.x.x.xconst SMTP_PORT = 465Mezitím firma přešla na vlastní mail server (mail.ourapp.example, IP 46.x.x.x) a nastavila ostré DNS:
profesionalniweb.cz TXT "v=spf1 a mx ip4:46.x.x.x -all"profesionalniweb.cz MX 0 mail.ourapp.example.Operátorky mají SMTP účet user@ourapp.example (heslo se ukládá u uživatele v DB). Aplikace ale tyto credentials posílala přes legacy-vendor server. Příjemcův mailserver provedl SPF check:
- envelope-from:
user@ourapp.example - connecting IP:
116.x.x.x(legacy vendor) - SPF authorized IPs: pouze
46.x.x.x - výsledek:
-all= hard fail → reject
Příjemce vrátil bounce. Z pohledu odesílatele to vypadá jako náhodná chyba — některé maily projdou (pokud má příjemce slabší SPF policy), jiné se vrátí. Často to maskuje měsíce.
Fix
Sekce “Fix”Routovat SMTP host podle domény odesílatele, aby SPF vždy odpovídal:
function resolveSmtpHost(senderEmail: string): string { const domain = senderEmail.split('@')[1]?.toLowerCase() if (domain === 'ourapp.example') return 'mail.ourapp.example' return 'mail.legacy-vendor.example' // backward compat}
function createTransporter(user: string, pass: string) { return nodemailer.createTransport({ host: resolveSmtpHost(user), port: 465, secure: true, auth: { user, pass }, })}Dále:
- Sjednotit SMTP heslo s mailserverem. Heslo uložené u uživatele musí být to, kterým se autentizuje na cílovém serveru — když měníš host, většinou musíš obnovit i heslo (každý server má vlastní user/pass).
- Po opravě otestovat doručení do externí domény (Gmail, Seznam) — interní mail z vlastní domény neukáže SPF problém.
Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection:
grep -rn "SMTP_HOST\s*=\s*['\"]" --include='*.ts' --include='*.js'— vždy podezřele, pokud je host hardcoded literál.- Po každé migraci mailserveru projdi
dig +short TXT yourdomaina porovnej s tím, kam aplikace skutečně připojuje. - Monitoruj
Mail Delivery Systemzprávy v inboxu odesílajících účtů — pokud jich přibývá, deliverability problém.
- Anti-pattern: Jediná konstanta
SMTP_HOSTna úrovni modulu, používaná pro všechny uživatele bez ohledu na to, z jaké domény posílají. - Lepší přístup:
- Routovat host podle domény odesílatele (jeden mailserver per doména).
- Nebo: nepoužívat per-user SMTP auth a posílat všechno přes jeden centrální transactional service (SES, Postmark, Resend) s plně nastaveným SPF + DKIM + DMARC.
- Při zavedení vlastního mailserveru zkontroluj všechny appky, které mohou posílat za danou doménu — nejen tu hlavní.
Sister bugs / související
Sekce “Sister bugs / související”- DKIM podpisy: i když SPF projde, chybějící/neplatný DKIM podpis stejné zprávy způsobí DMARC fail (pokud je DMARC
p=reject). SPF + DKIM musí být oba v souladu. - DMARC
p=nonemůže zakrýt problém — zprávy projdou bez ohledu na SPF fail, ale do reportu naletí spoustafailudálostí. Při přechodu nap=rejectse najednou všechno odmítne.