Engineering
How to Handle Email Bounces Programmatically
Retrying a hard bounce is one of the fastest ways to torch a sending domain’s reputation. Here’s how to handle bounces correctly, in code.
MailCraft Team · April 2, 2026 · 6 min read
A bounce is a receiving server telling you, unambiguously, that a message wasn't delivered. What you do next matters more for your sender reputation than almost anything else in your sending pipeline — mishandle bounces and you'll train mailbox providers to distrust everything you send, not just the messages that bounced.
Hard bounces vs. soft bounces
A hard bounce means permanent failure — the address doesn’t exist, the domain doesn’t exist, or the receiving server has permanently rejected the message. A soft bounce means temporary failure — the mailbox is full, the receiving server is temporarily down, or the message was too large. The correct handling is completely different for each.
The rule for hard bounces: never retry, always suppress
When you receive a hard bounce, add that address to your suppression list immediately and permanently. Never attempt to send to it again unless the contact explicitly re-confirms the address through some other channel. Retrying a hard bounce — even once — is one of the clearest negative signals you can send a mailbox provider about your list hygiene.
The rule for soft bounces: retry with backoff, then give up
A soft bounce deserves a few retry attempts with exponential backoff — say, three attempts over 24–48 hours — since the underlying cause is often temporary. If an address soft-bounces repeatedly across multiple separate sends over an extended period (not just retries of one message), treat it as a hard bounce and suppress it. A mailbox that’s been full for three weeks isn’t "temporarily" unavailable.
Wiring this up with webhooks
MailCraft sends an email.bounced webhook for permanent failures and email.delayed while a temporary failure is being retried; each payload carries the email’s id, recipients and subject. A minimal, correct handler looks like this:
- Receive the webhook and verify its signature against your webhook secret.
- On email.bounced: add the address to your suppression list through the API, so no future send reaches it, and mark the contact accordingly.
- On email.delayed: nothing is required — delivery is retried — but count repeats per address and suppress once one keeps failing (3–5 times is reasonable) within a rolling window.
- Log every bounce with its recipient domain — a spike from one provider can point to a reputation or content problem worth investigating beyond individual addresses.
Don’t forget complaints — they’re not bounces, but treat them the same way
A spam complaint (recipient marked your email as spam) isn’t a bounce, but it should trigger identical handling to a hard bounce: immediate, permanent suppression. MailCraft keeps one suppression list shared across every domain, campaign and automation in your account, so once an address is on it, no future send reaches it.
Put it into practice
Start on MailCraft's free plan — 3,000 emails a month, no credit card required.
Start free