The standard gives you machine-readable structure

RFC 3464 defines a Delivery Status Notification as a multipart/report message whose report-type is delivery-status. The second part uses message/delivery-status and contains per-message plus per-recipient fields. That structure exists so software can distinguish failed, delayed, delivered, or other delivery outcomes without parsing natural-language prose. A bounce processor should take advantage of it whenever the message is standards-conformant.

Do not use From or Subject as the primary classifier

Subjects vary by provider, language, and product: “Delivery Status Notification,” “Undeliverable,” “Mail delivery failed,” or something localized. Sender display names are equally inconsistent. Treat those values as hints for discovery, not authoritative state. First inspect Content-Type and MIME parts. If a message/delivery-status part exists, parse the fields inside it. This reduces false hard bounces from warning messages and makes the classifier portable across mailbox languages.

Extract per-recipient fields

A single DSN can describe more than one recipient. For each recipient block, store Final-Recipient, Original-Recipient when available, Action, Status, Diagnostic-Code, and Remote-MTA. The Action may indicate failed or delayed, while Status provides the enhanced class. Map the recipient back to the exact outbound lead rather than applying one top-level result to every address in the original message. This matters when one recipient fails permanently and another is merely delayed.

Preserve the original Message-ID linkage

DSNs can include the original message or headers as another MIME part. Use that material to recover the original Message-ID, envelope recipient, campaign identifier, or provider message ID. Matching only by subject can collide when many outbound messages share a template. A strong linkage lets the system suppress the correct lead, cancel only the relevant follow-up, and keep the delivery history attached to the exact logical message.

Classify Action and Status together

Action: failed with a 5.x.x status is a strong permanent-delivery signal. Action: delayed with a 4.x.x status should remain temporary. Real providers sometimes emit imperfect combinations, so keep the raw fields and use conservative fallbacks when they disagree. Do not translate every DSN into hard bounce. The classifier should preserve uncertainty instead of manufacturing certainty from a partially structured notice.

Handle non-standard bounces as a separate parser path

Not every provider or legacy system produces a perfect RFC 3464 DSN. After the standards parser fails, fall back to provider-specific patterns using the raw body, headers, and SMTP text. Keep those rules isolated and labeled by provider so they can be changed without weakening the standards path. Over time, unknown examples should be added to fixtures. This layered approach is safer than one huge regex applied to every incoming message.

Do not count DSNs as human replies

A bounce is an automated report, not evidence that the prospect engaged. The reply detector should classify DSN before the human-reply path and should never stop a sequence with a “positive reply” reason because MAILER-DAEMON sent a message. It may still stop the sequence for delivery failure, but that is a different state with different analytics. Keeping engagement and transport feedback separate makes campaign metrics far more honest.

Test with failed and delayed samples

Create fixtures for at least one permanent unknown-user DSN, one temporary delayed-delivery DSN, one mailbox-full notice, and one provider-specific non-standard bounce. Assert the extracted recipient, action, enhanced status, diagnostic text, and original-message linkage. Then run the parser on a mailbox dump in dry-run mode and manually inspect unknown classifications. Bounce parsing is infrastructure code; it deserves deterministic tests rather than learning in production.

Parse standards fields first, then fall back to provider text

RFC 3464 defines a DSN as `multipart/report` with `report-type=delivery-status`; the machine-readable component uses `message/delivery-status` and can contain per-recipient fields such as Final-Recipient, Action, Status, and Diagnostic-Code. A bounce processor should look for that structure before scraping a human-readable paragraph. One DSN can describe more than one recipient, so parse each recipient block independently rather than assigning one status to the whole returned message. This also prevents a single multi-recipient failure report from incorrectly suppressing every address on the original send.

Correlate the DSN back to your outbound record using the returned original-message material and identifiers your MTA preserves, while treating the human-readable subject as display text only. The `Action` field is especially useful alongside the enhanced `Status`: `delayed` means the reporting system may continue trying, while `failed` means it has abandoned delivery for that recipient. Keep non-standard bounce formats in a separate fallback path and tag them with lower parser confidence so they can be reviewed. Build test fixtures for a permanent invalid recipient, a temporary delay, a mailbox-full case, and a multi-recipient DSN. The parser is part of list hygiene infrastructure, so its mistakes should be visible and reproducible rather than silently turned into hard-bounce counts.

Keep the raw DSN beside the normalized bounce event

Normalization is useful, but the original DSN should remain available for audit. Store the raw MIME message or a privacy-conscious retained representation together with the parser version and normalized fields. When a provider changes wording or a rare multi-recipient report is classified incorrectly, you can replay the sample against a fixed parser instead of guessing from a database row that says only `hard_bounce`. A small regression corpus built from these preserved examples is particularly valuable after parser updates: the same permanent, delayed, quota, and policy samples should continue producing the expected structured events.

Field checklist

  • Detect multipart/report and report-type=delivery-status first.
  • Parse each per-recipient block rather than one top-level verdict.
  • Store Action, Status, Final-Recipient and Diagnostic-Code.
  • Link the DSN back to the original Message-ID or campaign message.
  • Keep non-standard provider regexes in a separate fallback layer.
  • Test permanent, delayed and mailbox-full DSN fixtures.

Primary sources

Standards and provider policies can change. These links are the reference points used for this field note.

  1. RFC 3464 — Delivery Status Notification FormatIETF / RFC EditorMachine-readable multipart/report and message/delivery-status format for delivery status notifications.
  2. RFC 3463 — Enhanced Mail System Status CodesIETF / RFC Editor2.x.x, 4.x.x and 5.x.x delivery status code classes.
  3. RFC 5321 — Simple Mail Transfer ProtocolIETF / RFC EditorSMTP reply classes, retry semantics, envelopes and mail transport behavior.
  4. RFC 5322 — Internet Message FormatIETF / RFC EditorMessage header structure, Message-ID, In-Reply-To and References threading fields.