emailmarketing.net

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

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 FROM on the original message. A return path that encodes each recipient (VERP, for example bounces+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

  • Status carries the enhanced status code, which does not depend on the transport. It is the main key for classification (4.x.x soft, 5.x.x hard).
  • Diagnostic-Code keeps the raw transport error. When its type is smtp, the text is the remote server's SMTP reply. The RFC notes that it is "somewhat redundant" with Status, but is provided to keep the original information. When Remote-MTA is 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 numeric Status lacks, so parse both.

Type conventions

  • The values of address-type, mta-name-type and diagnostic-type are case-insensitive atoms. The standard values are rfc822 (addresses), dns (MTA names) and smtp (diagnostics). unknown is used when the type cannot be determined, the X- 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

  1. Identify the recipient. Prefer Original-Recipient, and fall back to Final-Recipient. Strip the rfc822; type prefix. If the body cannot be parsed, fall back to the return path encoded with VERP.
  2. Filter on Action. Only failed should lead to suppression. Ignore delayed for list hygiene, but log it for delivery monitoring. Treat delivered, relayed and expanded as not being bounces.
  3. Classify on Status. Send 5.x.x to your hard-bounce logic and 4.x.x to 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 of 5.7.1 is a problem with the sender, not with the list.
  4. 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).
  5. Expect several recipient groups in one DSN. One bounce message can report several recipients, so process each group separately.
  6. Expect bounces that do not follow the format. Plenty of real-world bounces are free-form text with no message/delivery-status part at all. Parsing RFC 3464 is the fast path; keep a heuristic fallback, with VERP as the safety net.
  7. 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 →