Email Backscatter: Stop Bounces From Reaching Innocent Senders
Email backscatter is an unwanted bounce sent to an address that never sent the original message. It happens when a receiving system accepts mail with a forged SMTP reverse path, later decides it cannot deliver the message, and sends the failure report to the forged address.
The safest fix is to reject bad mail during the original SMTP transaction whenever the receiving system already has enough evidence. That leaves failure reporting with the connected sender instead of creating a new message to an uninvolved third party.
I use one boundary to review this problem: did our server reject the transaction, or did it accept responsibility and create a second email later?
How does one forged address become backscatter?
Imagine an attacker sends a message to a nonexistent user at example.net. The attacker chooses victim@example.org as the SMTP reverse path.
C: MAIL FROM:<victim@example.org>
S: 250 2.1.0 Sender accepted
C: RCPT TO:<missing-user@example.net>
S: 250 2.1.5 Recipient accepted
C: DATA
S: 354 End data with <CRLF>.<CRLF>
C: ...message...
C: .
S: 250 2.0.0 Message accepted
The receiver has now accepted responsibility for the message. If a downstream process discovers that missing-user@example.net does not exist, it may generate a delivery status notification for victim@example.org. The victim sees a bounce for a message they did not send.
The visible From: header is not the routing instruction for that report. SMTP uses the reverse path supplied by MAIL FROM, later recorded as Return-Path at final delivery. The From, Reply-To, and Return-Path guide separates those identities.
A forged reverse path is not automatically detectable. The important operational mistake is accepting a message that the system could already reject, then trusting that unverified path as the destination for a new notification.
What should the safe SMTP exchange look like?
If the receiving server knows during the SMTP session that the local recipient does not exist, it can reject that recipient before accepting the message:
C: MAIL FROM:<victim@example.org>
S: 250 2.1.0 Sender accepted
C: RCPT TO:<missing-user@example.net>
S: 550 5.1.1 Recipient address rejected
C: RSET
S: 250 2.0.0 Reset state
No new bounce leaves example.net. The connected SMTP client receives the failure and decides what to do with it. If that client is the attacker's system, the forged victim is not pulled into the exchange. If it is a legitimate relay, that relay still has the original submission context needed to report the failure correctly.
RFC 5321 section 6.1 draws the same responsibility boundary. A server that returns a positive completion reply after message data accepts responsibility for delivery or later failure reporting. My sent-versus-delivered guide explains why that final 250 is stronger than the earlier replies.
Recipient rejection is not always available at RCPT TO. A relay may not know the final mailbox state, a mailing list may expand later, or a temporary directory failure may make the answer inconclusive. Preserve those cases as temporary or unknown. Do not turn uncertainty into a definite nonexistent-mailbox claim.
Which failures belong before and after acceptance?
Move a decision into the original SMTP transaction when the necessary evidence is already available there. That commonly includes an invalid local recipient, a blocked sending client, an authentication policy rejection, a message-size limit, or content that the receiving edge has conclusively refused.
Some failures only appear later. A downstream hop can fail, storage can become unavailable, or a list expansion can produce a bad recipient after the first server accepted the message. Those are legitimate reasons to generate a delivery status notification. The design goal is not “never send a bounce.” It is “do not accept first and rediscover a known rejection later.”
There is a fair objection here. SMTP requires a server to take acceptance seriously, so silently dropping every post-acceptance failure would also be wrong. Correct. The fix is earlier rejection where possible, careful DSN generation where it is not, and special handling for hostile mail. RFC 5321 section 6.2 says rejection notices for hostile content should generally be avoided unless the receiving site is confident the notice will be useful.
How do you create a DSN without creating a loop?
A delivery status notification has two envelope rules that matter here.
- Address the DSN to the transport envelope return address that accompanied the original message, not to its visible
From:orReply-To:header. - Send the DSN itself with a null reverse path:
MAIL FROM:<>.
RFC 3464 defines the machine-readable report format and requires the null reverse path so another delivery failure does not generate a bounce for the bounce.
MAIL FROM:<>
RCPT TO:<original-envelope-sender@example.org>
Content-Type: multipart/report; report-type=delivery-status
Auto-Submitted: auto-generated
Never respond to a message whose reverse path is already empty. An empty path is an explicit loop-prevention signal used by DSNs and other system-generated mail. A second report would have no useful destination.
Keep the report compact. Include enough original header data and per-recipient status information to associate the failure with the attempted delivery. Do not attach an entire hostile payload merely because a full copy is convenient for debugging.
Automatic replies need the same audit
Backscatter is not limited to formal bounces. Vacation responders, challenge systems, contact-form acknowledgements, and “mailbox full” scripts can all send unwanted mail to a forged address.
RFC 3834 says an automatic responder must not answer blindly. It should not respond when Auto-Submitted has a value other than no, and it must not generate a response whose destination is the null address. Those rules prevent loops and reduce the value of your system as an abuse amplifier.
The auto-reply classification guide owns the full header and MIME workflow. For a backscatter audit, I would trace every code path that can create an outbound message after inbound mail: DSNs, out-of-office notices, helpdesk acknowledgements, forwarding errors, moderation notices, and challenge responses. Each path should identify its envelope destination, suppression signals, and rate limit.
Where does email verification fit?
Email verification can reduce avoidable outbound failures before an application sends an invite, confirmation, or transactional message. A malformed address, a domain without usable mail routing, or a confirmed mailbox rejection can be handled before it enters the sending queue.
That is different from validating recipients for your own inbound mail server. The current emailverifier.dev API keeps deliverable, risky, undeliverable, and unknown results distinct. An inconclusive check stays unknown, and no result proves inbox ownership or guaranteed delivery.
For the inbound side, audit the acceptance boundary directly. Send a message to a known nonexistent local recipient and confirm that the server returns a failure during the same SMTP transaction. Then submit a message with MAIL FROM:<> and confirm that no automatic response is generated. Finally, feed an Auto-Submitted: auto-generated message into each responder and confirm that it stops.
The useful success signal is not “we generated a better bounce.” It is that rejected mail stays inside the original SMTP conversation whenever the server already knows the answer.