Delivery Status Notifications — DSN Format (RFC 3464)
How bounce messages are structured: the multipart/report DSN format, per-message and per-recipient fields (Action, Status, Diagnostic-Code), and how senders should parse them.
RFC 3464 defines the standard format of the machine-readable bounce message — the Delivery Status Notification (DSN). When a message cannot be delivered (or delivery is delayed, or a requested positive notification fires), the reporting MTA sends a DSN back to the envelope sender. Every automated bounce processor is, at its core, a parser of this format plus heuristics for the non-conforming remainder.
Envelope rules
- A DSN sent over SMTP MUST use a null return path:
MAIL FROM:<>. This prevents mail loops — no DSN is ever generated for a DSN. - Corollary for senders: bounces arrive at whatever address you put in
MAIL FROMon the original message. Using a per-recipient encoded return path (VERP, e.g.bounces+user=example.com@sender.com) lets you identify the failed recipient even when the DSN body is malformed — and lets a feedback-loop processor reuse the same suppression pipeline. - Each sender-specified recipient SHOULD result in at most one "delivered" or "failed" DSN. When a message hits a mailing-list alias, the forwarding MTA SHOULD issue an "expanded" DSN for the original recipient and not propagate the DSN request to the expanded addresses.
Overall structure: multipart/report
A DSN is a MIME message with Content-Type: multipart/report; report-type=delivery-status, containing:
| Part | Content-Type | Purpose |
|---|---|---|
| 1 | text/plain (typically) |
Human-readable explanation of what happened |
| 2 | message/delivery-status |
Machine-parsable status — the part bounce processors read |
| 3 | message/rfc822 or text/rfc822-headers |
The returned original message (or its headers) — optional |
The message/delivery-status part is a set of header-style field groups: one per-message group, then one per-recipient group for each recipient reported, each group separated by a blank line.
Per-message fields
| Field | Required? | Syntax / meaning |
|---|---|---|
Reporting-MTA |
Required | mta-name-type; mta-name — the MTA reporting the result, e.g. dns; mail.example.net |
Original-Envelope-Id |
Optional | Transaction ID supplied at submission, for correlating the DSN with the original send |
DSN-Gateway |
Conditional | Present only when the DSN was translated from a foreign (non-Internet) reporting system |
Received-From-MTA |
Optional | Name of the MTA the message was received from |
Arrival-Date |
Optional | RFC 822 date-time the message arrived at the reporting MTA |
MTA names are case-sensitive; spelling must be preserved exactly.
Per-recipient fields
| Field | Required? | Syntax / meaning |
|---|---|---|
Final-Recipient |
Required | address-type; generic-address — the recipient address as the reporting MTA saw it |
Action |
Required | One of five values, below |
Status |
Required | Enhanced status code, DIGIT.1*3DIGIT.1*3DIGIT (e.g. 5.1.1) |
Original-Recipient |
Optional | The address as originally specified by the sender (before forwarding/rewriting) |
Remote-MTA |
Optional | The next-hop MTA involved in the reported delivery attempt |
Diagnostic-Code |
Optional | diagnostic-type; text — the actual transport-level error, e.g. the remote server's full SMTP reply |
Last-Attempt-Date |
Optional | RFC 822 date-time of the final delivery attempt |
Final-Log-ID |
Optional | Index into the reporting MTA's delivery log |
Will-Retry-Until |
Optional | For delayed only — when the MTA will give up |
Action values (exactly five, case-insensitive)
| Action | Meaning | Bounce-processor handling |
|---|---|---|
failed |
Could not be delivered; abandonment is final | The bounce. Classify via Status + Diagnostic-Code |
delayed |
Not yet delivered; retries continue (check Will-Retry-Until) |
Informational; do not suppress — a failed DSN may or may not follow |
delivered |
Successfully delivered to the recipient address | Positive DSN (only if requested); not a bounce |
relayed |
Forwarded into an environment that does not accept DSN responsibility | Terminal for DSN purposes, but not proof of delivery |
expanded |
Delivered to the sender-specified address, which was a multi-recipient alias/list that re-sent it | Non-terminal; only used for multi-recipient aliases |
Status vs Diagnostic-Code
Statuscarries the transport-independent enhanced status code — the primary classification key (4.x.xsoft,5.x.xhard).Diagnostic-Coderetains the raw transport error; typesmtpmeans the text is the remote server's SMTP reply. The RFC notes it is "somewhat redundant" withStatusbut is provided to retain the original information. WhenRemote-MTAis present, the diagnostic came from that MTA; otherwise from the reporting MTA. In practice the diagnostic text often carries provider-specific detail (blocklist names, policy URLs, "user unknown") that the numericStatuslacks — parse both.
Type conventions
address-type,mta-name-type,diagnostic-typevalues are case-insensitive atoms: standard values arerfc822(addresses),dns(MTA names),smtp(diagnostics);unknownwhen the type can't be determined;X-prefix for experimental types; new types are IANA-registered.- The content after the type may be case-sensitive (mailbox local parts) and must be preserved exactly.
Example (from the RFC)
Reporting-MTA: dns; cs.utk.edu
Original-Recipient: rfc822;louisl@larry.slip.umd.edu
Final-Recipient: rfc822;louisl@larry.slip.umd.edu
Action: failed
Status: 4.0.0
Diagnostic-Code: smtp; 426 connection timed out
Last-Attempt-Date: Thu, 7 Jul 1994 17:15:49 -0400
Parsing guidance for senders
- Identify the recipient: prefer
Original-Recipient, fall back toFinal-Recipient; strip therfc822;type prefix. If the body is unparsable, fall back to the VERP-encoded return path. - Gate on
Action: onlyfaileddrives suppression. Ignoredelayedfor list hygiene (log it for delivery monitoring); treatdelivered/relayed/expandedas non-bounces. - Classify on
Status:5.x.x→ hard-bounce logic,4.x.x→ soft-bounce counters. Use the detail-code table to separate bad-address codes (suppress) from policy/reputation codes (investigate — a wave of5.7.1is a sender problem, not a list problem). - Refine on
Diagnostic-Code: regex/keyword rules over the raw SMTP text catch provider-specific conditions the numeric code hides (named blocklists, "spam content", rate-limit notices). - Expect multiple recipient groups per DSN — one bounce message can report several recipients; process each group independently.
- Expect non-conformance: plenty of real-world bounces are free-form text with no
message/delivery-statuspart at all. RFC 3464 parsing is the fast path; keep a heuristic fallback and VERP as the safety net. - Never auto-reply to a DSN and never send bounces of your own with a non-null return path — the
MAIL FROM:<>rule is what keeps the ecosystem loop-free.