An email bounce code is a three-part number, such as 5.1.1, that tells you whether to retry, suppress the address or fix your own sending setup. The first digit decides the first move. The second and third digits tell you what to fix.
This page gives you the codes a cold outbound pipeline meets most, a classifier that acts on them, and a free check that removes the riskiest addresses before you send. For causes, read why B2B emails bounce. For the rate to watch, read email bounce rate.
TL;DR
- Read the enhanced status code (5.1.1), not your tool's "hard" or "soft" label. A 4 means retry, a 5 means stop and read the subject digit.
- A 5.1.x code means the address is dead. A 5.7.x code usually means your authentication, IP or content is the problem, so the address may be fine.
- About 30 minutes. Sizing audiences by
emailStatusis free. The optional test pull spends up to 26 LeadOcean records.
Prerequisites
- Raw bounce messages (DSNs) or the
Diagnostic-Codefield from your sequencer's export. - Python 3 for the classifier in Step 3.
- A LeadOcean API key in
$LEADOCEAN_API_KEYfor Steps 4 and 5. Start free at app.leadocean.io: 1,000 records, no card. curlandjq.
Step 1: Pull the code out of the bounce
Every bounce is a delivery status notification (DSN). It has a Status: field with the enhanced code and a Diagnostic-Code: field with the receiving server's own words (RFC 3464, September 2026). Keep both. The code is for your logic. The text is for the exceptions.
Final-Recipient: rfc822; jane.doe@acme.com
Action: failed
Status: 5.1.1
Diagnostic-Code: smtp; 550 5.1.1 The email account that you tried to reach does not exist.The three parts are class, subject and detail. In 5.1.1, class 5 is a permanent failure, subject 1 is addressing and detail 1 is a bad destination mailbox (RFC 3463, September 2026). A 4 is a persistent transient failure: the message was valid and a later try may work.
Step 2: Decode it with the table
Use this table for the codes you will see. The first rows are on the recipient. The later rows are on you. Codes and meanings come from RFC 3463 and Google's published SMTP error list (Google Workspace Help, September 2026). The action column is our default, not a standard.
| Code | Meaning | Whose problem | Action |
|---|---|---|---|
| 5.1.1 | Bad destination mailbox | Address | Suppress now |
| 5.1.2 | Bad destination system (domain) | Address | Suppress, check for a typo |
| 5.1.3 | Address syntax invalid | Address | Fix the address or suppress |
| 5.2.1 | Mailbox disabled or inactive | Address | Suppress (see the note below) |
| 5.2.2 | Mailbox full, now rejecting | Address | Pause 30 days, retry once |
| 4.2.2 | Mailbox full, temporary | Recipient | Retry over 3 to 7 days, then stop |
| 4.4.7 | Delivery time expired | Network | Retry later |
| 4.4.5 | Server busy | Receiver | Retry with backoff |
| 4.7.0 | Rate limited or low reputation | You | Slow down, fix reputation |
| 5.4.4 | Unable to route | Domain | Check MX, suppress the domain |
| 5.6.0 | Message malformed | You | Fix the message |
| 5.7.1 | Refused by policy | Usually you | Read the text before suppressing |
| 5.7.26 | Sender unauthenticated | You | Fix SPF, DKIM or DMARC |
Two rows deserve a second look. Google uses 5.2.1 for an inactive account and also for a recipient receiving mail too fast, so read the diagnostic text. And 5.7.1 covers a custom admin rule, a bad IP reputation and likely spam, none of which is a dead mailbox.
When a 5xx is not a dead address
The costly mistake is suppressing every 5xx. RFC 3463 says a permanent failure is one that will not resolve by resending the message in its current form. It does not say the address is bad. The subject digit tells you which part of the message or setup has to change.
Use the subject digit as a quick sort:
- Subject 1 is addressing. The mailbox or domain is wrong. Suppress.
- Subject 2 is the mailbox. It is disabled or full. Suppress or pause.
- Subject 4 is routing and network. Check DNS and the domain's mail server.
- Subject 6 is the message itself. Fix the content or headers.
- Subject 7 is security or policy. Check your SPF, DKIM, DMARC, IP and links.
Each provider also publishes its own list. Google lists its codes with exact wording, and Microsoft documents its Exchange Online non-delivery reports (Microsoft Learn, September 2026). Match the text from the receiving provider against its own list before you write a rule.
A retry policy that fits on one line
Retry a 4xx three times over about three days, with longer gaps each time. After that, stop and log the address as undelivered. Never retry a 5.1.x code. Do not mix retries with new sends in the same batch, or you hide a real bounce rate behind repeat attempts.
Step 3: Classify bounces in code
Classify on the enhanced code and keep the diagnostic text for review. This script reads a CSV of bounces with a diagnostic column and writes an action for each row. It is a starting point, so tune the maps to your own policy.
import csv, re, sys
SUPPRESS = {"5.1.1", "5.1.2", "5.2.1", "5.1.3"}
FIX_SETUP = {"5.7.1", "5.7.26", "5.6.0", "4.7.0"}
RETRY_CLASS = "4"
CODE = re.compile(r"\b([245])\.(\d{1,3})\.(\d{1,3})\b")
def action(text):
m = CODE.search(text)
if not m:
return "review", None
code = m.group(0)
if code in SUPPRESS:
return "suppress", code
if code in FIX_SETUP:
return "fix_setup", code
if m.group(1) == RETRY_CLASS:
return "retry", code
return "review", code
for row in csv.DictReader(open(sys.argv[1])):
a, c = action(row["diagnostic"])
print(f'{row["email"]},{c},{a}')Run it as python3 classify.py bounces.csv > actions.csv. Rows marked fix_setup are not suppressed. Check your authentication and sending IP first, because the same fault will reject other addresses too.
Step 4: Size the risky groups before you send
The cheapest bounce is the one you never send. LeadOcean publishes email_status for every person, and the filter emailStatus takes the values from GET /v1/enums/email_status. Sizing is free: send count=true as a query parameter with limit=1 (LeadOcean OpenAPI, September 2026).
for s in verified catch_all risky; do
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"emailStatus\": [\"$s\"], \"jobLevel\": [\"VP\"], \"country\": [\"US\"]}" \
| jq -c "{status: \"$s\", total: .meta.total}"
donePassing emailStatus replaces the default filter, which counts verified, catch_all_valid and catch_all. So you choose the group. Send verified first. Send catch_all later in a smaller batch. Leave risky, role, invalid, disposable, spam_trap and abuse out.
Step 5: Confirm one address before a big pull
Search rows carry email_status, but the docs warn that a search count of verified is an upper bound while the stricter rule rolls out. Enrichment applies the narrow rule per address, so it is the check to trust. Pull a small page, then enrich one person by person_id.
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
-d '{"emailStatus": ["verified"], "jobLevel": ["VP"], "country": ["US"], "limit": 25}' \
| jq -r '.meta.nextCursor, .data[0].meta.source_ids.person_id'
curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "Content-Type: application/json" \
-d '{"person_id": "1234567890", "reveal_email": true}' \
| jq '.contact_data.contact_emails'Replace 1234567890 with the id the first call prints. The search spends 25 records. The enrichment spends one. LeadOcean does not refund or credit back bounced emails, so read the status before you send.
What you get
Free counts from the LeadOcean MCP tool leadocean_count_leads on 2026-10-21, for people who are VPs in the US, split by emailStatus only. These are counts by email status. They are not mailable counts. The REST loop in Step 4 stops at 100,000 per call, so use the MCP tool or narrower filters for buckets this wide.
emailStatus filter | People | Meaning |
|---|---|---|
verified | 257,840 | Upper bound while the stricter rule rolls out |
catch_all | 984,961 | Also matches catch_all_valid |
risky | 72,112 | Use with care |
The enrichment call returns the shape below. This is the response shape from the API reference, with placeholder values.
{
"contact_data": {
"has_email": true,
"email_status": "verified",
"email_type": "work",
"contact_emails": [
{
"email": "jane.doe@acme.com",
"type": "work",
"status": "verified",
"verified_batch_date": "2026-09-20"
}
]
}
}A null in verified_batch_date means our own verifier never tested that address, per the API reference. Treat those as lower confidence than a dated verified.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key header | Send x-api-key: $LEADOCEAN_API_KEY and check the key in the app |
| 429 | Above your plan's rate limit per account (5 a second on Free, 200 on Agency) | Wait the Retry-After seconds, add a short sleep or a queue, then retry |
| 402 | The key has spent its records | Check usage in the app. Free is 1,000 one-off records |
| Empty results | A filter value that is not an exact enum string | Check the value against GET /v1/enums/email_status and the other enums |
| 404 on enrich | We hold nothing for that person | Nothing to fix. A 404 costs no record |
| Export refused | Over 50,000 rows a request or 500,000 a day | Split the export into smaller filtered runs |
FAQ
What is the difference between a hard and a soft bounce code?
A 5xx is a permanent failure and a 4xx is a persistent transient failure. The label your tool shows is a summary of that first digit. Read the full code, because 5.7.x often blames your setup, not the address.
Does a 550 always mean suppress the address?
No. A 550 5.1.1 means the mailbox does not exist, so suppress it. A 550 5.7.1 is a policy refusal and can come from your IP, content or a recipient admin rule. Read the text first.
How many times should I retry a 4xx bounce?
Retry with growing gaps for a few days, then stop. The 3 to 7 day window in the table is our default, not a standard. Keep a count per address so a stuck mailbox does not retry forever.
Can catch all addresses bounce?
Yes. A catch all domain says yes to every address at send time, so a missing mailbox can bounce later. See catch all emails explained. We have not run a bounce test, so this page gives no rate.
Where do I go next?
Start with bounce checker to email bounce management. Plans are Free, Solo at $97, Agency at $497 and Platform at $1,997 a month, on the pricing page. More guides are in the blog hub.
Size your verified and catch all lists before you send
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →