DSN Parsing Guide: Reading RFC 3464 Bounce Reports and Enhanced Status Codes in Production

DSN parsing complete guide covering RFC 3464 multipart/report structure and RFC 3463 enhanced status codes X.Y.Z. Explains the three DSN parts, all delivery-status fields (Reporting-MTA, Final-Recipient, Action, Status, Diagnostic-Code), the class/subject/detail breakdown of status codes, real production example from Postfix to Gmail, parsing code in Python and Node, correlation with Message-ID, and the 10 mistakes that corrupt bounce handling in production. dsn-parsing-guide
Alaa
By Alaa
SMTPedia documents email infrastructure end to end: SMTP standards from the RFC archive, delivera...
16 min read Updated Jul 12, 2026 142 views

Bounce handling series. This is the tactical guide to parsing Delivery Status Notifications. For related guides on bounce categorization, feedback loops, and suppression lists, see the bounce handling hub →

Quick DSN reference

A DSN (Delivery Status Notification) is the structured bounce message a mail server sends back when it cannot deliver your email. RFC 3464 defines the container format (a specific MIME multipart type). RFC 3463 defines the machine-readable status codes inside it. Every serious bounce-handling system parses these; every ESP webhook exposes them one way or another.

Container specRFC 3464 (Delivery Status Notifications)
Status code specRFC 3463 (Enhanced Mail System Status Codes)
MIME typemultipart/report; report-type=delivery-status
Standard partsHuman-readable explanation, machine-readable status, original headers
SenderThe receiving MTA that could not deliver (or the last MTA before the failure)
Common misunderstandingDSN status codes (X.Y.Z) are different from SMTP reply codes (5xx). Both usually appear in the same bounce.

Every hard bounce, soft bounce, and delivery deferral your mail server sees produces a DSN somewhere. The receiving MTA generates it, the sender’s MTA processes it, and eventually your application code needs to read it. Getting DSN parsing right is what separates a bounce handling system that works from one that silently corrupts your suppression list with false positives. This guide covers the exact structure, how to read every field, worked examples of real bounces, and the parsing code that reads them correctly in production.

What a DSN actually is

A DSN is a specific kind of email: a bounce message with a strictly defined structure that machines can parse reliably. It arrives at the envelope sender address (the MAIL FROM of the original message, recorded in the message’s Return-Path header) when the receiving side cannot deliver or has stopped trying.

Three properties make a DSN different from a plain rejection email:

  • Standardized container. RFC 3464 requires a multipart/report; report-type=delivery-status MIME structure with three specific parts. A message without this structure is a plain bounce, not a DSN.
  • Machine-readable status. The middle part uses the message/delivery-status content type, containing per-message and per-recipient fields in a specific format. Parsers do not need to guess.
  • Original context preserved. The third part carries the original message headers (or the whole original message), so the sender can correlate the bounce back to the send event.

Not every bounce you receive is a DSN. Older MTAs, some legacy anti-spam systems, and many hand-written rejection templates produce free-text bounces that need heuristic parsing. Modern ESPs and MTAs almost universally produce proper DSNs.

The multipart/report structure (RFC 3464)

The top-level Content-Type of a DSN looks like:

Content-Type: multipart/report;
   report-type=delivery-status;
   boundary="=_boundary_abc123"

The report-type=delivery-status parameter is mandatory. It tells parsers that the report inside is a delivery status notification (not a message disposition notification or another report type).

Inside the multipart body, three parts appear in order:

Part 1: Human-readable text (text/plain)

An explanation intended for a human reading the bounce. Usually says something like “Your message could not be delivered to the following recipients” and lists the addresses. Not standardized; each MTA writes its own text. Useful for humans, but do not parse this part; the machine-readable version is next.

Part 2: Machine-readable status (message/delivery-status)

The core of the DSN. Contains structured fields describing what happened, per message and per recipient. This is what your parsing code reads. Format is described in detail in the next section.

Part 3: Original headers or full message (message/rfc822 or text/rfc822-headers)

Either the complete original message (message/rfc822) or just its headers (text/rfc822-headers). The latter is more common and preserves privacy while still letting you correlate the bounce to a specific send event via the original Message-ID header.

The delivery-status part, field by field

The message/delivery-status part contains one or more field groups separated by blank lines. The first group describes the message-level context. Each subsequent group describes one recipient outcome. A DSN for a message sent to three recipients has one message group plus three recipient groups.

Per-message fields (first group)

FieldMeaning
Reporting-MTAThe MTA that generated this DSN. Format: dns; hostname. Required.
Received-From-MTAThe MTA that sent the message to the reporting MTA. Optional.
Arrival-DateWhen the reporting MTA received the message. Optional but recommended.
Original-Envelope-IdDSN envelope ID if the original send used one. Rarely populated.

Per-recipient fields (subsequent groups)

FieldMeaning
Original-RecipientThe recipient address as originally supplied in the RCPT TO. Format: rfc822; user@example.com. Optional.
Final-RecipientThe recipient address at the point of failure (may differ if forwarded or rewritten). Format: rfc822; user@example.com. Required.
ActionWhat happened. One of: failed, delayed, delivered, relayed, expanded. Required.
StatusThe RFC 3463 enhanced status code (X.Y.Z). Required.
Diagnostic-CodeThe raw SMTP response from the receiving server. Format: smtp; 550 5.1.1 User unknown. Optional but usually present.
Last-Attempt-DateTimestamp of the last delivery attempt. Optional.
Remote-MTAThe remote MTA name involved in the failure. Optional.
Will-Retry-UntilFor action=delayed: how long the reporting MTA will keep retrying. Optional.

Enhanced status codes X.Y.Z (RFC 3463)

The Status field is the most important piece of the DSN for automation. Every code has three parts separated by dots: class, subject, detail.

Class (X): the outcome

2.Y.ZSuccess. Delivered.
4.Y.ZPersistent transient failure. May succeed on retry.
5.Y.ZPermanent failure. Do not retry.

Subject (Y): the category of the problem

X.0.ZOther or undefined status
X.1.ZAddressing status (bad address, mailbox not found)
X.2.ZMailbox status (mailbox full, disabled, out of service)
X.3.ZMail system status (system full, out of resources)
X.4.ZNetwork and routing status
X.5.ZMail delivery protocol status
X.6.ZMessage content or media status
X.7.ZSecurity or policy status (blocked, spam, authentication failure)

Detail (Z): the specific reason

The most operationally useful codes to memorize:

CodeMeaningWhat to do
5.1.1Bad destination mailbox address (user unknown)Auto-suppress. Address does not exist.
5.1.2Bad destination system address (domain does not exist)Auto-suppress. Domain is invalid or misspelled.
5.2.1Mailbox disabled, not accepting messagesSuppress after 2-3 occurrences.
5.2.2Mailbox fullRetry over days, suppress if persistent.
5.4.4Unable to routeInvestigate. Usually a DNS or MX issue.
5.7.1Delivery not authorized, message refusedAuth/policy failure. Check SPF, DKIM, DMARC, IP reputation.
5.7.26Multiple authentication failuresSame as 5.7.1, but explicitly cites auth. See our Authentication-Results guide.
4.4.1No answer from hostRetry. Usually temporary network issue.
4.2.2Mailbox full (temporary)Retry. If persistent over 24 hours, suppress.
4.7.1Delivery not authorized (temporary)Retry. Often greylisting.

The SMTP error codes reference covers the traditional three-digit SMTP replies (5xx, 4xx, 2xx) that appear in the Diagnostic-Code field alongside the enhanced status.

A worked example: reading a real DSN

Here is a real DSN generated by Postfix when delivering to a nonexistent Gmail address, edited for brevity:

Return-Path: <>
From: MAILER-DAEMON@mta.sender.example.com (Mail Delivery System)
To: bounce+abc123@sender.example.com
Subject: Undelivered Mail Returned to Sender
Message-Id: <20260702143512.4A2E3.mta-05@sender.example.com>
Content-Type: multipart/report; report-type=delivery-status;
    boundary="=_boundary_x1y2z3"

--=_boundary_x1y2z3
Content-Type: text/plain; charset=us-ascii

This is the mail system at host mta-05.sender.example.com.

I'm sorry to inform you that your message could not be delivered
to one or more recipients.

