Skip to main content

Blog

Hard bounce vs soft bounce: what each means and what your app should do

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 bounceSoft bounce
What it meansDelivery failed and won't work if you try againDelivery failed this time but may work later
SMTP reply codeStarts with 5, such as 550 5.1.1Starts with 4, such as 421 4.7.0
Common causesThe address or domain doesn't exist, or the account was closedFull mailbox, server down or busy, rate limiting, message too large
RetryingDon't. It fails again and hurts your sender reputationMail servers retry automatically before reporting it
What to do with the addressStop sending to it and ask the person for a new oneKeep it, and treat it as hard if it keeps bouncing
Effect on your bounce rateCounts toward itUsually 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.x is about the address, x.2.x the mailbox, x.4.x the network or routing, and x.7.x security 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.

CodeMeaningTypeWhat to do
550 5.1.1The mailbox doesn't existHardStop sending. Ask the person to check the address
556 5.1.10The domain says it accepts no mail (null MX)HardStop sending
550 5.2.1The mailbox is disabledHardStop sending
452 4.2.2The mailbox is fullSoftNothing. It's retried automatically
552 5.2.2The mailbox is full, reported as permanentUsually softSome servers send a full mailbox as 5xx. Most senders still treat it as soft
421 4.7.0Try again later, often rate limiting or greylistingSoftNothing, unless it happens a lot. Then send more slowly
552 5.3.4The message is too largeHard for this messageSend a smaller message. The address is fine
550 5.7.1Refused by policy: spam filter, block or contentHardCheck your content, domain and reputation
550 5.7.26Gmail: the message failed authenticationHardFix your DKIM, SPF and DMARC records
4.4.7The message expired after retries ran outSoftThe 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.com or hotmial.com in 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.

TypeScript
// 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.

Send your first email today.

Free includes 3,000 emails every month. No card needed.