Every sending platform splits bounces into two columns, hard and soft, and the layout makes them look like a severity scale. They are not two sizes of the same thing. A hard bounce is a statement about a mailbox. A soft bounce is a statement about a moment.
What the codes actually say
| Response | What it is about | What it licenses |
|---|---|---|
| 550 5.1.1 user unknown | The mailbox | Retire the address - the server looked and could not find it |
| 550 5.1.2 domain not found | The domain, not the person | Retire the domain decision, then ask whether the employer changed |
| 452 / 552 mailbox full | The moment | Nothing. A full mailbox is a working mailbox with a storage problem |
| 421 / 450 greylisting, rate limits | Your probing pattern | Slow down and re-probe. The address was never judged |
| 5.x.x from a relays in the path | The relay | Nothing about the recipient |
Only the first two rows are statements about whether the address can exist. The rest are statements about the attempt.
The expensive habit
Most pipelines act on one boolean - did the send fail - and delete the contact. That collapses three different events into one: a mailbox that never existed, a mailbox that was full on Tuesday, and a probe that got rate-limited. Two of those three deletions cost you a live contact, and nothing downstream reports the loss, because there is no event attached to a contact that was never contacted.
Meanwhile the list keeps moving underneath the decision. 23% of contacts change jobs in a year (ZoomInfo, 2025) and 40% of email addresses are dead within two years (NeverBounce). A clean list is not a static object waiting to be cleaned; it is decaying while you decide what to delete.
The pattern matters too. If most of your "hard" bounces come from one source or one domain, that is a sourcing finding, not a list-quality finding - and deleting rows individually files it as noise.
The rule that survives contact with reality
- Retire only on an explicit negative answer. An address leaves when a server said the mailbox does not exist - not when the attempt failed.
- Keep unresolved as its own state, with a date. A timeout with no timestamp is indistinguishable from a mailbox that died six months later.
- Re-probe before the send, not on a calendar. The reason to re-check is that a batch is going out; a monthly scrub validates dates, not deliverability.
- Watch the soft pile for a pattern. Repeated soft bounces on one domain usually mean a blocked path or an aggressive gateway, not a list of dead people.
- Log domain-level failures separately. "Domain gone" and "person gone" lead to different fixes, and only one of them means find a new contact at the same company.
What a checker can add
The published spread between checked and unchecked contacts is 2-5% bounce against 10-35% for static resold databases, and that gap is exactly this distinction: a check that answers about the mailbox, not about the attempt.
TAPAC probes the mailbox inside the search that found the contact, so each result arrives with one of three states - deliverable, undeliverable, or unknown for catch-all domains - plus the time of the call and the source page or profile the address came from. That timestamp is what separates a contact worth retrying from one worth retiring.
npx -y @tapacapi/mcp # tools: tapac_find_contacts, tapac_status
# hosted: https://tapacapi.com/mcp
# REST: POST https://tapacapi.com/v1/contacts/search
Pay-per-use, $0.10-0.50 per contact, 100 free searches.
Top comments (0)