--=_boundary_x1y2z3
Content-Type: message/delivery-status

Reporting-MTA: dns; mta-05.sender.example.com
X-Postfix-Queue-ID: 4A2E3
X-Postfix-Sender: rfc822; news@sender.example.com
Arrival-Date: Wed, 02 Jul 2026 14:35:10 +0000

Final-Recipient: rfc822; nonexistent@gmail.com
Original-Recipient: rfc822;nonexistent@gmail.com
Action: failed
Status: 5.1.1
Remote-MTA: dns; gmail-smtp-in.l.google.com
Diagnostic-Code: smtp; 550-5.1.1 The email account that you tried to reach
    does not exist. Please try 550-5.1.1 double-checking the recipient's email
    address for typos or 550-5.1.1 unnecessary spaces.
Last-Attempt-Date: Wed, 02 Jul 2026 14:35:12 +0000

--=_boundary_x1y2z3
Content-Type: text/rfc822-headers

Return-Path: <news@sender.example.com>
Message-ID: <20260702143508.7fa3b2c1@mta-05.sender.example.com>
Date: Wed, 02 Jul 2026 14:35:08 +0000
From: Marketing <news@sender.example.com>
To: nonexistent@gmail.com
Subject: Your July update

--=_boundary_x1y2z3--

Reading it end to end:

  • Reporting-MTA is Postfix on the sender’s own outbound server. It generated this DSN after Gmail refused the message.
  • Final-Recipient is nonexistent@gmail.com.
  • Action: failed and Status: 5.1.1: permanent failure, bad destination address.
  • Diagnostic-Code quotes Gmail’s raw SMTP response (550-5.1.1 The email account that you tried to reach does not exist).
  • Message-ID in part 3 ties this bounce back to the original send. Correlate this with your outbound log to find which send event failed.

Programmatic action: add nonexistent@gmail.com to the suppression list. Do not retry. Log the bounce with a reference to the original Message-ID.

Parsing DSN programmatically

Python (standard library)

import email
from email import policy

with open('bounce.eml', 'rb') as f:
    msg = email.message_from_binary_file(f, policy=policy.default)

if msg.get_content_type() != 'multipart/report':
    print('Not a DSN')
else:
    # Iterate the parts
    for part in msg.iter_parts():
        if part.get_content_type() == 'message/delivery-status':
            # The delivery-status part contains sub-parts:
            # first is per-message, rest are per-recipient
            groups = list(part.iter_parts())
            per_message = groups[0]
            for recip in groups[1:]:
                final = recip.get('Final-Recipient', '')
                status = recip.get('Status', '')
                diag = recip.get('Diagnostic-Code', '')
                print(f'{final} -> {status}: {diag}')

Node.js (mailparser)

const { simpleParser } = require('mailparser');
const fs = require('fs');

const raw = fs.readFileSync('bounce.eml');
simpleParser(raw, (err, parsed) => {
    if (!parsed.attachments) return;
    for (const att of parsed.attachments) {
        if (att.contentType === 'message/delivery-status') {
            const body = att.content.toString('utf-8');
            // Parse fields with simple regex or a dedicated DSN parser
            const status = body.match(/Status:\s*(\S+)/);
            const recip = body.match(/Final-Recipient:\s*rfc822;\s*(\S+)/);
            console.log(recip[1], status[1]);
        }
    }
});

PHP

$msg = imap_rfc822_parse_headers(file_get_contents('bounce.eml'));
// PHP's imap functions handle DSN structure natively.
// For richer parsing, use zbateson/mail-mime-parser or php-mime-mail-parser.

Rule of thumb: use a library that understands MIME multipart nesting. The message/delivery-status part is itself multipart internally (message-level group + recipient groups). Naive line-based regex breaks on nested boundaries.

What to do with a parsed DSN

Every DSN triggers one of three actions:

Suppress immediately (permanent failures)

Status codes 5.1.1, 5.1.2, and most other 5.x.x codes indicate permanent failure. Add the Final-Recipient to your suppression list and never send to it again. Suppression should happen on the FIRST hard bounce, not after multiple attempts. See our hard bounce vs soft bounce guide for the underlying category logic.

Retry with schedule (transient failures)

