What SMTP 250 proves
RFC 5321 says that a receiver sending 250 OK after the terminating DATA sequence accepts responsibility to deliver or relay the message. A failure discovered after that acceptance is reported separately to the envelope return path. The application therefore has evidence of one completed SMTP handoff, not final inbox placement.
- Proved
- The receiving SMTP hop accepted responsibility for this message transaction.
- Not proved
- The destination mailbox exists, every recipient succeeded, the final recipient server accepted the message, or a user can see it in an inbox.
- Immediate failure
- A 4yz or 5yz response before final acceptance remains a synchronous submission result for the affected recipient or transaction.
- Later failure
- A DSN, NDR, or verified provider event can change an accepted attempt to delayed or bounced after the application request has returned.
Use an evidence-based state model
Avoid a single boolean named sent or delivered. Keep transport evidence and user-visible evidence separate, with timestamps and the source of each transition.
pending: the application stored a stable notification and recipient-attempt ID before submission.submission_failed: the local client or SMTP hop rejected the attempt synchronously.accepted_by_relay: the SMTP transaction received final positive completion. This is the honest meaning of the initial success.delivery_delayedorbounced: an authenticated asynchronous event reports a temporary or permanent downstream outcome.recipient_server_accepted: a provider event or success DSN explicitly supports that narrower claim. It still does not prove inbox placement.inbox_visible: separate recipient-side evidence, when the product can obtain it lawfully and safely.
Do not present an MX lookup as mailbox verification. MX proves that a domain advertises mail routing. It does not prove that a particular local part exists or accepts mail.
Return every synchronous recipient result
In a batch loop, do not let the first send error discard the outcomes for later recipients. Store each recipient attempt first, submit independently, and return a bounded result for every attempted recipient. Do not expose full addresses in logs or public responses.
- Stable key
- Use an opaque notification ID plus recipient-attempt ID generated before sending.
- Success value
- Return
accepted_by_relaywith the SMTP or provider transaction identifier, not "email delivered." - Failure value
- Return a classified synchronous error per recipient while continuing the batch according to an explicit policy.
- Retry rule
- Make transitions idempotent and retry only transient failures. A duplicate webhook or DSN must not duplicate notifications or reverse a terminal state.
Correlate asynchronous outcomes
Use an opaque token in the envelope return path when VERP-style correlation is available. If the relay advertises the SMTP DSN extension, ENVID identifies the transaction and ORCPT preserves the original recipient for the DSN. RFC 3464 explicitly separates Original-Envelope-Id from the message header's Message-ID, so Message-ID should not be the only correlation key.
- For provider webhooks, verify the signature before parsing, map the provider message ID to the stored attempt, and deduplicate by a stable provider event ID.
- For mailbox-delivered DSNs, parse
message/delivery-statuswith bounded MIME depth, body size, field count, and UTF-8 handling. - Reject forged, unmatched, stale, or invalid transitions. Record the evidence source and observed time without retaining unnecessary message content.
- Keep recipient addresses, message bodies, verification links, OTPs, credentials, and raw provider payloads out of analytics and public diagnostics.
A safe implementation sequence
- Rename the current synchronous success to
accepted_by_relay. - Persist one stable attempt per recipient before calling the mailer.
- Collect all per-recipient send results instead of aborting the batch at the first error.
- Add a monitored envelope return path or authenticated provider webhook and correlate it to the attempt.
- Apply idempotent delayed, bounced, and recipient-server-accepted transitions.
- Notify the original application actor with bounded status text; never include another recipient's result.
- Test immediate 4yz/5yz failures, delayed DSNs, permanent DSNs, duplicate events, forged events, partial batches, and retry races.
See the provider-event boundary map when a delivery service supplies queued, delayed, bounced, or delivered events. For a later NDR containing a three-digit or enhanced status code, decode the bounded diagnostic locally.