# SMTP Error Codes Explained: What 250, 421, 450, and 550 Actually Mean

**SMTP reply codes fall into three classes: 2xx means the receiving mail server accepted the address, 4xx means a temporary condition — try again later, not "invalid" — and 5xx means a permanent rejection. Inside 4xx and 5xx, an "enhanced status code" like `5.1.1` or `5.7.1` narrows the reason further, but the base three-digit number is the part that actually decides whether a mailbox looks real, temporarily unreachable, or gone for good.**

If you're reading this with a bounce log open, you don't need the protocol history — you need to know what the specific code in front of you means for that address, and whether to remove it, retry it, or ignore it. Below is a working reference for the codes that come up constantly, plus something most reference pages on this topic can't give you: how a real SMTP probe — not a spec, an actual running one — turns each of these into a verdict.

## The three reply-code classes, at a glance

| Class | Range | What it means | What it implies about the mailbox |
|---|---|---|---|
| 2xx | 200–299 | The server accepted the command | The mailbox looked real and reachable at that exact moment — not a permanent guarantee |
| 4xx | 400–499 | Temporary failure | Unknown, not invalid — the server is asking to be tried again, is rate-limiting, or is deferring |
| 5xx | 500–599 | Permanent failure | The server refused the address for good; treat it as terminal |

Everything below breaks each class down further, including the part every raw code list skips: what happens to a real verdict once one of these actually comes back.

## 250: accepted — not the same as deliverable

A `250` response means the receiving mail server said yes to the `RCPT TO` command for that specific address, in that specific moment. In PingValid's pipeline, that response passes the SMTP-level check — but a pass on this one check doesn't by itself make the overall verdict `valid`. The final verdict still depends on every other check that ran alongside it: syntax, DNS, MX, disposable/role filtering, and a separate catch-all check (more on that below). A `250` on an address that also matches a disposable pattern, for instance, still ends up `invalid` — the SMTP accept doesn't override it.

There's a second reason a `250` isn't proof of deliverability: the probe never gets past `RCPT TO`. It never issues `DATA`, so it never learns how the server would have handled the actual message — content filtering, spam scoring, and anything that happens after acceptance are outside what any RCPT TO-level check can see. A mailbox can also change state — get deactivated, fill up, get suspended — in the time between a check and a real send. A `250` confirms the mailbox existed when it was asked, nothing about tomorrow.

## The 4xx class: temporary, not invalid

A 4xx code is the server saying "not right now," not "this address is bad." Reducing every 4xx to a hard fail is the single most common mistake in list-cleaning: it throws away addresses that would validate cleanly an hour later.

**Greylisting** is the most common reason a real mailbox returns a 4xx. It's a spam-control technique: the receiving server temporarily refuses an unfamiliar sender/recipient/IP combination on the first attempt, on the theory that spam software rarely retries and a real mail server always does. A greylisted address isn't invalid — it's asking to be tried again, sometimes minutes later, sometimes longer.

PingValid's SMTP-level check treats this as its own case rather than a generic failure. Specifically: response codes **450, 451, and 452 are always read as greylist-shaped**, regardless of the message text. Code **421 is only read as greylisting if the server's response text contains a recognizable hint** — wording like "greylist," "try again," "try later," or "deferred." A bare 421 with no such wording is treated as an ordinary transient failure instead. Either way, the outcome at the SMTP-level check is `unknown`, not `fail`.

One distinction worth being precise about, because it decides what you pay: billing keys off the **overall verdict**, not off this one check's outcome. If nothing else on the address resolves it either, the final verdict is `unknown` and that check costs no credits. But the other checks keep scoring after the SMTP step returns unknown — so if a disposable match, or a role-account and catch-all result together, push the score high enough to land `invalid` or `risky`, the check is billed at the normal rate even though the SMTP outcome still reads `unknown`.

That's a deliberate design choice, not an accident: an `unknown` verdict means the check genuinely couldn't reach a confident answer, and forcing a fail or a pass out of "try again later" would be worse than saying so. If none of the mail servers for a domain give a clean accept or reject — whether that's greylisting or just a run of ordinary transient errors — the check works through every mail server for the domain, in priority order (subject to a per-server rate limit so it doesn't hammer one host), and only returns `unknown` once none of them resolve it.

## The 5xx class: permanent rejection — and where the probe stops reading

A 5xx code means the receiving server refused the address outright: no such mailbox, no such user, domain doesn't accept it, or a policy block. PingValid's SMTP-level check treats any 5xx the same way — a fail — and because this specific check is a blocking step in the pipeline, that single fail forces the overall verdict to `invalid` with a risk score of 100 immediately, without waiting on or averaging in any other check.

