Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Handle a Soft Bounce Email (Retry, Suppress, Prevent)

A soft bounce is a temporary 4xx refusal. Read the code, retry on a schedule, suppress what keeps failing, and size your next list by email status.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

A soft bounce is a temporary delivery failure: the receiving server answered with a 4xx code, not a 5xx. Retry it for a few days, then suppress it if it keeps failing. This guide gives you the code table, a script that applies the rules and the filter that lowers the count.

TL;DR

  • Soft bounce = 4xx reply. Hard bounce = 5xx reply. Retry the first, suppress the second (RFC 5321, September 2026).
  • Setup takes about 30 minutes. Steps 1 to 4 run on your own bounce log and spend no LeadOcean records. Step 5 pulls 25 records.
  • To prevent soft bounces, size and pull by emailStatus first. Counts are free.

Prerequisites

  • A bounce log with the SMTP reply text: a CSV export from your sending tool, or the bounce notices themselves.
  • Python 3 for the classifier in step 2.
  • A LeadOcean key in $LEADOCEAN_API_KEY for step 5. The Free plan has 1,000 records, no card.
  • curl.

Step 1: Read the code, not the label

Your sending tool says "soft bounce". The server said a number. Read the number.

SMTP groups replies by first digit. A 4xx reply means a temporary failure. A 5xx reply means a permanent one (RFC 5321, September 2026). RFC 3463 adds the dotted code. Its first digit is the class (4 is a persistent transient failure, 5 is permanent). The rest names the reason (RFC 3463, September 2026).

The soft codes you will see most:

CodeMeaning (RFC 3463)Typical cause
4.2.1Mailbox disabled, not accepting messagesAccount suspended or inactive
4.2.2Mailbox fullOver quota
4.3.2System not accepting network messagesServer down for maintenance
4.4.1No answer from hostServer offline or unreachable
4.4.7Delivery time expiredMessage queued too long

A raw bounce line looks like this (placeholders):

code
452 4.2.2 <jane.doe@acme.com>: Mailbox full

The 452 is the SMTP reply. The 4.2.2 is the enhanced code. Both start with 4, so retry.

Read the dotted code before you act, because the same 4xx class covers very different problems. A full mailbox may clear in a day. A host that is down may take longer. A rate limit clears only when you slow down.

One trap: some servers return a 4xx for policy reasons, such as a rate limit on your IP. That is a block, not a full inbox. Retrying faster makes it worse. Slow down instead.

Step 2: Classify bounces in a script

Put the rule in code so no one decides by hand. This script reads a CSV with email and reply columns and writes a bucket for each row.

python
import csv, re, sys

SOFT = re.compile(r"\b4\.\d{1,3}\.\d{1,3}\b|^\s*4\d\d\b")
HARD = re.compile(r"\b5\.\d{1,3}\.\d{1,3}\b|^\s*5\d\d\b")

def bucket(reply: str) -> str:
    if HARD.search(reply):
        return "hard"
    if SOFT.search(reply):
        return "soft"
    return "unknown"

with open(sys.argv[1], newline="") as f, open("classified.csv", "w", newline="") as out:
    w = csv.writer(out)
    w.writerow(["email", "bucket", "reply"])
    for row in csv.DictReader(f):
        w.writerow([row["email"], bucket(row["reply"]), row["reply"]])

Run it:

bash
python3 classify.py bounces.csv
head -4 classified.csv
code
email,bucket,reply
jane.doe@acme.com,soft,452 4.2.2 Mailbox full
john.roe@example.com,hard,550 5.1.1 User unknown

Check the hard rule first. A message with both digits in the text should fail safe, as a suppress.

Step 3: Set the retry rule

The rule has two numbers: how long you keep trying and how many failed sends you tolerate. Write both down before the campaign starts. Teams that decide mid-campaign tend to retry too long and keep mailing dead addresses.

Retry soft bounces for several days, then give up. RFC 5321 suggests a sender keep trying a deferred message for at least four to five days (RFC 5321, September 2026). Most sending tools do this for you. Check that yours does and what the window is.

A workable policy:

yaml
soft_bounce:
  retry_window_days: 5      # RFC 5321 floor is 4-5 days
  suppress_after:
    failed_sends: 3         # three separate sends that soft bounced
  on_success: reset_counter
hard_bounce:
  action: suppress_now

Soft bounces also cluster by domain. If many addresses at one company defer on the same day, the problem is their server, not your list. Pause that domain for a day and resend, instead of counting each address against the contact.

Two details matter. Count failed sends, not retry attempts: one send retried six times is one failure. And reset the counter when a message lands, so a mailbox that was full once is not punished forever.

Step 4: Suppress what keeps failing

