Bounce code SMTP: come leggerli davvero (e perché il 90% li interpreta male)
I bounce code SMTP non sono tutti uguali: un 421 non è un 550, e un 451 non è un 550. Guida pratica per leggerli, parsarli e impostare retry intelligenti.
Ogni giorno milioni di email rimbalzano contro server di posta restituendo codici di stato a tre cifre. Il problema è che la maggior parte degli sviluppatori legge solo la prima cifra (4 o 5) e archivia il messaggio come "soft bounce" o "hard bounce", perdendo informazione cruciale. In realtà RFC 5321 e RFC 3463 definiscono una semantica precisa che, se ignorata, porta a strategie di retry sbagliate, reputation IP rovinata e cancellazioni di contatti validi. In questo articolo analizziamo i bounce code più comuni nel 2026, come parsarli con regex robuste, quando ritentare davvero e quando arrenderti subito.
La differenza tra status code e enhanced status code
Un bounce SMTP completo contiene tre informazioni distinte: il codice di risposta a tre cifre (es. 550), il codice esteso secondo RFC 3463 (es. 5.1.1) e un testo libero in inglese spesso scritto dal sysadmin del destinatario. Esempio reale di una risposta Google Workspace:
550-5.1.1 The email account that you tried to reach does not exist. Please try
550-5.1.1 double-checking the recipient's email address for typos or
550-5.1.1 unnecessary spaces. Learn more at
550 5.1.1 https://support.google.com/mail/?p=NoSuchUser z6si12345678wmf.42 - gsmtp
Qui abbiamo: 550 (basic code, RFC 5321), 5.1.1 (enhanced code, RFC 3463), e il testo. Il basic dice "permanent failure", l'enhanced dice esattamente "bad destination mailbox address". Sono due livelli diversi di informazione: il primo cifra (5) coincide tra i due, ma il livello di dettaglio è radicalmente differente.
Tabella dei codici più comuni
| Codice | Enhanced | Significato reale | Retry? |
|---|---|---|---|
| 421 | 4.7.0 | Servizio temporaneamente non disponibile, connessione chiusa | Sì, 15-30 min |
| 421 | 4.7.28 | IP temporaneamente bloccato per rate limit (Gmail tipico) | Sì, backoff esponenziale |
| 450 | 4.2.1 | Mailbox non disponibile (out of office, manutenzione) | Sì, 1 ora |
| 451 | 4.3.0 | Errore locale del receiver, riprova | Sì, 30 min |
| 452 | 4.2.2 | Mailbox piena, quota superata | Sì, 6-24 ore |
| 550 | 5.1.1 | Utente inesistente (hard bounce definitivo) | NO, suppress |
| 550 | 5.1.2 | Dominio inesistente o MX non risolvibile | NO, suppress |
| 550 | 5.2.1 | Mailbox disabilitata | NO, suppress |
| 550 | 5.7.1 | Rifiutato per policy (spam, SPF/DKIM fail) | Dipende |
| 550 | 5.7.26 | Multiple authentication failure (Gmail) | NO, fix DKIM/SPF |
| 552 | 5.2.3 | Messaggio troppo grande | NO, riduci payload |
| 553 | 5.1.8 | Sender address rifiutato (envelope MAIL FROM) | NO, fix sender |
| 554 | 5.7.1 | Transazione fallita, spesso reputation | NO, investiga |
⚠️ Attenzione: il codice 5.7.1 è il più ambiguo dell'ecosistema SMTP. Può significare "questo singolo messaggio è considerato spam" oppure "il tuo IP è in blacklist". Senza leggere il testo libero non puoi distinguere i due casi, e la strategia di rimedio è opposta.
Parsare bounce code con regex
La maggior parte dei provider SMTP italiani consegna i bounce in due forme: inline (durante la sessione SMTP, restituiti come eccezione dal client) oppure asincroni (DSN - Delivery Status Notification, una mail di ritorno con MIME type multipart/report). Per gli inline ecco una regex Python robusta:
import re
BOUNCE_RE = re.compile(
r"^(?P<basic>[45]d{2})[s-]"
r"(?:(?P<enhanced>[245].d{1,3}.d{1,3})s+)?"
r"(?P<message>.+)$",
re.MULTILINE
)
def parse_bounce(raw: str) -> dict:
last_line = raw.strip().split("
")[-1]
m = BOUNCE_RE.match(last_line)
if not m:
return {"basic": None, "enhanced": None, "message": raw}
return {
"basic": int(m.group("basic")),
"enhanced": m.group("enhanced"),
"message": m.group("message").strip(),
"permanent": m.group("basic").startswith("5"),
}
Per i DSN asincroni, la library standard email.parser di Python espone direttamente il blocco message/delivery-status; non serve regex se non per pulizia finale.
Strategia di retry corretta
Una retry policy sbagliata è la causa numero uno di reputation IP devastata. Tre regole non negoziabili:
- 4xx = retry, 5xx = stop. Sembra ovvio, ma metà dei sistemi custom sbaglia almeno questo.
- Backoff esponenziale con jitter. Niente retry ogni 60 secondi a oltranza:
delay = min(2^attempt + random(0, 30), 14400)in secondi, max 4 ore. - Hard cap a 72 ore. RFC 5321 raccomanda di smettere dopo 4-5 giorni; in pratica 72 ore è il cap industriale corretto.
def next_retry_delay(attempt: int, basic: int, enhanced: str) -> int | None:
if basic >= 500:
return None
if enhanced == "4.2.2": # mailbox piena
return min(3600 * (attempt + 1), 21600)
if enhanced == "4.7.28": # rate limit Gmail
return min(900 * (2 ** attempt), 7200)
if attempt >= 12:
return None
return min(60 * (2 ** attempt) + random.randint(0, 30), 14400)
I codici "trappola" da conoscere
421 4.7.0 vs 550 5.7.1: errore o blacklist?
Quando un Gmail risponde 421 4.7.0 IP not in whitelist non è un errore: è una richiesta di rallentare. Aspetta 30 minuti, non ritentare prima. Se invece risponde 550 5.7.1 Our system has detected unusual traffic, il messaggio è già rifiutato definitivamente e il tuo IP è sotto osservazione.
451 4.3.0: il "boh" di Postfix
Postfix usa 451 4.3.0 come catch-all per errori locali (database non raggiungibile, content filter offline). Significa quasi sempre "ritenta tra 15 minuti, sarà tornato".
550 5.4.1: rete o policy?
Outlook/Office 365 usa 550 5.4.1 Recipient address rejected: Access denied sia per indirizzi inesistenti sia per regole policy interne. In assenza di altre informazioni considera l'indirizzo invalido e suppressalo.
💡 Suggerimento: tieni una suppression list separata per tipologia: hard bounce permanenti, complaint (FBL), unsubscribe, manuali. Non mescolarle. Quando reinvii dopo 6 mesi, vorrai sapere se l'indirizzo era invalido o se aveva cliccato "spam".
Cosa NON fare con i bounce
- Mai cancellare contatti al primo soft bounce. Tre soft bounce consecutivi su tre invii separati sono il minimo per declassificare un indirizzo.
- Mai trattare il 552 come problema dell'utente. È un problema tuo: il messaggio è troppo grande. Comprimi allegati o usa link a CDN.
- Mai ignorare i 5.7.x. Sono i bounce di reputation: nascondono problemi sistemici che peggiorano nel tempo.
- Mai accettare il bounce code dal subject del DSN. Il testo umano (
Returned mail: see transcript for details) è inaffidabile, parsare sempre il bloccomessage/delivery-status.
Riferimenti normativi
- RFC 5321 — Simple Mail Transfer Protocol
- RFC 3463 — Enhanced Mail System Status Codes
- RFC 3464 — DSN format
- IANA registry degli enhanced status code
La gestione bounce è la differenza tra un sender che sopravvive un anno e uno che brucia il primo IP in tre mesi. Se gestisci email transactional di volume medio-alto, Target SMTP applica automaticamente le retry policy descritte e mantiene suppression list separate per tipologia; il Send-Time Firewall intercetta inoltre invii verso indirizzi storicamente in hard bounce prima ancora che lascino il server, evitando il danno reputazionale al colpo successivo.