Status codes starting with 4 are transient. The reporting MTA has already retried (usually 4-5 times over a few days) before giving up. Your automation should not retry immediately. If you receive a Will-Retry-Until field, respect the timeframe. If a soft bounce persists past 72 hours or occurs 5+ times on the same recipient, treat it as a de facto hard bounce and suppress.

Investigate (auth and policy failures)

Status codes 5.7.x are auth or policy failures. These are NOT recipient-specific problems; they indicate something wrong with your SPF, DKIM, DMARC, IP reputation, or content. Do not suppress the recipient. Do investigate the sending setup. Check the Authentication-Results header on any successful message to the same domain to confirm auth is working.

Correlating DSNs to the original send

Every DSN contains, in its third part, either the full original message or its headers. The Message-ID header is the single field to extract for correlation. Your outbound logs record the Message-ID at send time. When a DSN arrives, match the Message-ID from the DSN to the log to identify the campaign, segment, subject line, or template that failed.

Without this correlation, DSNs become anonymous statistics. With it, you can identify content-specific failure patterns (this template gets more 5.7.1 rejections than that one), segment-specific issues (imports from source X have more 5.1.1s), and time-specific problems (bounces spiked after a DNS change).

10 common DSN parsing mistakes

  1. Treating all bounces as DSNs. Free-text bounces from older systems have no structure. Detect the multipart/report content type before parsing as DSN; fall back to heuristics for others.
  2. Reading only the SMTP code, ignoring the enhanced status. The Diagnostic-Code field carries the raw 3-digit SMTP reply, but the Status field carries the more granular X.Y.Z code. Both are useful; the enhanced code is more precise.
  3. Treating 4.x.x like 5.x.x for suppression. A 4-series code is transient. Suppressing on the first occurrence removes recoverable addresses from your list.
  4. Ignoring 5.7.x policy failures. These indicate a sending-side problem (auth, reputation, content), not a bad recipient. Suppressing them corrupts your list without fixing anything.
  5. Not correlating with the original Message-ID. The third part of the DSN contains the original headers. Extract the Message-ID for log correlation; without it, you cannot analyze failure patterns by campaign or template.
  6. Regex-parsing multipart bodies. The message/delivery-status part contains sub-parts separated by blank lines. Boundary strings from the outer multipart appear nowhere useful. Use a real MIME parser.
  7. Not handling non-DSN bounces. Some MTAs (older, misconfigured) reject inline (SMTP-level 5xx during the connection) instead of generating a DSN. Your bounce handling must cover both paths.
  8. Case-sensitive field matching. RFC 3464 field names are case-insensitive. Status:, status:, and STATUS: are all valid. Your parser must handle any case.
  9. Ignoring the Action field. Action explicitly says what happened (failed, delayed, delivered, relayed, expanded). Do not derive it from the status code when it is explicit.
  10. Not logging the raw DSN. Keep the original bounce message for at least 30 days. When your parser misclassifies a bounce, the raw message is the only way to debug the parser.

DSN parsing FAQ

Is every bounce a DSN?

No. A DSN is specifically a bounce message with a multipart/report; report-type=delivery-status MIME structure per RFC 3464. Older mail systems, some legacy anti-spam appliances, and hand-written rejection templates produce free-text bounces without this structure. Your bounce handling should detect the DSN case first and parse it correctly, then fall back to heuristics (regex on the message body, sender pattern matching) for non-DSN bounces.

What is the difference between an SMTP reply code and a DSN status code?

SMTP reply codes are three-digit numbers (like 550, 250, 421) defined by RFC 5321 and used in real-time SMTP conversations. DSN status codes are three-part enhanced codes (like 5.1.1) defined by RFC 3463 and carried inside DSN Status fields for asynchronous reporting. Both usually appear in the same bounce: the Diagnostic-Code field in a DSN quotes the SMTP reply that was received, while the Status field carries the enhanced code. The enhanced code is more precise (X.1.1 unambiguously means bad address, whereas 550 alone could mean many things).

Should I suppress an address after one soft bounce?

