# Hard bounce vs soft bounce: what each one means and what to do

> Hard bounce vs soft bounce: when to retry an email and how an email suppression list prevents repeat sends.

- URL: https://hail.so/blog/hard-bounce-vs-soft-bounce
- Type: Guide
- Tags: deliverability, email-api
- Published: 2026-11-02
- Reading time: 6 min

A hard bounce is a permanent email delivery failure. A soft bounce is temporary, so a later attempt may work. Check the bounce reason before you retry or stop sending to the address.

## What is a hard bounce?

A typo or a closed account can leave you sending to an address that no longer exists. The receiving server can reject it with a reply like this:

```text
550 5.1.1 user unknown
```

Code `5.1.1` means the mailbox does not exist. Sending the same message again will not fix that address.

Other `5.X.X` failures can involve authentication, message content, or a receiving server's policy. The address may still be valid. [RFC 3463](https://www.rfc-editor.org/rfc/rfc3463.html) says a permanent failure needs a change to the message or destination. Read the full error before you decide what to fix.

## What is a soft bounce?

A full mailbox, a busy server, or a temporary sending limit can cause a soft bounce. The server may accept the message later. A temporary error alone does not confirm that the address is valid.

The `4.X.X` codes describe temporary failures. For example, `4.2.2` means the mailbox is full. RFC 3463 says a later send may succeed once the temporary problem clears.

## Hard bounce vs soft bounce: key differences
| | Hard bounce | Soft bounce |
| --- | --- | --- |
| SMTP class | 5.X.X | 4.X.X |
| Example | `5.1.1` mailbox does not exist | `4.2.2` mailbox full |
| Will a later send work? | Not without fixing the cause | It may, once the temporary problem clears |
| What to do | Stop automatic retries and read the error | Check existing retries, then apply your retry limit |

## What to do after each bounce

After a hard bounce, stop automatic retries and read the error. If the mailbox does not exist, add the address to your suppression list. Ask the person for a correct address through another channel if you still need to reach them.

If the error names an authentication or policy problem, fix that cause before sending again. Do not switch senders to get around a block. If your sending service suppresses the address, resolve the cause before removing that suppression.

After a soft bounce, check whether your sending service still plans to retry the message. If it does, let those attempts finish before you submit another copy. Then track these details for each address:

- The latest bounce reason.
- The number of messages that failed in a row.
- The time since the first failure.

Set a limit for repeated failures and a time window that fits your sending frequency. For example, an app could pause sends after three consecutive soft bounces within seven days. This is an example policy, not a universal email rule. Use your sending service's retry guidance to choose your own limit.

Gmail's [sender guidelines](https://support.google.com/a/answer/81126) suggest unsubscribing recipients after multiple bounced messages. If many messages bounce at once, reduce your sending volume while you check the cause. A bad import or a broken form can fill a list with invalid addresses quickly.

## What is an email suppression list?

An email suppression list holds the addresses you must not send to. Your sending system checks it before every send and blocks a match.

Three events usually add an address:

- a hard bounce;
- a spam complaint (the recipient clicked "Report spam");
- an unsubscribe.

Keep this list in one place so every part of your app uses the same exclusions. Add addresses automatically when these events arrive. A manual update can miss an opt-out or let another message through first.

## How the List-Unsubscribe header enables one-click unsubscribe
A spam complaint means someone marked your message as spam. Gmail and Yahoo ask bulk senders to keep the spam rate below 0.3%. Gmail recommends staying below 0.1%.

Give people an easy way to unsubscribe instead of reporting spam. Gmail requires one-click unsubscribe for marketing and subscription messages from bulk senders. Its [bulk sender rules](https://support.google.com/mail/answer/14229414) cover about 5,000 or more messages a day to personal Gmail accounts. These messages also need a visible unsubscribe link in the body. Yahoo's [sender requirements](https://senders.yahooinc.com/best-practices/) also cover easy unsubscribe and require senders to honor requests within two days.

[RFC 8058](https://www.rfc-editor.org/rfc/rfc8058.html) defines one-click unsubscribe with two headers:

```text
List-Unsubscribe: <https://example.com/unsubscribe?token=abc123>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
```

The message needs a valid DKIM signature that covers both headers. The mail app can then offer an Unsubscribe button. When the recipient clicks it, the mail provider sends a `POST` to that URL. Your server records the opt-out without asking the recipient to open a page.

Test the `POST` with the body `List-Unsubscribe=One-Click`, not only a browser visit to the link. A page that loads correctly does not prove the one-click request works.

## How Hail handles bounces

[Hail](https://hail.so/email) automatically blocks future sends to addresses that hard bounce or report your email as spam. This applies to email you send through its API or an agent. The [email delivery docs](https://hail.so/docs/email) explain how suppression works.

- A hard bounce or a spam complaint adds the recipient to your organization's suppression list. A soft bounce is recorded as an event only.
- An email to exactly one recipient carries both one-click unsubscribe headers. An unsubscribe adds the address to the list. Messages with multiple recipients carry neither header.
- The next send to a suppressed address returns `403` and names the reason:

```json
{ "detail": "recipient 'bob@example.com' is suppressed (hard_bounce)" }
```

The reasons are `hard_bounce`, `spam_complaint`, and `recipient_unsubscribed`. One suppressed address in `to`, `cc`, or `bcc` blocks the whole send.

Subscribe to the `email.bounced` [webhook](https://hail.so/docs/webhooks) to receive bounce events in your app. Read `data.detail.hard` to distinguish hard bounces from soft bounces. The `data.detail.diagnostic_code` field carries the server's reply, such as `smtp; 550 5.1.1 user unknown`. Record the reason and event time to track repeated soft bounces.

List suppressed addresses with the first command below. Remove an address only after you resolve its suppression reason. For an unsubscribe, first confirm that the person wants mail again.

```bash
hail email suppressions list
hail email suppressions delete bob@example.com
```

## Bounce handling checklist

- [ ] Stop automatic retries after a hard bounce and check the cause.
- [ ] Suppress invalid addresses and fix authentication or policy errors before sending again.
- [ ] Track soft bounces and pause sends when your failure limit is reached.
- [ ] Add spam complaints and unsubscribes to the same suppression list.
- [ ] Check the suppression list before every send, in code, not by hand.
- [ ] Send `List-Unsubscribe` and `List-Unsubscribe-Post`, and test the `POST`.
- [ ] Watch your spam rate in Google Postmaster Tools.
- [ ] When many messages bounce at once, slow down and look for a bad import.

Check your domain with the [email deliverability checker](https://hail.so/tools/email-deliverability-checker). Read the [DMARC policy guide](https://hail.so/blog/dmarc-policy) if the error points to authentication. Then [send a test email through the API](https://hail.so/blog/send-email-with-an-api) to an inbox you control and read its events.
