Un rebote es un servidor de correo diciéndote por qué no aceptaría tu mensaje, en un código de tres dígitos. Clasificar bien esos códigos marca la diferencia entre suprimir una dirección genuinamente muerta y descartar una que habría entregado en un reintento. Esta guía construye un clasificador de rebotes y muestra cómo aplicar esa misma clasificación antes de enviar en vez de después.
La respuesta corta#
El primer dígito del código SMTP decide la acción: 5xx es un fallo
permanente — suprímelo — y 4xx es temporal — reintenta. El matiz que
importa es el código de estado ampliado: un 5.7.x es un bloqueo por política o
reputación (el servidor rechazándote a ti, no juzgando el buzón), que no debes
tratar como una dirección muerta. Un verificador aplica la misma lógica a una
sonda del buzón, así que puedes clasificar una dirección antes de que llegue a
producirse un rebote real.
Las dos dimensiones de un rebote#
Todo rebote tiene dos propiedades que conviene separar:
- Permanencia — ¿es definitivo (
5xx, un rebote duro) o temporal (4xx, un rebote blando)? Esto decide entre suprimir o reintentar. - Sujeto — ¿el servidor está juzgando el buzón ("no such user") o tu remitente ("your IP is blocklisted")? Un rechazo permanente de tu remitente parece un rebote duro, pero no debe gestionarse como tal — suprimir al destinatario no arregla nada, porque el problema es tu reputación.
La guía de códigos de respuesta SMTP cubre la gramática completa; la clasificación consiste en convertirla en una acción.
Un clasificador de rebotes#
Aquí tienes un clasificador que lee el código SMTP y el código ampliado opcional y
devuelve una acción. Gestiona el caso que a la mayoría de clasificadores ingenuos
se les escapa — el bloqueo por política disfrazado de 5xx:
/**
* Classify a bounce into an action.
* @param {number} code basic SMTP code, e.g. 550
* @param {string} [enhanced] enhanced status code, e.g. "5.1.1"
*/
export function classifyBounce(code, enhanced) {
const cls = Math.floor(code / 100); // 2, 4, or 5
// Policy / reputation block: the server is refusing YOU, not the mailbox.
// Suppressing the recipient would be treating the wrong problem.
if (enhanced?.startsWith("5.7") || enhanced?.startsWith("4.7")) {
return { type: "policy", action: "review", note: "sender reputation, not the mailbox" };
}
if (cls === 5) {
// Permanent: no such user, disabled, or the domain won't relay.
return { type: "hard", action: "suppress", reason: "rejected_email" };
}
if (cls === 4) {
// Temporary: full mailbox, greylisting, or a rate limit.
const fullMailbox = code === 452 || enhanced === "4.2.2";
return {
type: "soft",
action: "retry",
reason: fullMailbox ? "full_mailbox" : "timeout",
};
}
return { type: "unknown", action: "retry" };
}
classifyBounce(550, "5.1.1"); // { type: "hard", action: "suppress" }
classifyBounce(452); // { type: "soft", action: "retry", reason: "full_mailbox" }
classifyBounce(554, "5.7.1"); // { type: "policy", action: "review" } ← don't suppress
La regla que queda incrustada aquí: suprime solo los rechazos limpios de
buzón. Un 5.7.x significa que arregles tu reputación de envío; suprimir al
destinatario oculta la señal sin resolver nada.
Clasifica antes del rebote, no después#
El problema de la clasificación de rebotes es que es reactiva — solo te enteras de que una dirección está muerta enviándole y dañando tu reputación en el proceso. La verificación aplica esa misma clasificación a una sonda SMTP en vez de a un envío real, así que obtienes el veredicto sin el rebote. Los códigos de veredicto y motivo se asocian directamente a las mismas acciones:
const ACTION_BY_REASON = {
accepted_email: "keep", // deliverable
rejected_email: "suppress", // hard bounce confirmed by probe
invalid_email: "suppress", // bad syntax
invalid_domain: "suppress", // no mail route
low_quality: "suppress", // disposable
low_deliverability: "segment", // catch-all or full mailbox — send carefully
timeout: "retry", // greylisted / no answer yet
unavailable_smtp: "retry", // couldn't reach the server
unknown: "retry",
};
// From a verification result row:
const action = ACTION_BY_REASON[row.reason] ?? "retry";
El mismo árbol de decisión, adelantado en el tiempo. Las direcciones que habrían
dado un rebote duro vuelven como undeliverable y se suprimen antes del envío; las
unknown reciben un reintento en lugar de una conjetura.
Cómo enlazarlo con el ciclo#
En cualquier dirección que lo ejecutes, la salida alimenta un único sitio — tu lista de supresión:
- Reactivo: analiza los webhooks de rebote de tu ESP, ejecuta
classifyBouncey suprime los duros mientras programas reintentos para los blandos. - Proactivo: ejecuta un trabajo de verificación masiva, recupera
los resultados con
include=results, asocia cadareasona través deACTION_BY_REASONy aplica las acciones — el ciclo de exportar-verificar-suprimir que las guías de limpieza para ESP recorren plataforma por plataforma.
Lo ideal es mantener ambos: verificar de forma proactiva para prevenir la mayoría de los rebotes, y clasificar de forma reactiva los rebotes residuales para cazar las direcciones que se degradaron desde entonces.
Preguntas frecuentes#
¿Cuál es la diferencia entre un rebote duro y uno blando, en código?#
La clase SMTP: 5xx es un rebote duro (permanente — suprimir), 4xx es un rebote
blando (temporal — reintentar). La única trampa es un código ampliado 5.7.x, que
es un bloqueo por política permanente contra tu remitente, no un buzón
muerto — clasifícalo aparte y arregla tu reputación en lugar de suprimir al
destinatario.
¿Cuántas veces debería reintentar un rebote blando antes de suprimir?#
No hay un número universal, pero un patrón habitual es reintentar una dirección que rebota de forma blanda a lo largo de varios envíos consecutivos y escalarla a suprimida si nunca se recupera — que es más o menos lo que hacen internamente los proveedores de buzón y los ESP. Los buzones llenos suelen vaciarse por sí solos, así que los rebotes blandos merecen una paciencia que un rebote duro no.
¿Puedo clasificar direcciones antes de enviarles?#
Sí — eso es justamente la verificación. Una sonda SMTP del buzón produce las mismas
señales que produciría un rebote, asociadas a un veredicto deliverable | risky | undeliverable | unknown con un código de motivo, de modo que puedes suprimir las
direcciones muertas antes de que lleguen a rebotar. El mapa ACTION_BY_REASON de
arriba convierte esos veredictos en las mismas acciones de suprimir/reintentar/mantener.
¿Quieres los veredictos contra los que clasificar? El plan gratuito
incluye 100 créditos, y la referencia de la API documenta los
códigos de motivo y el endpoint de resultados /jobs del que lee el ciclo
proactivo.