No. Soft bounces (4.x.x codes) are transient failures: the receiver could not accept the message right now (mailbox full, temporary greylisting, system busy), but the situation may resolve. Standard practice is to keep retrying (usually the sending MTA handles this automatically for 3 to 5 days), and only suppress if the same recipient soft-bounces 5+ times over 72 hours, or if the specific 4.x.x code correlates with a persistent problem (like 4.2.2 mailbox full over multiple weeks). Suppressing on the first soft bounce removes recoverable addresses.

How do I match a DSN back to my original outbound send?

Extract the Message-ID from the third part of the DSN (text/rfc822-headers or message/rfc822), then look it up in your outbound send log. Every outbound send should be logged with its Message-ID as a searchable key. The correlation lets you tie the bounce to the specific campaign, recipient list, template, and time of send. Without correlation, DSNs are anonymous statistics; with it, they become actionable data.

What does the Action field tell me that the Status field does not?

The Action field is a small enumeration: failed, delayed, delivered, relayed, expanded. It gives you the top-level outcome without needing to interpret the Status code. Two useful cases: (1) action=delayed combined with a 4.x.x Status tells you the reporting MTA is still trying and this is not a final answer; (2) action=delivered in a DSN is a success notification (rare, but possible if the sender requested one via ESMTP DSN extension). Use Action as the primary sort key and Status as the detail.

Do ESPs give me raw DSNs or preprocessed data?

Most modern ESPs (SendGrid, Postmark, Mailgun, SES) preprocess DSNs into webhook events with a normalized schema: they extract the recipient, the reason category, the SMTP code, and a timestamp, and post it to your webhook endpoint as JSON. Some also expose the raw bounce message for cases where the normalization loses detail. Amazon SES is the closest to raw (SNS notifications include the DSN structure). If you self-host or use a direct SMTP relay, you handle raw DSNs yourself. Either way, understanding the underlying DSN format helps you interpret the ESP’s normalization when it does not match your expectations.

Final words

DSN parsing is one of those pieces of email infrastructure that everyone thinks is solved until they debug a suppression bug and realize the parser has been silently misclassifying 5.7.1 policy failures as bad addresses for months. The RFCs are precise; the implementations vary; the receiver behavior is mostly consistent but not always. Getting parsing right pays for itself the first time you avoid mass-suppressing a segment because of a temporary DNS issue on your side.

The mental model to keep: multipart/report is the envelope, message/delivery-status is the payload, the Status field is the answer, the Diagnostic-Code is the raw quote, the third part is your correlation key. Every DSN follows the same skeleton; only the values change.

The next articles in this series build on DSN parsing: bounce categorization shows how to derive the five practical categories (address, mailbox, policy, content, technical) from the raw enhanced status codes; feedback loops (FBL) covers the complaint side; suppression lists ties everything together into a persistent do-not-send system. For the rest of the picture, see our guides on hard bounce vs soft bounce, the complete SMTP error codes reference, and the broader Deliverability hub. For the header interactions, see the Message-ID header and Authentication-Results header guides.

The best DSN is the one you never receive.

SMTPing catches invalid addresses, disposables, catch-all traps, role accounts and dead mailboxes before you send. Fewer sends means fewer 5.1.1 bounces flooding your parser. 13 validation types, 25 free daily.

Try SMTPing →

About the Author

Alaa - SMTPedia author

Alaa · LinkedIn

Email infrastructure specialist with 8+ years of hands-on experience in SMTP, deliverability, and email verification. I’ve configured and troubleshot mail systems across Postfix, Exchange, and cloud relays, managed IP reputation and warmup campaigns, and built verification pipelines processing millions of addresses. My work spans DNS authentication (SPF, DKIM, DMARC, BIMI), bounce handling, blocklist monitoring, and compliance frameworks including CAN-SPAM and GDPR. I write every article on SMTPedia to give email professionals, developers, and marketers the accurate, RFC-grounded reference they need.


About SMTPedia

SMTPedia is an independent email industry reference covering SMTP, IMAP, POP3, email deliverability, marketing platforms, DNS authentication, and email verification. Every article is researched from official provider documentation, IETF RFCs, and industry best practices. Settings and configurations are verified quarterly.

We are cited as a source by ChatGPT, Microsoft Copilot, and thousands of email professionals worldwide. Learn more about our editorial process.