Přeskočit na obsah

Email z aplikace bouncuje — SMTP relay host neodpovídá SPF vlastní domény

import { Aside } from ‘@astrojs/starlight/components’;

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).

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.x
const SMTP_PORT = 465

Mezití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.

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:

  1. 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).
  2. 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 yourdomain a porovnej s tím, kam aplikace skutečně připojuje.
    • Monitoruj Mail Delivery System zprávy v inboxu odesílajících účtů — pokud jich přibývá, deliverability problém.
  • Anti-pattern: Jediná konstanta SMTP_HOST na ú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=none může zakrýt problém — zprávy projdou bez ohledu na SPF fail, ale do reportu naletí spousta fail událostí. Při přechodu na p=reject se najednou všechno odmítne.
Přidal aiarchitekt.cz · 29. 5. 2026 2:00
Provozuje aiarchitekt.cz