Here's the honest limitation worth stating plainly: that classifier reads the **numeric code only**. It does not parse the enhanced status code (the `x.y.z` part) to tell a "no such user" rejection apart from a policy-based one. A `550 5.1.1` and a `550 5.7.1` both just register as "5xx" to the check — both fail the same way. That matters, because those two enhanced codes don't mean the same thing about the address, which is the next section.

## Enhanced status codes: what 5.1.1, 5.7.1, and the rest mean

The three-digit code says which class a response falls in. The `x.y.z` enhanced status code that often rides alongside it narrows the reason. These are the ones that come up most in bounce logs:

| Enhanced code | Base class | Conventional meaning |
|---|---|---|
| `5.1.1` | Permanent | Bad destination mailbox address — the classic "no such user" |
| `5.1.2` | Permanent | Bad destination system address — the domain/system doesn't recognize the address |
| `5.7.1` | Permanent | Delivery not authorized, message refused — a **policy** rejection (relay access denied, sender not authorized). Not necessarily a statement that the mailbox doesn't exist |
| `4.7.1` | Temporary | Delivery not authorized, temporary — several mail servers use this exact code for greylisting |
| `4.2.2` | Temporary | Mailbox full — try again later |
| `5.2.2` | Permanent | Mailbox full and permanently over quota |
| `4.4.1` | Temporary | No answer from the remote host — a connectivity/timeout issue, not a statement about the address at all |

`5.7.1` — the one behind "relay access denied" — is worth isolating because it's the code most likely to be mistaken for "this address doesn't exist." It usually isn't saying that. It's a policy decision by the receiving server: it doesn't like this sender, this connection, or this relay attempt, independent of whether the specific mailbox is real. As covered above, PingValid's check still treats it as a fail (it's a 5xx), which is defensible as a conservative default — but it's a different kind of rejection than a `5.1.1`, and worth reading differently if you're deciding whether to trust the signal or investigate the sending side instead.

## Why some servers lie

Not every response is answering the question you think it's asking. Two patterns worth knowing about:

Some receiving servers deliberately blur the RCPT TO signal on purpose, specifically so a prober can't use it to enumerate which addresses exist on their domain. In practice that shows up as either: accepting almost anything at RCPT TO time regardless of whether the specific mailbox is real (functioning like a catch-all whether or not the domain is formally configured as one), or the opposite — rejecting unfamiliar probe-shaped connections with a policy-flavored 5xx (often `5.7.1`) regardless of whether the address is genuine.

Separately, some mail systems accept the message at RCPT TO and DATA time and only reject it afterward, generating a bounce well after the SMTP conversation closed. A live probe that stops at RCPT TO and never sends `DATA` — which is how PingValid's check works, and how a legitimate check should work, since it never actually delivers a message to test — has no way to see that outcome. It isn't a gap specific to any one tool; it's a limit of what any RCPT TO-only check can observe.

Both are reasons a single reply code is a signal, not a verdict — which is the whole argument for scoring it alongside other checks instead of trusting it alone.

## Catch-all domains: why RCPT TO alone is uninformative

A catch-all (or "accept-all") domain accepts mail for any address at that domain, real or not, instead of rejecting unrecognized local parts. Against a catch-all domain, a `250` on the address you're actually checking tells you almost nothing on its own — the same server would say `250` to a string of random characters at the same domain. (Full definition and how it interacts with MX records: [email verification vs. validation](/blog/email-verification-vs-validation).)

PingValid runs a dedicated check for this rather than trusting the RCPT TO result at face value: it probes a random, guaranteed-bogus address at the same domain, through the same mail server. If the bogus address is also accepted with a `250`, the domain is confirmed catch-all, and that check fails — the receiving server just told you it accepts anything. If the bogus address is rejected with a 5xx, the domain isn't catch-all, and that check passes cleanly.

This is a separate, non-blocking check from the SMTP-level check on the real address, and it feeds the risk score rather than deciding the verdict outright. A catch-all fail on its own adds a fixed amount of risk — not enough, by itself, to cross into `invalid`, and not an `unknown` either, because the check did reach a definite answer (the domain is catch-all). The result: a domain confirmed catch-all lands the verdict at **risky**, on its own — a real, non-zero signal that the mailbox behind the address is uncertain, without going as far as calling it dead.

That risk doesn't have to stay isolated, either. Stack a catch-all result with another soft signal on the same address — a role-account pattern like `info@` or `support@`, for example — and the combined weight from those two checks alone is enough to cross into `invalid`, even though neither check individually would have gotten there, and even though no blocking check ever failed. That's the mechanism the risk score exists for: two checks that are each ambiguous on their own can add up to a confident answer together. (Disposable-address matches work differently — a disposable match crosses that same threshold entirely on its own, which is a separate scope from what RCPT TO or catch-all detection measure.)

