550 5.7.1 Message rejected due to content restrictions
Hard bounce? The 5 says permanent, so a bounce handler built on "5xx means dead" files it with the dead addresses and suppresses the recipient forever. But the code is not about the address. It says a content filter at their end objected to your message, maybe over a link shortener, and the same template will likely hit that filter for other readers behind it. The usual rule, "5xx is hard, 4xx is soft", is a hint, not a verdict. This post covers the rest: where a bounce comes from in the SMTP conversation, how to read one, and which ones to suppress. Disclosure: I help build the two simulators below; they are free, run in the browser, and need no signup.
Where a bounce comes from
Most bounces start as a negative SMTP reply. If the receiving server says no during the session, your relay turns that into a bounce for you. If it accepts the message and delivery fails later, RFC 5321 says it must send a notification of its own, from the null sender <>, to your envelope MAIL FROM address. The rest come from your own relay with no reply at all: a domain that does not exist, a connection that never opens, a queue that expires. The SMTP Flow Simulator plays an illustrative timeline of the conversation in three scenarios, mixing SMTP commands with the DNS and delivery steps around them. The actors (App, SMTP relay, DNS auth, Recipient MX, Mailbox) light up in turn, and a trace pane prints each step as a C: and S: pair:
-
Successful transactional email: nine steps, from a password reset submitted on port 587 through EHLO, STARTTLS, a second EHLO, AUTH,
MAIL FROMandRCPT TO, DATA, the SPF, DKIM and DMARC checks, and the relay to the recipient's MX, to inbox placement. (The simulator shows the auth checks as one step before the hand-off. In real delivery, your side signs DKIM and the receiving server runs the checks as the message arrives.) -
Temporary greylisting retry: the recipient's MX answers
RCPT TOwith451 4.7.1 Try again later, the relay backs off ("retry in 10 minutes, then 30, then 2 hours"), and the second attempt is accepted. -
Authentication failure:
535 5.7.8 Authentication credentials invalidat AUTH, then530 5.7.0 Authentication requiredwhen the app triesMAIL FROManyway. Both codes come from RFC 4954. That is a rejection, not a bounce: the message never left.
Step through them with Play or Step and read the protocol notes, especially "A 250 after DATA means the next server accepted responsibility" and "Accepted does not always mean inboxed".
You can watch the start of a real conversation yourself. Receiving servers listen on port 25, and dig +short MX gmail.com gives gmail-smtp-in.l.google.com as the most preferred host (preference 5). Many home ISPs and cloud providers block outbound port 25, so this can time out. From my connection it worked, and this is the real output, with my IP swapped for a documentation address:
$ python3 -c '
import smtplib
s = smtplib.SMTP("gmail-smtp-in.l.google.com", 25, timeout=10)
print(s.ehlo("test.invalid")[1].decode())
s.quit()'
mx.google.com at your service, [203.0.113.10]
SIZE 157286400
8BITMIME
STARTTLS
ENHANCEDSTATUSCODES
PIPELINING
CHUNKING
SMTPUTF8
The line to notice is ENHANCEDSTATUSCODES. The server announces that its replies carry a second, more precise code, and that code is where bounce triage starts.
How to read a bounce
Take 550 5.1.1 User unknown. It has three parts:
- The reply code, 550, defined in RFC 5321. The first digit says 4 (try again later) or 5 (do not repeat this as-is).
- The enhanced status code, 5.1.1, defined in RFC 3463 as class.subject.detail. The middle number says what the problem is about: 1 addressing, 2 mailbox, 3 mail system, 4 network and routing, 5 protocol, 6 content, 7 security or policy.
- The text, free-form and often the most useful part.
The class says how long. The subject says what kind of problem it is. A few lines of Python give you a first look:
import re
SUBJECT = {"0": "other", "1": "addressing", "2": "mailbox", "3": "mail system",
"4": "routing", "5": "protocol", "6": "content", "7": "policy"}
def first_look(reply):
m = re.search(r"\b([45])\.(\d{1,3})\.(\d{1,3})\b", reply)
if not m:
return "no enhanced code, read the text"
cls, subject, _ = m.groups()
when = "permanent" if cls == "5" else "temporary"
return f"{when}, about {SUBJECT.get(subject, 'other')}"
Run against three replies:
550 5.1.1 User unknown -> permanent, about addressing
452 4.2.2 The email account th -> temporary, about mailbox
550 5.7.1 Message rejected due -> permanent, about policy
For 5.1.1, "permanent, about addressing" means a dead address: suppress it. Other addressing codes need the detail digit, since 5.1.7 and 5.1.8 are about your sender address, not theirs. "Permanent, about policy" usually points at your side: content, authentication or sending reputation. Suppressing the recipient fixes none of those. The IANA registry lists every assigned code.
Four kinds, five actions
The Bounce Triage Simulator turns this into practice. It shows twelve bounces one at a time, each with the recipient, the provider (Gmail, Outlook, Postfix, Proofpoint and others) and the raw response. For each one you answer two questions. "What kind of failure is this?": Hard bounce, Soft bounce, Blocked, or Complaint. "What do you do with it?": Suppress permanently, Retry with backoff, Fix the message then retry, Slow down then retry, or Stop sending and fix the cause. The last one is the answer for a relay denial and a blocklist listing. Press "Triage it" to see what the code means, plus a box titled "What getting it wrong costs you".
Get the kind right but the action wrong and it tells you "Right diagnosis, wrong treatment", the exact mistake it is built to catch. The rounds that teach the most:
-
550-5.7.26 Unauthenticated email from yourdomain.com is not accepted due to domain's DMARC policy.A 5xx that is an authentication bug on your side. RFC 7372 named 5.7.26 "Multiple authentication checks failed", and Gmail uses it for DMARC failures. Receivers that enforce your DMARC policy keep rejecting mail that fails this way until you fix SPF or DKIM alignment. -
421 4.7.0 Too many messages from your IP. Please try again later.A 421 also closes the connection. The right action is to slow down. Retrying at the same rate makes it worse. -
450 4.7.1 ... Greylisted, try again in 300 seconds. Treat it as a failure and you never reach a whole class of small self-hosted domains, because the retry is the test. - A Yahoo feedback loop report with
Feedback-Type: abuse. Not a bounce at all: the mail was delivered and the reader pressed "report spam".
Complaints get the hardest line. Suppress them at once and for good. Google's sender guidelines require every sender to keep the spam rate in Postmaster Tools below 0.3%, and recommend staying under 0.1%.
What to suppress
Seven of the twelve rounds are 5xx, and four of those seven should not be suppressed. The policy:
- Suppress permanently: hard bounces (5.1.1 user unknown, 5.1.2 domain not found, 5.2.1 disabled mailbox) and every complaint.
- Retry with backoff: soft bounces (4.2.2 over quota, greylisting, 4.3.0 server errors), within your relay's queue lifetime. RFC 5321 says the give-up time generally needs to be at least 4 to 5 days. It also says the retry interval SHOULD generally be at least 30 minutes, with smarter schedules when the sender knows why delivery failed, so the SMTP Flow Simulator's first greylisting retry after 10 minutes is within the rules. One expired message does not prove the address is dead.
- Keep the recipient, investigate: 5.7.x content and authentication blocks, blocklist listings, rate limits and relay errors. Most are fixed on your side (template, DNS, send rate, delisting), some need the recipient's mail admin, and none is fixed by resending the same message unchanged.
The first digit tells you how long. The middle digit tells you what kind of problem it is. Play the three SMTP scenarios to see where replies come from, then triage the twelve bounces and count how many 5xx codes you would have suppressed by mistake. To type the conversation by hand, one command at a time, read Send an Email by Hand: The Raw SMTP Conversation.
Which bounce code has fooled your bounce handling? Tell us in the comments.

Top comments (0)