Bounce Handling
A bounce is a delivery failure. ScaiSend classifies bounces, records them on the message timeline, fans out bounce events to webhooks, and adds the recipient to the suppression list so you don't repeat the failure. This page explains how.
Two paths to a bounce#
Bounces arrive two ways:
- Synchronous SMTP response. The recipient MX returns a 5xx at the end of the SMTP conversation. ScaiSend sees it immediately.
- Asynchronous DSN. The recipient MX accepts the message (returns 2xx), but later — after internal routing — can't deliver. It sends a Delivery Status Notification (RFC 3464) back to the envelope sender. ScaiSend's inbound SMTP server receives and parses this.
Both paths end up the same: a bounce event on the message, the address added to the bounce suppression list.
Synchronous bounces#
At the end of RCPT TO: or DATA, the recipient can reply with:
| Response | Classification | Action |
|---|---|---|
250 OK |
Delivered | Record delivered event, update message status |
4xx |
Temporary failure (deferred) | Retry with exponential backoff; record deferred event |
5xx |
Permanent failure (bounce) | Record bounce event; add to bounce suppression list |
ScaiSend parses the response string to classify further. A 550 5.1.1 User unknown is a hard bounce (recipient doesn't exist). A 552 5.2.2 Mailbox full is a soft bounce (might succeed later if retried). A 550 5.7.1 Message rejected due to content is a block (policy decision; not necessarily permanent).
Bounce types recorded#
bounce_type |
Trigger |
|---|---|
hard |
Permanent failure: invalid recipient, non-existent mailbox, account closed |
soft |
Temporary failure that exhausted retries: mailbox full, message too large |
block |
Sender reputation issue: IP blocklisted, content rejected |
Asynchronous bounces (DSNs)#
Sometimes the recipient MX accepts a message, queues it internally, and only later discovers it can't deliver (e.g., forward loop, downstream server unreachable, virus-scan rejection). The convention is to send a DSN back to the envelope sender.
ScaiSend's inbound SMTP server (port 25) accepts these DSNs. The parser:
- Reads the multi-part message body.
- Extracts the
message/delivery-statuspart, which contains machine-readable reporting. - Finds the
Original-Recipient,Action(e.g., "failed"),Status(SMTP enhanced status code), andDiagnostic-Codefields. - Matches the DSN to the original message via the
Message-IDor theX-ScaiSend-Message-Idheader that ScaiSend injected. - Records a
bounceevent with parsed fields and adds the recipient to the suppression list.
What the inbound server accepts#
The inbound server is narrowly scoped: it accepts DSN messages, ARF feedback-loop reports, and refuses everything else. It's not a general-purpose MTA. Arbitrary inbound mail from the internet is rejected at the SMTP conversation level.
For the inbound server to work, port 25 must be reachable and an A/AAAA record pointing to it. See Deployment.
Retry policy for deferrals#
A 4xx response doesn't give up immediately. ScaiSend retries with exponential backoff:
| Attempt | Delay after previous attempt |
|---|---|
| 1 (initial send) | — |
| 2 | ~60 seconds |
| 3 | ~5 minutes |
| 4 | ~15 minutes |
| 5 | ~1 hour |
| 6 (last) | ~4 hours |
Actual delays have jitter to avoid thundering herds, and are bounded by SMTP_MAX_RETRIES (default 5 retries = 6 attempts total) and SMTP_RETRY_BASE_DELAY (default 60 seconds).
After the final retry, if still deferred, ScaiSend classifies as a soft bounce and marks the message BOUNCED.
Greylisting#
Some recipient MXs greylist — they return 4xx on first attempt from an unknown sender, expecting a real MTA to retry. ScaiSend detects the common greylisting responses (e.g., 4.7.1 with specific wording like "greylisted") and retries more aggressively (shorter initial delay), which satisfies the greylist check.
You typically see one deferred event, then a delivered event seconds later. Nothing to do; this is working as intended.
Suppression#
Every hard bounce automatically adds the recipient to the tenant's bounce suppression list. Future sends to that address are dropped (status FAILED, event dropped, reason suppressed_bounce) unless you bypass with mail_settings.bypass_bounce_management: {enable: true}.
Soft bounces also add to the list by default. If you'd rather let soft-bounced addresses be retried on future sends, delete them from the list after they resolve:
1 2 | |
See Suppressions for the full suppression model.
Event payload#
A bounce event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
metadata.diagnostic_code is what the recipient MX actually said (for synchronous bounces, it's the raw SMTP response; for async DSNs, it's the Diagnostic-Code field from the DSN). Log this for support triage.
Interpreting bounce reasons#
Common bounce reasons and their meaning:
| SMTP class | Typical cause | What to do |
|---|---|---|
5.1.1 |
Recipient doesn't exist | Remove from your list; the address is dead |
5.1.10 |
Recipient address null-routed | Same as above |
5.2.1 |
Mailbox disabled | User left the company or was deactivated |
5.2.2 |
Mailbox full | Soft bounce; retry later possibly |
5.4.4 |
Unable to route | DNS issue at recipient; usually transient |
5.5.0 |
Syntax error in message | Check your message structure; template bug |
5.7.1 |
Message rejected for policy | Spam filter, IP reputation, content block |
5.7.26 |
DMARC failure | Your DKIM/SPF/DMARC isn't aligned — fix DNS |
Reputation management#
Hard bounce rate is the single most important deliverability metric. Over 2% and you'll start getting rate-limited by major providers. Over 5% and you'll be outright blocked by some.
Monitor:
1 2 | |
Calculate bounces / requests. If it's trending up:
- Audit your list sources. Did you import addresses from a suspect list?
- Audit your signup flow. Are you validating emails at registration?
- Check a random sample of bounced addresses. If they're all
5.1.1, your list has invalid addresses. If they're5.7.*, your reputation is the problem.
Related#
- Suppressions — what happens to bounced addresses.
- Feedback Loops — the spam-complaint counterpart to bounces.
- Events and Webhooks — receiving
bounceevents.