Email learning series
Accepted, delayed, bounced: understanding email delivery outcomes
By Pedro Paiva
Our application gets a successful SES response and saves a message ID. That is evidence of submission, not evidence that the reader received the welcome message. Later events describe later stages.
Three timelines
These are synthetic examples. Replies belong to the receiving SMTP server; queue behavior belongs to the sending service.
Successful transfer
App → SES: submission accepted, ID saved
SES → recipient server: RCPT accepted; DATA ends; 250 2.0.0 accepted
SES → app: delivery event, if configured
Recipient system: filtering and mailbox placement happen internally
Temporary failure, later success
App → SES: submission accepted, ID saved
SES → recipient server: 451 4.3.0 temporary system problem after DATA
SES queue: retain message, wait, retry according to provider policy
SES → recipient server: final 250 on a later attempt
SES → app: delivery event, if configured
Permanent failure
App → SES: submission accepted, ID saved
SES → recipient server: RCPT rejected, 550 5.1.1 bad destination mailbox
SES: record failed delivery for this recipient
SES → app: bounce event, if configured
Only final acceptance after DATA transfers responsibility for that message. Recipient acceptance before DATA is an earlier checkpoint. A delivery event says the receiving server accepted the message; it doesn’t expose inbox versus spam placement or reading.
A queue bridges temporary outages. SMTP recommends retry strategies, but individual providers choose schedules and expiry behavior. Don’t invent a universal “retry every five minutes for 24 hours” rule. When a sender exhausts retries, it can report failure even though earlier replies were temporary.
Decode a status in context
An SMTP reply has a three-digit code. Enhanced status codes add a class.subject.detail value when supported. They refine the reason, rather than replacing the SMTP exchange’s stage.
| Illustrative reply | What this attempt tells us |
|---|---|
250 2.0.0 after DATA |
Receiver accepted responsibility. |
451 4.3.0 after DATA |
Temporary system problem; message not accepted. |
550 5.1.1 after RCPT |
Permanent bad destination mailbox result for that recipient. |
550 5.7.1 after DATA |
Permanent authorization/policy refusal of the message. |
The examples are not promises that all servers use these exact codes or descriptions. Capture the command stage, replying system, and complete response; “we saw 250” is insufficient evidence.
Providers often call failures hard or soft bounces. Their definitions and classification rules can differ. Preserve actual protocol evidence and the provider’s own classification rather than mapping every 4xx directly to “soft” forever.
Immediate rejection versus a later notification
If RCPT or DATA is rejected during an open connection, the sender directly receives the failure. The rejecting server hasn’t accepted responsibility for that message. The submitting application might still learn through a later SES event because SES, not the app, was the SMTP client.
If a receiving server first accepts the message and later cannot deliver it, it can generate a delivery-status notification (DSN) to the envelope return address. DSNs use a null envelope sender to prevent bounce loops. A standards-based machine-readable failure part can contain:
Reporting-MTA: dns; mx.example.net
Final-Recipient: rfc822; reader@example.net
Action: failed
Status: 5.1.1
This is an illustrative message/delivery-status body excerpt, not a complete multipart DSN. It reports a recipient’s failure; it doesn’t revoke the fact that an earlier SMTP handoff was accepted. A multi-recipient message can have different outcomes per recipient.
Turn events into useful records
For SES, record submission, provider ID, delivery, bounce and complaint evidence separately. Enable feedback notifications or event publishing, correlate the ID, and retain recipient-level outcomes. A complaint means feedback was reported, not that every provider reports every complaint.
Stop sending to permanently invalid addresses and honor complaints through suppression policy. Treat temporary failure as retryable only within a bounded, deliberate policy. Avoid retrying an already submitted message just because the delivery event hasn’t arrived: the provider may still be attempting it.
Saved only in this browser until you clear it. Reading progress isn’t sent to us.
References
- RFC 5321, sections 4.2, 4.5.4 and 6 — replies, queues and failure responsibility.
- RFC 3463 and IANA enhanced status registry — status semantics.
- RFC 3464 — DSN fields.
- AWS SES notification contents — delivery, bounce and complaint evidence.