Guide

SMTP Reply Codes Explained

An SMTP server answers every command with a three-digit reply code. The first digit tells you everything that matters: 2xx means it worked, 4xx means try again later, and 5xx means stop and fix something. This reference decodes the codes you actually see in bounces, shows how to read the enhanced status code inside a delivery status notification, and how to confirm what landed using the Nylas CLI from the terminal.

Written by Pouya Sanooei Software Engineer

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

Command references used in this guide: nylas email list, nylas email search, and nylas email read.

What are SMTP reply codes?

An SMTP reply code is a three-digit number a mail server returns after every command, defined in RFC 5321. The first digit is the verdict: 2 means accepted, 4 means a temporary failure, and 5 means a permanent failure. Read that digit before anything else.

The standard has used this structure since RFC 821 in 1982, so the same five families work across every provider. The second and third digits add detail, but the leading digit alone tells you whether to celebrate, retry, or stop. A 250 after the final DATA command is the only code that means the receiving server took responsibility for the message. Everything from a 354 prompt to a 421 shutdown is a step along the way, and a single failed step is what produces the bounce that lands back in your inbox minutes later.

# The five SMTP reply families (first digit = verdict)
# 2yz  Positive completion   accepted (e.g. 250 OK)
# 3yz  Positive intermediate server wants more (e.g. 354 start mail input)
# 4yz  Transient failure     retry later (e.g. 421, 451, 452)
# 5yz  Permanent failure     fix something (e.g. 550, 552, 554)

What do 2xx and 3xx SMTP codes mean?

A 2xx SMTP reply code is a success: the server accepted the command and is ready for the next one. A 3xx code is intermediate — the server understood you and is waiting for more input before it can finish. Together they form the normal path of a healthy delivery, and you rarely see them unless you watch a live session.

The two codes that matter are 250 and 354. After your client issues MAIL FROM, RCPT TO, and then DATA, the server answers 354 to ask for the message body, and a final 250 once it accepts custody. That final 250 is the moment delivery is the receiving server's problem, not yours. RFC 5321 reserves 220 for the greeting and 221 for a clean disconnect, so a full conversation touches four or five success codes before it ends. None of these reach the recipient as a bounce, which is why a sender almost never reads them directly.

# A successful SMTP exchange (server replies on the left)
220 mx.example.com ESMTP ready
250 mx.example.com Hello
250 2.1.0 Sender OK            # MAIL FROM accepted
250 2.1.5 Recipient OK         # RCPT TO accepted
354 Start mail input           # send the message body now
250 2.0.0 Message accepted     # custody transferred
221 2.0.0 Bye

Why does a 4xx SMTP code mean retry but 5xx mean fix it?

A 4xx SMTP reply code is a transient failure: the server could not accept the message right now but might in a few minutes. A 5xx code is permanent — retrying changes nothing because the problem is the request itself, like a recipient address that does not exist. The leading digit decides whether your retry queue should hold the message or drop it.

This split is why a well-behaved sender treats the two families differently. A 421 (service unavailable) or 451 (local processing error) should sit in a retry queue with exponential backoff; most senders retry for up to 5 days before giving up, the default Postfix bounce window (its maximal_queue_lifetime defaults to 5 days). A 550 (mailbox unavailable) or 554 (transaction failed) should never be retried — resending the same message to the same dead address just damages your sender reputation. Greylisting deliberately returns a 4xx on first contact and accepts the retry, so a sender that drops on the first 4xx loses legitimate mail. Read the digit, then decide retry versus fix.

# Common transient (4xx) codes — keep retrying
# 421  Service not available, closing channel (or greylisting / rate limit)
# 450  Mailbox unavailable, busy — try later
# 451  Local error in processing
# 452  Insufficient system storage (often a quota or rate cap)

# Common permanent (5xx) codes — stop and fix
# 550  Mailbox unavailable / address does not exist
# 552  Message exceeds size limit
# 553  Mailbox name not allowed (bad address syntax)
# 554  Transaction failed (often spam rejection or policy block)

How do I read a bounce DSN and its enhanced status code?

A bounce arrives as a delivery status notification (DSN), a structured report defined in RFC 3464. Inside it sits an enhanced status code — three numbers like 5.1.1 — defined in RFC 3463. It tells you the exact reason far more precisely than the bare 550 does.

The enhanced code has three parts: class, subject, and detail. The class digit mirrors SMTP — 2 success, 4 transient, 5 permanent — so 5.1.1 is a permanent address failure (bad mailbox) and 4.2.2 is a transient over-quota condition. The middle digit names the subject: 1 for addressing, 2 for the mailbox, 7 for security or policy. IANA maintains the full registry of these codes in its enhanced status code assignments. A bounce with 5.7.1 is a policy rejection (often anti-spam) you fix by authentication, while 5.1.1 means the address is wrong and no fix on your side will deliver it. The DSN's Action: failed field confirms it was a hard bounce, not a delay.

# The machine-readable part of a bounce DSN (message/delivery-status)
Reporting-MTA: dns; mx.example.com
Final-Recipient: rfc822; nobody@example.com
Action: failed
Status: 5.1.1                       # class.subject.detail
Diagnostic-Code: smtp; 550 5.1.1 The email account that you tried to
 reach does not exist.

# Read it as: permanent (5) . addressing (1) . bad mailbox (1)

How do I debug delivery with the Nylas CLI?

You debug delivery by checking two things: did the message leave, and did a bounce come back. The Nylas CLI confirms both from the terminal without an SMTP client or raw log access. The CLI sends over OAuth across Gmail and Outlook, so most 5xx auth-and-relay failures that plague hand-configured SMTP never happen in the first place.

The nylas email send command transmits the message; a non-zero exit code surfaces a hard failure immediately instead of a silent drop. To confirm what landed, list the Sent folder with nylas email list --folder SENT, then catch a bounce with nylas email search — mailer-daemon notifications almost always carry the subject prefix Undelivered or arrive from a mailer-daemon address. Open the matched message with nylas email read to see the DSN body and its enhanced status code. The search auto-paginates past 200 results and accepts --after to scope a window to the minutes around a send, so a bounce from a batch of 500 is one command away.

# 1. Send the message (OAuth handles auth; no SMTP config)
nylas email send --to user@example.com --subject "Build report" \
  --body "Nightly build passed." --yes

# 2. Confirm it left — list the Sent folder
nylas email list --folder SENT --limit 10 --json

# 3. Catch a bounce — mailer-daemon DSNs land in the inbox
nylas email search "Undelivered" --after 2026-06-09 --json

# 4. Read the DSN body to see the enhanced status code (e.g. 5.1.1)
nylas email read <message-id> --json

Next steps