Hard vs Soft Bounces: SMTP Codes and Suppression Lists
The difference between hard and soft bounces, how to read SMTP and enhanced status codes, and how to build suppression rules that protect your reputation.
6 min read
How VERP encodes the recipient in the return-path, how to parse RFC 3464 DSNs, and how to tell real bounces from auto-replies in an automated bounce pipeline.
Pidgely teamPublished 5 min readMarkdown version

VERP (Variable Envelope Return Path) is a bounce-handling technique in which every message is sent with a unique envelope sender, such as bounce-7f3k9q@bounces.example.com, that encodes which recipient or message it belongs to. When a delivery failure comes back, the address it was sent to tells you exactly which recipient failed, even if the bounce body is malformed. A complete automated pipeline combines VERP for identification, RFC 3464 Delivery Status Notification (DSN) parsing for the reason, and filters that separate real bounces from auto-replies and other noise.
Synchronous failures are easy: your MTA receives 550 5.1.1 User unknown during the SMTP session and records it. The hard part is asynchronous bounces. A server accepts the message, then later finds it cannot deliver it and sends a new message back to the envelope sender.
Those messages are inconsistent. Many follow RFC 3464, but some servers send free-text bounces, some rewrite the recipient after forwarding, and some report an internal alias rather than the address you sent to. If you rely on parsing the human-readable text to find the recipient, you will mismatch or miss bounces. VERP removes that guesswork.
Without VERP, all mail uses one return-path:
MAIL FROM:<bounces@example.com>With VERP, the local part becomes unique per recipient or per message:
MAIL FROM:<b-01HZX4Q7M2-jane=example.net@bounces.example.com>The classic VERP format, as introduced for qmail, embeds the recipient address with @ replaced by =. Modern systems usually prefer an opaque token instead, which does not leak the recipient address and keeps the local part short:
MAIL FROM:<b-7f3k9q2xv1@bounces.example.com>The token maps (in a database or through an HMAC-signed encoding) to the message ID, the recipient and the stream. When a bounce arrives at b-7f3k9q2xv1@bounces.example.com, you look up the token and know exactly what failed.
The return-path domain must receive mail and authorize your sending IPs:
bounces.example.com. IN MX 10 inbound.example.com.
bounces.example.com. IN TXT "v=spf1 ip4:203.0.113.0/28 -all"Because it is a subdomain of example.com, SPF aligns with a From: ...@example.com address under DMARC relaxed alignment (RFC 7489). VERP and DMARC work together without conflict.
A standards-compliant bounce is a multipart/report message (RFC 6522) with report-type=delivery-status. It is sent with an empty envelope sender (MAIL FROM:<>) so that bounces never generate more bounces.
Return-Path: <>
To: b-7f3k9q2xv1@bounces.example.com
Subject: Undelivered Mail Returned to Sender
Content-Type: multipart/report; report-type=delivery-status; boundary="XYZ"
--XYZ
Content-Type: text/plain
The following address failed: jane@example.net
--XYZ
Content-Type: message/delivery-status
Reporting-MTA: dns; mx1.example.net
Arrival-Date: Mon, 12 Oct 2026 14:03:20 +0000
Final-Recipient: rfc822; jane@example.net
Original-Recipient: rfc822; jane@example.net
Action: failed
Status: 5.1.1
Remote-MTA: dns; mailstore3.example.net
Diagnostic-Code: smtp; 550 5.1.1 <jane@example.net>: Recipient address rejected: User unknown
--XYZ
Content-Type: message/rfc822-headers
Message-ID: <01HZX4Q7M2@mail.example.com>
From: Example <hello@example.com>
--XYZ--The fields defined by RFC 3464 that matter:
failed, delayed, delivered, relayed or expanded. Only failed is a bounce. delayed is a warning that the remote server is still retrying.5.1.1 or 4.2.2.The third part, either message/rfc822 or message/rfc822-headers, contains the original message or its headers. Your own Message-ID there is a second way to correlate.
The return-path address receives more than bounces. Classify every inbound message before acting:
| Message | How to recognize it | Action |
|---|---|---|
| DSN failure | multipart/report; report-type=delivery-status, Action: failed | Classify by Status, maybe suppress |
| DSN delay | Same, Action: delayed | Record, do not suppress |
| Auto-reply | Auto-Submitted: auto-replied (RFC 3834), X-Autoreply, Precedence auto_reply, normal content type | Ignore for suppression |
| Challenge-response | Asks you to verify you are human | Ignore |
| Non-standard bounce | Plain text with phrases like "user unknown", from MAILER-DAEMON or postmaster | Heuristic parsing, conservative actions |
| Complaint (ARF) | multipart/report; report-type=feedback-report (RFC 5965) | Route to complaint handling |
| Human reply | Anything else | Forward to a mailbox or drop by policy |
Never suppress an address because of an out-of-office reply. The mailbox exists, and the person will be back.
inbound SMTP (bounces.example.com)
-> accept; store raw message
-> resolve VERP token -> (message_id, recipient, stream)
-> classify: dsn | arf | auto-reply | unknown
-> dsn: parse per-recipient fields; for each Action=failed
-> classify Status: address | transient | policy
-> address: add to suppression list (global)
-> transient: count toward soft-bounce threshold
-> policy: record + alert, no suppression
-> emit event (bounced / delayed / complained) to webhooksImplementation notes:
550 or accept and discard. Do not let your bounce domain become an open relay or backscatter target.Action: delayed as a bounce.Pidgely uses VERP on a custom return-path subdomain for every message, parses DSNs and ARF reports on its own inbound servers, and turns them into bounce and complaint events with automatic suppression.
VERP (Variable Envelope Return Path) is a technique where each message gets a unique envelope sender address that encodes the recipient or message ID, such as bounce-a1b2c3@bounces.example.com. Any bounce sent back to that address identifies the failed recipient without parsing the bounce body.
No, as long as the return-path domain is covered by your SPF record and aligns with the From domain. Only the local part varies, so a subdomain such as bounces.example.com still aligns under relaxed DMARC alignment.
A real bounce is usually a multipart/report message with report-type=delivery-status (RFC 3464), sent with an empty envelope sender. Auto-replies are normal messages, often marked with Auto-Submitted: auto-replied (RFC 3834), and must never be treated as bounces.
In the per-recipient section, Final-Recipient or Original-Recipient identifies the address, Action (failed, delayed, delivered, relayed, expanded) gives the outcome, Status is the enhanced status code, and Diagnostic-Code contains the remote server's reply.
Pidgely team
Published
We build Pidgely, the email API that gets you to the inbox. We write about what we learn delivering email, citing the standards and mailbox provider guidelines behind every claim.
The difference between hard and soft bounces, how to read SMTP and enhanced status codes, and how to build suppression rules that protect your reputation.
6 min read
How email feedback loops and ARF complaint reports work, how to measure complaint rate correctly, and practical ways to keep it under Gmail's 0.1% target.
5 min read
A practical checklist of the Gmail and Yahoo bulk sender requirements: authentication, DMARC alignment, one-click unsubscribe, spam rate limits and DNS.
5 min read