Um bounce é um servidor de e-mail te dizendo por que ele não aceitaria a sua mensagem, em um código de três dígitos. Classificar esses códigos corretamente é a diferença entre suprimir um endereço realmente morto e jogar fora um que teria sido entregue em um reenvio. Este guia constrói um classificador de bounces e mostra como rodar a mesma classificação antes de enviar, em vez de depois.
A resposta curta#
O primeiro dígito do código SMTP define a ação: 5xx é uma falha permanente —
suprima — e 4xx é temporária — reenvie. O refinamento que importa é o código de
status enhanced: um 5.7.x é um bloqueio de política ou reputação (o servidor
recusando você, não julgando a caixa postal), que você não deve tratar como um
endereço morto. Um verificador aplica a mesma lógica a uma sondagem de caixa
postal, então você pode classificar um endereço antes que um bounce real aconteça.
As duas dimensões de um bounce#
Todo bounce tem duas propriedades que vale a pena separar:
- Permanência — isto é final (
5xx, um hard bounce) ou temporário (4xx, um soft bounce)? Isso decide suprimir vs. reenviar. - Alvo — o servidor está julgando a caixa postal ("usuário inexistente") ou o seu remetente ("seu IP está em uma blocklist")? Uma rejeição permanente do seu remetente parece um hard bounce, mas não pode ser tratada como um — suprimir o destinatário não resolve nada, porque o problema é a sua reputação.
O guia de códigos de resposta SMTP cobre toda a gramática; a classificação é sobre transformá-la em uma ação.
Um classificador de bounces#
Aqui está um classificador que lê o código SMTP e o código enhanced opcional e
retorna uma ação. Ele trata o caso que a maioria dos classificadores ingênuos
deixa passar — o bloqueio de política disfarçado 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
A regra embutida aqui: suprima apenas as rejeições limpas de caixa postal. Um
5.7.x significa corrigir a sua reputação de envio; suprimir o destinatário
esconde o sinal sem resolver nada.
Classifique antes do bounce, não depois#
O problema com a classificação de bounces é que ela é reativa — você só descobre que um endereço está morto enviando para ele e, no processo, prejudicando a sua reputação. A verificação roda a mesma classificação em uma sondagem SMTP em vez de um envio real, então você obtém o veredito sem o bounce. Os códigos de veredito e de motivo mapeiam diretamente para as mesmas ações:
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";
Mesma árvore de decisão, deslocada para mais cedo no tempo. Os endereços que
teriam gerado hard bounce voltam como undeliverable e são suprimidos antes do
envio; os unknown recebem um reenvio em vez de um chute.
Encaixando isso no fluxo#
Seja qual for a direção em que você rode, a saída alimenta um único lugar — a sua lista de supressão:
- Reativo: interprete os webhooks de bounce do seu ESP, rode
classifyBouncee suprima os hard enquanto agenda reenvios para os soft. - Proativo: rode um job de verificação em massa, puxe os
resultados com
include=results, mapeie cadareasonatravés deACTION_BY_REASONe aplique as ações — o ciclo de exportar-verificar-suprimir que os guias de limpeza de ESP percorrem para cada plataforma.
Manter os dois é o ideal: verifique proativamente para prevenir a maioria dos bounces e classifique reativamente os bounces residuais para pegar os endereços que se deterioraram desde então.
Perguntas frequentes#
Qual é a diferença entre um hard bounce e um soft bounce, em código?#
A classe SMTP: 5xx é um hard bounce (permanente — suprima), 4xx é um soft
bounce (temporário — reenvie). A única armadilha é um código enhanced 5.7.x, que
é um bloqueio de política permanente contra o seu remetente, e não uma caixa
postal morta — classifique-o à parte e corrija a reputação em vez de suprimir o
destinatário.
Quantas vezes devo reenviar após um soft bounce antes de suprimir?#
Não existe um número universal, mas um padrão comum é reenviar para um endereço com soft bounce ao longo de alguns envios consecutivos e escalá-lo para suprimido se ele nunca se recuperar — que é mais ou menos o que os provedores de caixa postal e os ESPs fazem internamente. Caixas postais cheias costumam se resolver sozinhas, então os soft bounces merecem uma paciência que o hard bounce não merece.
Posso classificar endereços antes de enviar para eles?#
Sim — é exatamente isso que a verificação faz. Uma sondagem de caixa postal via
SMTP produz os mesmos sinais que um bounce produziria, mapeados para um veredito
deliverable | risky | undeliverable | unknown com um código de motivo, então
você pode suprimir os endereços mortos antes que eles gerem qualquer bounce. O
mapa ACTION_BY_REASON acima transforma esses vereditos nas mesmas ações de
suprimir/reenviar/manter.
Quer os vereditos para classificar? O plano gratuito inclui 100
créditos, e a referência da API documenta os códigos de motivo e o
endpoint de resultados /jobs de onde o ciclo proativo lê.