## SMTP codes to PingValid verdict, mapped

| What the server sent | What it means | What the check does with it | Contribution to the final verdict |
|---|---|---|---|
| 250 (or any 2xx) on the real address | Mailbox accepted right now | Passes the SMTP-level check | Doesn't by itself force `valid` — every other check still counts |
| Any 5xx (550, 551, 552, 553, 554…) | Permanent rejection | Fails the SMTP-level check | Forces `invalid`, risk score 100, immediately — this check is blocking, so nothing else changes it |
| 421 with a greylist-shaped message, or 450/451/452 | Temporary deferral, commonly greylisting | Flags it and returns unknown rather than guessing | Overall verdict `unknown` — and an `unknown` **verdict** costs no credits. If other checks push the score to `risky` or `invalid`, that verdict is billed normally |
| Any other 4xx, or no usable response from any mail server for the domain | Transient failure, not clearly greylisting | Tries every mail server for the domain, then gives up | Same outcome, and the same billing rule: free only if the **final verdict** is `unknown`, not merely this check |
| 250 on a random bogus address at the same domain | Domain accepts mail for addresses that don't exist — catch-all | The separate catch-all check fails | Lands at `risky` on its own; stacked with another soft signal (e.g. a role-account match), the combined score can cross into `invalid` |
| 5xx on that same bogus address | Domain rejects unrecognized addresses individually | The catch-all check passes | No risk added |

## What to do with the code in front of you

- **5xx (550 and its relatives):** treat as terminal. Suppress or remove the address.
- **4xx generally — especially 421, 450, 451, 452:** don't treat it as a bounce. Re-check later rather than purging it.
- **250 on a domain you haven't confirmed isn't catch-all:** treat as weak evidence, not confirmation. A single accept response from a catch-all domain would say the same thing to a typo.
- **A raw code from your own server's log is missing the context a full pipeline adds around it** — the same 250, on two different domains, can carry two different amounts of risk once catch-all, disposable, and role-account checks run alongside it.

If you're parsing this out of raw SMTP logs by hand, that's exactly the plumbing a validation API is built to skip: [the API docs](/docs/api) cover the response shape — a `tier_results` object where each check comes back as `pass`, `fail`, or `unknown`, not a raw numeric code you have to interpret yourself.

For the mechanics of the handshake itself — HELO/EHLO, `MAIL FROM`, `RCPT TO` — see [what an SMTP check actually does](/blog/smtp-check-explained).

## Try it yourself

PingValid's Free tier includes 100 validations a month, no card required — enough to run real addresses through the full pipeline, including a live SMTP-level check, and see the parsed verdict instead of a raw code. A live SMTP probe is targeted under 3 seconds per check (a cached lookup is targeted under 300 ms p95 — the two aren't comparable, since a live probe means an actual conversation with a remote mail server, not an internal lookup). Start on the [free tier](/pricing).

## FAQ

### What does SMTP error code 550 mean?
`550` is a permanent SMTP rejection — the receiving mail server refused the address for good, most commonly because the mailbox doesn't exist. It should be treated as terminal, not retried.

### What does SMTP error code 421 mean?
`421` is a temporary failure. It's only counted as a greylisting signal when the server's response text contains a recognizable hint (words like "greylist," "try again," "deferred"); without that, it's read as an ordinary transient error. Either way, it isn't a rejection — it's a request to try again.

### What is the difference between SMTP 550 5.1.1 and 550 5.7.1?
Both are permanent (`550`) rejections, but the enhanced status code narrows the reason differently. `5.1.1` means "bad destination mailbox address" — the standard "no such user" signal. `5.7.1` means "delivery not authorized, message refused" — a policy-based rejection (such as relay access denied) that isn't necessarily a statement the mailbox is fake.

### Does a temporary (4xx) SMTP error mean an email address is invalid?
No. A 4xx code means the server couldn't give a definitive answer right now — commonly greylisting, sometimes a rate limit or a busy server. It should be re-checked, not treated the same as a permanent rejection.

### Why does a catch-all domain make an SMTP check unreliable?
A catch-all domain accepts mail for any address, real or not, instead of rejecting unrecognized ones. A `250` response from a catch-all domain doesn't confirm the specific mailbox exists — the same server would likely accept a made-up address too. That's why catch-all status needs its own dedicated check rather than being inferred from one RCPT TO response.

### Can I trust every SMTP response code exactly as written?
Not entirely. Some receiving servers deliberately return misleading responses — accepting nearly everything to avoid revealing which addresses are real, or rejecting unfamiliar probe traffic with a policy-based error regardless of whether the address is genuine. A single response code is a signal to weigh alongside other checks, not a standalone verdict.