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.
Reference5 min read
Who it is for ESP operators, Senders
Applies to senders on any platform
ContentsOn this page — 6 sections
To process bounces automatically, you need to read the machine-readable bounce message that mail servers send back. When a message cannot be delivered (or its delivery is delayed, or a positive notification you requested is triggered), the reporting MTA sends a Delivery Status Notification (DSN) back to the envelope sender.
RFC 3464 defines the standard format of that message. Every automated bounce processor is, at its core, a parser of this format plus heuristics for the bounces that do not follow it.
Envelope rules
- A DSN sent over SMTP MUST use a null return path:
MAIL FROM:<>. This prevents mail loops, because no DSN is ever generated for a DSN. - It follows that bounces arrive at whatever address you put in
MAIL FROMon the original message. A return path that encodes each recipient (VERP, for examplebounces+user=example.com@sender.com) lets you identify the failed recipient even when the body of the DSN is malformed. It also lets a feedback loop processor reuse the same suppression pipeline. - Each recipient the sender specified SHOULD produce at most one "delivered" or "failed" DSN. When a message reaches a mailing list alias, the forwarding MTA SHOULD issue an "expanded" DSN for the original recipient, and should not pass the DSN request on to the expanded addresses.
Overall structure: multipart/report
A DSN is a MIME message with Content-Type: multipart/report; report-type=delivery-status. It contains:
| Part | Content-Type | Purpose |
|---|---|---|
| 1 | text/plain (typically) |
An explanation of what happened, for people to read |
| 2 | message/delivery-status |
The status in a form machines can parse. This is 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 groups of fields written like headers: one per-message group, then one per-recipient group for each recipient reported. A blank line separates the groups.
Per-message fields
| Field | Required? | Syntax and meaning |
|---|---|---|
Reporting-MTA |
Required | mta-name-type; mta-name: the MTA reporting the result, for example dns; mail.example.net |
Original-Envelope-Id |
Optional | Transaction ID supplied at submission, for matching 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, and their spelling must be preserved exactly.
Per-recipient fields
| Field | Required? | Syntax and 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 (for example 5.1.1) |
Original-Recipient |
Optional | The address as the sender originally specified it (before forwarding or 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, for example 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 | How a bounce processor handles it |
|---|---|---|
failed |
Could not be delivered, and delivery has been abandoned for good | This is the bounce. Classify it using Status and Diagnostic-Code |
delayed |
Not yet delivered; retries continue (check Will-Retry-Until) |
Informational. Do not suppress, because a failed DSN may or may not follow |
delivered |
Successfully delivered to the recipient address | A positive DSN (sent only if requested); not a bounce |
relayed |
Forwarded into an environment that does not take responsibility for DSNs | Final as far as DSNs are concerned, but not proof of delivery |
expanded |
Delivered to the address the sender specified, which was an alias or list with several recipients that sent the message on | Not final; used only for aliases with several recipients |
Status vs Diagnostic-Code
Statuscarries the enhanced status code, which does not depend on the transport. It is the main key for classification (4.x.xsoft,5.x.xhard).Diagnostic-Codekeeps the raw transport error. When its type issmtp, the text is the remote server's SMTP reply. The RFC notes that it is "somewhat redundant" withStatus, but is provided to keep the original information. WhenRemote-MTAis present, the diagnostic came from that MTA; otherwise it came from the reporting MTA. In practice, the diagnostic text often carries details specific to the provider (blocklist names, policy URLs, "user unknown") that the numericStatuslacks, so parse both.
Type conventions
- The values of
address-type,mta-name-typeanddiagnostic-typeare case-insensitive atoms. The standard values arerfc822(addresses),dns(MTA names) andsmtp(diagnostics).unknownis used when the type cannot be determined, theX-prefix marks experimental types, and new types are registered with IANA. - The content after the type may be case-sensitive (the local parts of mailboxes, for example) 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, and fall back toFinal-Recipient. Strip therfc822;type prefix. If the body cannot be parsed, fall back to the return path encoded with VERP. - Filter on
Action. Onlyfailedshould lead to suppression. Ignoredelayedfor list hygiene, but log it for delivery monitoring. Treatdelivered,relayedandexpandedas not being bounces. - Classify on
Status. Send5.x.xto your hard-bounce logic and4.x.xto your soft-bounce counters. Use the table of detail codes to separate codes for bad addresses (suppress them) from codes for policy or reputation (investigate them). A wave of5.7.1is a problem with the sender, not with the list. - Refine on
Diagnostic-Code. Rules based on regular expressions or keywords, applied to the raw SMTP text, catch conditions specific to a provider that the numeric code hides (named blocklists, "spam content", notices of rate limits). - Expect several recipient groups in one DSN. One bounce message can report several recipients, so process each group separately.
- Expect bounces that do not follow the format. Plenty of real-world bounces are free-form text with no
message/delivery-statuspart at all. Parsing RFC 3464 is the fast path; keep a heuristic fallback, with VERP as the safety net. - Never auto-reply to a DSN, and never send bounces of your own with a return path that is not null. The
MAIL FROM:<>rule is what keeps the email ecosystem free of loops.
Check your own record
The free check reads what your domain publishes in DNS.
In this topic
All 3 in Bounce Handling →