Hard bounces, soft bounces, and what to do with each
Not every bounce means the same thing. Treating them identically either burns your reputation or loses you customers.
A bounce is a receiving server telling you it will not deliver your message. The reason determines whether you should ever try that address again.
Hard bounces: stop immediately
A hard bounce is permanent. The mailbox does not exist, the domain does not resolve, or the server has explicitly refused you.
550 5.1.1 User unknown
Retrying achieves nothing except proving to the receiver that you do not process bounces — which is itself a spam signal. Suppress the address on the first hard bounce. Not the third. The first.
Keep hard bounces under 2%. Amazon SES places accounts under review above 5%, and that review can end in a sending pause.
Soft bounces: retry, then give up
A soft bounce is temporary. The mailbox is full, the server is overloaded, or you are being rate limited.
452 4.2.2 Mailbox full
421 4.7.0 Too many messages from your IP
These are worth retrying with exponential backoff. But a mailbox that has been full for two weeks is abandoned. After four or five failures across several days, treat it as permanent and suppress it.
Blocks: a different problem entirely
Sometimes the rejection is about you, not the recipient:
554 5.7.1 Message rejected due to poor reputation
Retrying makes this worse. This is a signal to stop and investigate — check your complaint rate, whether your IP is on a blocklist, and what you sent recently.
Complaints outrank everything
A complaint is someone pressing "this is spam". It is not a bounce; the message was delivered and then rejected by a human.
Suppress immediately and permanently. Never require a second complaint. And treat a rising complaint rate as an emergency rather than a metric — it is the fastest route to being blocked wholesale.
Building it properly
Bounce data arrives asynchronously, minutes or hours after the send. You need a webhook, not a response code.
Three things your handler must get right:
Verify the notification is genuine. An unauthenticated bounce webhook lets anyone suppress any address on your platform.
Store the message ID. It is how a bounce that arrives an hour later is matched to the send that caused it. Index that column — at scale it is queried on every event.
Make suppression the send path's concern. A suppression list only works if something checks it before every send. A list nothing reads is just a table.