A hard bounce is a permanent delivery failure: the address doesn't exist, the domain doesn't take mail, or the receiving server has refused your message for good. Stop sending to that address. A soft bounce is a temporary failure: the mailbox is full, the server is busy, or it asked you to try later. Mail servers retry those on their own, so you only need to act if the same address keeps soft-bouncing. Below are the causes of each, how to read the codes in a bounce message, and the code to handle both.
Hard bounce vs soft bounce at a glance
| Hard bounce | Soft bounce | |
|---|---|---|
| What it means | Delivery failed and won't work if you try again | Delivery failed this time but may work later |
| SMTP reply code | Starts with 5, such as 550 5.1.1 | Starts with 4, such as 421 4.7.0 |
| Common causes | The address or domain doesn't exist, or the account was closed | Full mailbox, server down or busy, rate limiting, message too large |
| Retrying | Don't. It fails again and hurts your sender reputation | Mail servers retry automatically before reporting it |
| What to do with the address | Stop sending to it and ask the person for a new one | Keep it, and treat it as hard if it keeps bouncing |
| Effect on your bounce rate | Counts toward it | Usually doesn't |
What is a hard bounce?
A hard bounce means the receiving mail server rejected the message and the problem won't fix itself. The usual causes:
- The address doesn't exist. A typo at sign-up (
jane@gmial.com,jnae@example.com), a deleted account, or someone who left the company that hosted their email. - The domain doesn't exist or doesn't accept mail. The domain expired, was mistyped, or has no mail server.
- The account is disabled. Some providers keep a closed account's address around and reject everything sent to it.
Each hard bounce is a message to an address that can't receive mail. Mailbox providers read a high hard bounce rate as a sign that you don't look after your list, which hurts how they treat the rest of your mail, including mail to good addresses. So a hard-bounced address should never be sent to again unless the person gives it to you again.
When a 5xx rejection isn't about the address
Some permanent rejections are about your mail, not the recipient. The receiving server refused it because the message failed authentication, matched a spam filter, or came from a sender it has blocked. These come with 5.7.x codes. If one address bounces this way, the address is probably fine. If many different addresses bounce with the same 5.7.x reason, fix your sending setup before you send more. Taking those addresses off your list won't fix anything.
What is a soft bounce?
A soft bounce means the receiving server couldn't take the message right now. The common causes:
- The mailbox is full. The account exists, but it's over its storage quota.
- The server is down, busy or unreachable. This includes temporary DNS failures.
- The server asked you to slow down or try later. Large providers limit how fast they accept mail from one sender, and some servers deliberately refuse the first attempt from an unknown sender to see if it comes back (greylisting).
- The message is too large for the receiving server. This one won't go away by retrying; send a smaller message.
You rarely need to retry a soft bounce yourself. When a server replies with a 4xx code, the sending mail server keeps the message and tries again, usually for hours or days. The soft bounce you hear about is what's left after those retries ran out. While they're still going, the email shows as delayed, not bounced.
How to read a bounce message
A bounce carries the receiving server's reply. It has two parts: a three-digit SMTP reply code, and often an enhanced status code in the form class.subject.detail (defined in RFC 3463).
- The first digit gives you the class. 4 is temporary, so it's a soft bounce. 5 is permanent, so it's a hard bounce.
- The rest says why.
x.1.xis about the address,x.2.xthe mailbox,x.4.xthe network or routing, andx.7.xsecurity or policy.
The text after the code is written by the receiving server, so the wording differs between providers. The numbers mean the same everywhere.
| Code | Meaning | Type | What to do |
|---|---|---|---|
550 5.1.1 | The mailbox doesn't exist | Hard | Stop sending. Ask the person to check the address |
556 5.1.10 | The domain says it accepts no mail (null MX) | Hard | Stop sending |
550 5.2.1 | The mailbox is disabled | Hard | Stop sending |
452 4.2.2 | The mailbox is full | Soft | Nothing. It's retried automatically |
552 5.2.2 | The mailbox is full, reported as permanent | Usually soft | Some servers send a full mailbox as 5xx. Most senders still treat it as soft |
421 4.7.0 | Try again later, often rate limiting or greylisting | Soft | Nothing, unless it happens a lot. Then send more slowly |
552 5.3.4 | The message is too large | Hard for this message | Send a smaller message. The address is fine |
550 5.7.1 | Refused by policy: spam filter, block or content | Hard | Check your content, domain and reputation |
550 5.7.26 | Gmail: the message failed authentication | Hard | Fix your DKIM, SPF and DMARC records |
4.4.7 | The message expired after retries ran out | Soft | The address may be fine. Check it if it happens again |
What your app should do
The aim is simple: stop sending to addresses that can't receive mail, and don't give up on good addresses because of a temporary problem.
- On a hard bounce, stop sending to the address and tell the person. Mark it as undeliverable in your database. Next time they sign in, ask them to confirm or change their email. For a sign-up confirmation that bounces, the address almost always has a typo, so show it to them and let them fix it.
- On a soft bounce, don't retry straight away. The retries already happened. Record it, and if the same address soft-bounces again and again, say three times in a few weeks, treat it like a hard bounce.
- On a policy bounce (5.7.x), look at your sending, not the address. Check that your domain passes DKIM, SPF and DMARC, and that your content doesn't look like spam.
- Keep your bounce rate low. Amazon SES, for example, recommends staying under 2%, puts accounts under review at 5% and may pause sending at 10%. Only hard bounces count toward that rate.
Most hard bounces can be prevented before the first send:
- Confirm addresses at sign-up with a confirmation email that the person has to click.
- Catch obvious typos like
gmial.comorhotmial.comin the sign-up form and ask the person to check. - Don't send to old or imported lists that you haven't emailed in a long time. Addresses go stale, and some old addresses are turned into spam traps.
- Test with real addresses you control. A made-up address at a real domain hard-bounces and counts against you like any other.
Handling bounces with Sendaro
Sendaro does the first part for you. A hard bounce adds the address to your workspace's suppression list automatically, along with spam complaints, and later sends to it end as failed without being sent. Soft bounces are recorded but don't suppress the address. A bounce that can't be classified either way is treated as hard.
Either way, the email's status becomes bounced and your app gets an email.bounced webhook. The event history on the email says which kind it was: the bounce event's payload has bounceType (hard or soft) and reason, which holds the receiving server's reply when it gave one.
This Next.js route handler verifies the webhook, looks up the bounce, and updates your app. It suppresses an address after three soft bounces, using an API key with the emails:read and suppressions:write scopes. db stands for your own data layer.
// app/api/sendaro/route.ts
import { Sendaro, verifyWebhook } from "sendaro";
const sendaro = new Sendaro(); // reads SENDARO_API_KEY
export async function POST(request: Request) {
// Use the raw body. Parsing and re-serialising JSON breaks the signature.
const payload = await request.text();
const valid = verifyWebhook(
payload,
request.headers.get("rf-signature"),
process.env.SENDARO_WEBHOOK_SECRET!,
request.headers.get("rf-timestamp"),
);
if (!valid) return new Response("Invalid signature", { status: 401 });
const event = JSON.parse(payload);
if (event.type !== "email.bounced") return new Response(null, { status: 204 });
const email = await sendaro.get(event.data.id);
const bounce = email.events.find((e) => e.type === "bounce");
if (!email.to || !bounce) return new Response(null, { status: 204 });
const reason = String(bounce.payload.reason ?? email.error ?? "");
if (bounce.payload.bounceType === "soft") {
// Webhooks can be retried, so count each email id once.
const softBounces = await db.bounces.recordSoft(email.to, email.id, reason);
if (softBounces >= 3) {
await sendaro.addSuppression({ email: email.to, reason: "manual", sourceRef: email.id });
await db.users.markEmailUndeliverable(email.to, reason);
}
} else {
// Sendaro has already suppressed the address. Stop using it in your app too.
await db.users.markEmailUndeliverable(email.to, reason);
}
return new Response(null, { status: 204 });
}When someone changes their email to a new address, there's nothing to remove. If they fix the same address, for example after their mailbox was full for weeks, remove it from the list with DELETE /api/suppression?email=… or sendaro.removeSuppression(address).
Questions
Is a soft bounce bad?
One soft bounce isn't. Full mailboxes and busy servers are normal, and the message was retried before it was reported. A soft bounce becomes a problem when the same address keeps doing it, or when many addresses at one provider soft-bounce at once, which usually means that provider is limiting your mail.
Can a soft bounce turn into a hard bounce?
Not by itself, but the cause can change. A mailbox that stays full for months is often abandoned, and the provider may close it. That's why a common rule is to treat an address as hard-bounced after a few soft bounces in a row.
What is a good email bounce rate?
As low as you can get it. For transactional email, which goes to people who just gave you their address, the hard bounce rate should be well under 2%. A higher rate usually means missing address confirmation at sign-up, or an old list.
Why did my email bounce when the address is correct?
Check the code. A 4xx code means a temporary problem on the recipient's side, such as a full mailbox. A 5.7.x code means the receiving server refused your message, not the address, often because your domain failed DKIM, SPF or DMARC checks. A 552 5.3.4 means the message was too large.
Should I remove a bounced address from the suppression list?
Only when you have a reason to think it works now, for example the person confirmed it or fixed their mailbox. Sending to it again just to check means another hard bounce if it still doesn't work.