A 200 response is not a delivery receipt
HTTP status describes how the server handled the HTTP request. It does not automatically prove that a background job ran, SMTP credentials existed, a provider accepted the message, a receiving server accepted it, or a user could see it.
- HTTP 200 or 202
- The endpoint completed according to its code path. Read the response contract before calling the email sent.
- Queue or job ID
- The application accepted work. The worker can still fail, skip, time out, or use the wrong environment.
- Provider message ID
- The provider accepted a specific request. Suppression, delay, bounce, and receiver acceptance still follow.
- Delivered event
- A receiving mail system normally accepted the SMTP transaction. Inbox visibility is still a separate boundary.
Map the incident locally. Use evidence categories only; do not enter a recipient, message, code, link, token, or credential.
Read the signed public Nostr incident path for the complete application-to-provider workflow and separate operations-kit handoff.
The first ten minutes
- Freeze repeated sends and choose one attempt with an exact UTC time and random internal correlation ID.
- Read the handler result and logs. Confirm whether it returned after validation, enqueue, worker completion, or provider acceptance.
- Follow the correlation ID into the queue. Record enqueue, start, retry, terminal failure, and dead-letter evidence.
- At worker start, verify that required configuration is present and points at the intended environment without printing any secret value.
- Require either a provider message ID or an explicit failure class. If neither exists, stop at the application-to-provider boundary.
Common false-success paths
- A mail helper returns early when an SMTP username, password, API key, sender identity, or template is missing, while its caller ignores the result.
- A broad exception handler logs a warning and lets the endpoint return the same success payload used for a real handoff.
- The endpoint returns before a background task executes, but the UI changes the wording from requested or queued to sent.
- The worker runs in a different environment, region, account, or secret namespace than the API process.
- A provider sandbox, unverified sender, suppression entry, quota, or rate limit blocks the message before SMTP delivery.
A recent public bug report documented the exact first pattern: missing SMTP credentials caused the send helper to return without sending, while registration still returned 200 and told the user a code was sent. The useful evidence was the early return, not another inbox search.
Never log a live OTP, verification URL, password-reset token, SMTP password, provider key, or recipient address to make this path observable. Log bounded status, random correlation IDs, configuration presence, and provider identifiers instead.
Make the response contract truthful
Model the delivery request as states that can be measured. A minimal path is requested, queued, worker_started, provider_accepted, receiver_accepted, and failed. Do not collapse those states into a boolean named sent.
result = requestVerificationEmail(correlationId)
if result.status != "provider_accepted":
return 503, {
"status": result.status,
"retryable": result.retryable,
"trackingId": correlationId
}
return 202, {
"status": "provider_accepted",
"trackingId": correlationId
}
The exact status code and asynchronous contract depend on the application. The invariant is simpler: user-facing wording, API fields, logs, alerts, and tests must not claim a stronger boundary than the evidence supports.
Add failure tests before the next incident
- Missing credentials: the mail helper returns or throws an explicit configuration failure and no endpoint says sent.
- Queue unavailable: the request exposes a retryable queue failure and does not create an orphaned verification secret.
- Provider rejects: retain the provider error class without leaking credentials or a message body.
- Provider accepts: persist one provider message ID against the correlation ID before reporting that boundary.
- Retry: enforce idempotency, rate limits, and clear invalidation rules for older links or codes.
Only then investigate sender and receiver delivery
Once a real provider message ID exists, follow provider events through suppression, processing, delay, bounce, or receiver acceptance. Preserve complete SMTP diagnostics. After acceptance, inspect quarantine, rules, forwarding, mailbox views, and a trusted original header.
For the actual production sending path, verify SPF authorization, the observed DKIM selector, DMARC alignment, sending-IP PTR, consent, complaints, bounce handling, and volume changes. Public DNS evidence can explain a documented rejection; it cannot prove that the application generated a message.
Use the SMTP error decoder when a complete reply exists, or the local header analyzer after a controlled copy arrives. If SMTP first returns 250 and a later DSN changes the outcome, follow the per-recipient asynchronous status model.
Retry without weakening authentication
Fix the first unproven boundary, then send one controlled replacement through the same production path. Keep rate limits, abuse controls, expiration, and single-use behavior active. Make the current request visible and invalidate older secrets according to the product's security model.