How to find why an email bounced in Postfix
“So-and-so says they never got my email.” That is usually how this starts. The sender did not receive a bounce message (no non-delivery report), the recipient says it is not in their spam folder, and now you have to work out what actually happened.
The Postfix mail log is the source of truth. It records exactly what your server did with the message: handed it off to the recipient’s server, kept retrying it, or rejected it outright. This guide walks the diagnostic workflow: find the delivery attempt, read what happened, and when it failed, decode the reason.
Find the delivery attempt in the mail log
Section titled “Find the delivery attempt in the mail log”Search the mail log for the recipient address or the Postfix queue ID. Queue IDs are typically 10-16 alphanumeric characters (example: E1234567890ABC).
To find by recipient:
grep "to=<user@example.com>" /var/log/mail.log | tail -20To find by queue ID:
grep "E1234567890ABC" /var/log/mail.logLook for a line with the smtp or lmtp service. You will see something like this:
Jun 17 10:22:33 mail postfix/smtp[12345]: E1234567890ABC: to=<user@acme.io>, relay=mail.acme.io[192.0.2.1]:25, delay=0.45, delays=0.01/0.02/0.15/0.27, dsn=5.1.1, status=bounced (550 5.1.1 <user@acme.io> user unknown)This single line contains everything you need.
What the log tells you happened
Section titled “What the log tells you happened”Before decoding anything, read the status= field. It puts the message in one of four situations, and it decides whose problem this is:
status=sent: your server handed the message off and the recipient’s server accepted it (a250reply). It left your infrastructure successfully. If the recipient still does not see it, the problem is downstream: their spam filtering, a mailbox rule, or a silent discard on their side. The log line is your proof of handoff (relay host, IP, timestamp, and the accepting250response).status=deferred: a transient failure. The message is still queued and Postfix is retrying. It has not arrived yet, but it has not failed permanently either.status=bounced: a permanent failure. Postfix gave up and generated a non-delivery report to the sender. If the sender never saw that report, it was filtered or routed elsewhere, but the log still has the reason.- No matching line at all: the message never reached this server. The problem is upstream, at submission or on whichever host the client actually sent through. Investigate that host instead.
The most common surprise is status=sent for a message the recipient swears never arrived. That is not a Postfix delivery failure: your server did its job and has the receipt to prove it, so the next step is the recipient’s side, not yours. The rest of this guide covers the case where the log shows a failure and you need the reason.
Decode the status and DSN fields
Section titled “Decode the status and DSN fields”The status= field tells you whether Postfix gave up permanently or will retry:
status=bounced: a PERMANENT failure. Postfix will not retry. A delivery status notification (DSN) is sent back to the original sender.status=deferred: a TRANSIENT (temporary) failure. Postfix will retry later.
The dsn= field is the Enhanced Status Code (RFC 3463). This is a structured 3-part code that classifies the failure type. The first digit tells you the category:
2.x.x: Success. (You will not see this in a bounce.)4.x.x: Persistent transient failure. Postfix will retry.5.x.x: Permanent failure. This is a bounce. Postfix gives up.
In our example, dsn=5.1.1 starts with 5, so the status is permanent. Postfix will not retry and will generate a bounce message to the sender.
Read the remote server’s reply
Section titled “Read the remote server’s reply”Everything in parentheses at the end is the remote mail server’s SMTP response. This is the most human-readable part:
(550 5.1.1 <user@acme.io> user unknown)The first number (550) is the 3-digit SMTP status code (RFC 5321). The second number (5.1.1) echoes the Enhanced Status Code. Everything after that is the server’s text explanation.
Here are the most common permanent failure codes (all start with 5):
- 5.1.1: Bad destination mailbox (user unknown, no such user). The email address does not exist on the receiving server.
- 5.2.2: Mailbox full, permanent. The mailbox quota is exceeded and cannot accept mail. Some servers treat this as transient (4.2.2) and will retry; others make it permanent.
- 5.7.1: Delivery not authorized (blocked by policy). The receiving server rejected the message due to a policy rule, such as SPF/DKIM/DMARC failure, rate limiting, or explicit blocklist. When a 5.7.1 points at authentication, paste the bounced message’s headers into the Email Header Analyzer to see which of SPF, DKIM, or DMARC failed.
- 5.4.4: Unable to route. The receiving domain has no reachable mail server (a DNS or MX lookup failed, or there is no route to the host). Often means the domain is misspelled or no longer accepts mail.
The remote server’s text varies by implementation. A Postfix server might say user unknown; another system might say no such user or invalid recipient. Both indicate 5.1.1.
Note: The codes and text come from the remote server, not from your Postfix instance. You cannot change them; they reflect the receiving server’s decision.
Transient failures vs. permanent bounces
Section titled “Transient failures vs. permanent bounces”If you see status=deferred with a dsn=4.x.x code, Postfix is retrying. Examples:
4.4.2(connection dropped): The server disconnected mid-transaction. Postfix will retry.4.7.1(temporary policy restriction): The server temporarily rejected the mail. Postfix will try again.
Postfix retries deferred mail according to its queue configuration (default: several attempts over 5 days). You can force a retry with postqueue -i or allow it to timeout.
Only status=bounced with a 5.x.x code generates a DSN back to the sender. The DSN itself is an email message (RFC 3464) that includes the recipient address, the DSN codes, the remote server’s text, and a copy of the original message headers. Your Postfix bounce daemon generates and sends this DSN.
Real example workflow
Section titled “Real example workflow”You receive a complaint: “My email to compliance@nope.org did not arrive.”
-
Search the log:
Terminal window grep "to=<compliance@nope.org>" /var/log/mail.log | grep bounced -
You see:
Jun 17 11:15:22 mail postfix/smtp[23456]: F9876543210XYZ: to=<compliance@nope.org>, relay=mx.nope.org[203.0.113.42]:25, delay=1.23, delays=0.05/0.10/0.85/0.23, dsn=5.1.1, status=bounced (550 5.1.1 user unknown) -
You can now tell the sender: “Our system tried to deliver to compliance@nope.org at nope.org’s mail server. The server rejected it with a ‘550 5.1.1 user unknown’ error. That address does not exist on their server. Please verify the address and resend.”
If you see dsn=4.4.2 with status=deferred instead, you would say: “The delivery is queued and being retried. The connection was temporarily dropped. Check back later or contact nope.org if the issue persists.”
Using Postfix Insights to speed up diagnostics
Section titled “Using Postfix Insights to speed up diagnostics”Reading the log by hand is straightforward, but Postfix Insights automates the correlation. The tool:
- Extracts the queue ID from any message you search for.
- Groups all lines for that message (from
pickup,cleanup,qmgr,smtp, etc.) in one view. - Highlights the recipient status and DSN code for quick scanning.
- Shows the raw log line so you can read the remote server’s reply without grepping.
- Lets you search by recipient, sender, queue ID, or domain.
This means no more manual grepping or context-switching between terminals. For a sysadmin managing thousands of messages a day, that time adds up.
Get started with Postfix Insights. The source code and issue tracker are at https://github.com/Xodus-CO/postfix-insights.
References
Section titled “References”- RFC 3463: SMTP Enhanced Status Codes. https://www.rfc-editor.org/rfc/rfc3463
- RFC 3464: An Extensible Message Format for Delivery Status Notifications. https://www.rfc-editor.org/rfc/rfc3464
- RFC 5321: Simple Mail Transfer Protocol. https://www.rfc-editor.org/rfc/rfc5321
- Postfix documentation. https://www.postfix.org/documentation.html