# SMTP bounce codes explained, from 550 5.1.1 to 421 4.7.0

What each SMTP reply and enhanced status code means, which are hard bounces, and how a verification handshake maps them to smtp_reason values.

Date: 2026-09-03

An SMTP bounce code is the three-digit reply a receiving mail server gives when it refuses a message, usually followed by a dotted enhanced status code that says why. `550 5.1.1` means the mailbox does not exist and is a hard bounce; `452 4.2.2` means the mailbox is full and is a soft bounce; `421 4.7.0` means the server is throttling you and says nothing about the mailbox at all. Reading the second number is what turns a bounce log into a decision.

## How are SMTP reply codes structured?

[RFC 5321](https://www.rfc-editor.org/rfc/rfc5321) defines the three-digit reply. The first digit is the verdict, the second the category, the third a detail.

| First digit | Meaning | What a sender does |
| --- | --- | --- |
| 2xx | Success | Continue |
| 3xx | More input needed (only `354` after `DATA`) | Send the content |
| 4xx | Transient failure | Queue and retry later |
| 5xx | Permanent failure | Stop and bounce |

The second digit narrows it: `x0x` syntax, `x1x` informational, `x2x` connection, `x5x` mail system. In practice the second digit of the plain code is coarse, which is why almost every modern server appends an enhanced status code.

## What are enhanced status codes?

[RFC 3463](https://www.rfc-editor.org/rfc/rfc3463) adds a `class.subject.detail` triple after the reply code, and it is the part worth parsing.

| Class | Meaning |
| --- | --- |
| 2.x.x | Success |
| 4.x.x | Persistent transient failure: retrying may succeed |
| 5.x.x | Permanent failure: retrying will not help |

| Subject | Area |
| --- | --- |
| x.0.x | Other or undefined |
| x.1.x | Addressing: the recipient or sender address |
| x.2.x | Mailbox: full, disabled, over quota |
| x.3.x | Mail system: the receiving system itself |
| x.4.x | Network and routing: cannot reach the next hop |
| x.5.x | Mail delivery protocol: bad commands or sequence |
| x.6.x | Message content or media |
| x.7.x | Security or policy: authentication, spam, rate limits |

A `5.1.1` is therefore a permanent addressing failure, and `4.7.1` a transient policy failure. The subject tells you whether the problem is the address, the mailbox, the network or your own reputation, and that is the axis a list-hygiene decision turns on.

## Which codes will you actually see?

| Reply | Meaning | Bounce type | What it says about the address |
| --- | --- | --- | --- |
| `250 2.1.5` | Recipient accepted | none | The server will take mail for it; on a catch-all server that is all it proves |
| `550 5.1.1` | Bad destination mailbox address, "user unknown" | hard | The mailbox does not exist. Remove the address |
| `550 5.1.2` | Bad destination system address | hard | The domain part is wrong or unroutable |
| `550 5.1.3` | Bad destination mailbox syntax | hard | The address itself is malformed |
| `550 5.2.1` | Mailbox disabled, not accepting messages | hard | The account exists but is closed or suspended |
| `552 5.2.2` | Mailbox full, permanent | hard | Over quota and not expected to clear; treat as risky at best |
| `452 4.2.2` | Mailbox full, transient | soft | Over quota right now; retry in days |
| `450 4.2.1` | Mailbox temporarily unavailable | soft | Often a hold or migration; retry |
| `450 4.7.1` or `451 4.7.1` | Try again later, policy | soft | Greylisting: the server wants a retry from the same sender |
| `421 4.7.0` | Too many connections or messages | soft | Throttling of the sender; nothing about the address |
| `421 4.3.2` | System not accepting network messages | soft | The server is shutting the session; retry later |
| `451 4.4.1` or `4.4.2` | Connection or transmission failure | soft | Network trouble on the receiving side |
| `550 5.7.1` | Delivery not authorized, message refused | hard for this message | A policy rejection: blocklist, reputation, missing authentication. The mailbox may be fine |
| `550 5.7.26` | Unauthenticated email is not accepted | hard for this message | The sender failed SPF or DKIM alignment; fix authentication, not the list |

Two rules of thumb follow. A `5.1.x` or `5.2.1` reply is about the address and justifies removing it. A `5.7.x` reply is about you, the sender, and removing the address would hide a problem you need to fix.

## What is the difference between a hard and a soft bounce?

A hard bounce is a permanent `5xx` failure about the recipient: the mailbox or domain does not exist. Sending platforms suppress hard-bounced addresses automatically and mailbox providers count repeated hard bounces against your reputation. A soft bounce is a transient `4xx` failure: the mailbox is full, the server is busy, the message is too large. Senders retry soft bounces for a day or two and then give up.

The line blurs at the edges. A `552 5.2.2` is permanent by class but describes a full mailbox, and a `550 5.7.1` is permanent for this message but says nothing about the recipient. Classifying purely by the first digit gets both wrong, which is why the enhanced code matters.

## How does a verification handshake use these codes?

A verifier never sends `DATA`, so it only sees replies up to `RCPT TO`. That is enough to classify the mailbox, and Spaw reports the classification in `smtp_reason` next to the verdict.

| Server reply during the handshake | `smtp_reason` | Verdict |
| --- | --- | --- |
| `5.1.1` user unknown at `RCPT TO` | `no_mailbox` | `undeliverable`, reason `mailbox_not_found` |
| `5.2.1` mailbox disabled | `disabled` | `undeliverable`, reason `mailbox_disabled` |
| `5.2.2` or `4.2.2` mailbox full | `mailbox_full` | `risky`, reason `mailbox_full` |
| `4.7.x` come back later | `greylisted` | rests on the other signals, `smtp_checked: false` |
| No reply within the time limit | `timeout` | rests on the other signals |
| Connection refused on port 25 | `no_connect` | rests on the other signals |
| Host unreachable | `mx_unreachable` | rests on the other signals |
| `5.7.x` policy rejection or any ambiguous refusal | unverified, never guessed | rests on the other signals |

Only an explicit "no such user" becomes `no_mailbox`. A policy rejection during verification is not evidence about the mailbox, so Spaw leaves `mailbox_exists` at `null` rather than turning a reputation problem on the verifier's side into a bounce prediction for you.

```bash
curl https://spaw.co/api/v1/email \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email": "undeliverable@spaw.test"}'
```

```json
{
  "success": true,
  "data": {
    "email": "undeliverable@spaw.test",
    "deliverable": "undeliverable",
    "reason": "mailbox_not_found",
    "risk_score": 100,
    "smtp_checked": true,
    "mailbox_exists": false,
    "smtp_reason": "no_mailbox",
    "// 20 more fields": "see the endpoint reference"
  },
  "meta": { "credits_used": 0, "credits_remaining": 10, "cache_hit": false }
}
```

Undeliverable answers are always free, and the address is added to your suppression list so batch and bulk runs answer it without another handshake.

## What should you do with bounce codes from your own sends?

Feed them back. The codes your sending platform records after a real delivery attempt are stronger evidence than any handshake, in both directions: a delivery proves the mailbox, a `5.1.1` disproves it. Post the outcome to the feedback endpoint with the diagnostic string, or point your provider's bounce webhook at Spaw and let the adapters for Postmark, Amazon SES, Mailgun and SendGrid map their event types. Hard bounces join your suppression list, deliveries turn later lookups into confirmed mailboxes, and the per-domain bounce rate feeds the confidence score for catch-all addresses.

## What to do next

- Map your bounce log against the `smtp_reason` values on the [mailbox-check details page](/docs/reasons#smtp).
- Verify addresses before sending with the [handshake described here](/guides/verify-email-without-sending); the test address `undeliverable@spaw.test` shows the exact shape of a hard-bounce answer.
- Send your provider's bounce events to the [feedback endpoint](/docs/api/report-delivery-feedback) so `5.1.1` replies from real sends suppress addresses automatically.
- Treat every `5.7.x` reply as a sender problem and check your [SPF, DKIM and DMARC](/guides/spf-dkim-dmarc-for-developers) before touching the list.

Reference: https://spaw.co/guides/smtp-bounce-codes-explained