Add the repeat offenders to your suppression list, and keep that list outside the campaign. Campaigns get deleted. The suppression list must not.

python
import csv, collections

fails = collections.Counter()
with open("classified.csv", newline="") as f:
    for r in csv.DictReader(f):
        if r["bucket"] == "soft":
            fails[r["email"]] += 1

with open("suppress.csv", "w", newline="") as out:
    w = csv.writer(out)
    w.writerow(["email", "soft_fails"])
    for email, n in fails.items():
        if n >= 3:
            w.writerow([email, n])

Keep the reason beside the address. A suppressed contact with a note such as "soft x3, 4.2.2" is easy to review in three months. A contact who changed jobs may deserve a fresh address later, so do not delete the record, only block the send.

Load suppress.csv into your sender's global block list. Also suppress every hard row from step 2 now.

LeadOcean gives no refund or credit for bounced emails, so this step protects your sender reputation, not your balance.

Step 5: Prevent the next batch

Most soft bounces on a new list are mailbox or server state you cannot fix. What you can do is send to addresses with a stronger verdict first. LeadOcean exposes this as the emailStatus filter. It takes 13 values from GET /v1/enums/email_status (LeadOcean API enums, September 2026): verified, catch_all_valid, catch_all, risky, unknown, untested, invalid, role, disposable, spam_trap, abuse, derived and none.

Size each group first. Send count=true as a query parameter, not in the body, with limit=1. It costs nothing (LeadOcean OpenAPI, September 2026).

bash
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": ["verified"], "jobLevel": ["VP"], "jobFunction": ["Sales & Business Development"], "country": ["CA"]}'

Counts also return distinctCompanies, which tells you how many employers the list spans. A list of 1,000 people at 20 companies carries more server risk than 1,000 people at 800 companies, because one bad server affects many rows.

Then pull the group you chose. Each person returned counts one record, so this call spends 25.

bash
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"], "jobFunction": ["Sales & Business Development"], "country": ["CA"], "limit": 25}'

The body is flat JSON and every filter is an array. Enum values are exact strings: VP, not vp, for jobLevel. The same filter works in the leadocean_count_leads MCP tool and on the Exports page at app.leadocean.io.

What you get

Same filters, three email statuses, run on 2026-10-21 with leadocean_count_leads (free, no records spent). Filters: jobLevel VP, jobFunction Sales & Business Development, country CA.

emailStatusPeopleDistinct companies
verified1,8541,392
catch_all3,7202,567
risky547506

These are counts of each status, not mailable counts. As a filter, catch_all also matches catch_all_valid. Passing emailStatus replaces the default mailable filter, so each row stands alone. The practical read: send verified first, test catch_all in a small batch, and hold risky back until you have a clean sending history.

Response shape from the API reference, with placeholder values, for the search call in step 5:

json
{
  "success": true,
  "data": [ { "...": "one Person object per match, no contact points" } ],
  "meta": { "count": 25, "limit": 25, "credits": 25, "nextCursor": "<cursor>" }
}

Search returns a thin projection without contact points. Pass nextCursor back as cursor for the next page.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported and sent as the x-api-key header
429Above the plan's rate limit (5 requests a second on Free, 200 on Agency)Back off and honor the Retry-After header
402Records spent (Free is 1,000, one-off)Move to a paid plan on pricing or narrow the pull
Empty dataBad enum value, such as vp for jobLevel or US spelled out for countryRead the exact strings from GET /v1/enums or leadocean_list_enum_values
Body rejectedcount=true placed in the JSON bodyPut it in the query string
Export refusedOver the ceiling of 50,000 rows a request or 500,000 a daySplit the filter and run several exports

FAQ

What is a soft bounce email?

A soft bounce is a temporary delivery failure. The server answered with a 4xx code, such as a full mailbox (4.2.2) or an unreachable host (4.4.1). The message may still be delivered on a later try.

How many times should I retry a soft bounce?

Keep trying for four to five days, which is the floor RFC 5321 suggests (RFC 5321, September 2026). Suppress the address after about three separate sends that soft bounced. That threshold is a policy choice, not a standard.

Soft bounce vs hard bounce: which hurts reputation more?

Hard bounces. A permanent failure says the address is bad. Repeated soft bounces to one address look the same over time, so suppress those too. For the full comparison, read why emails bounce.

What bounce rate is too high?

That depends on your sender and your tool's own limits, so check your provider's policy. See email bounce rate for how to calculate yours. We have not run a bounce test, so this page gives no benchmark.

Does LeadOcean refund bounced emails?

No. There is no refund or credit-back for bounces. Filter by emailStatus before you send, and see bounce checker to bounce management for the wider workflow.

More guides are in the blog.

Filter by email status before your next send

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →