Bounce Classification
PostMTA classifies every delivery failure as either a hard bounce (permanent) or a soft bounce (temporary). Classification drives suppression decisions, retry scheduling, and alerting thresholds.
Overview
A bounce occurs when a receiving mail server rejects a message during the SMTP delivery attempt. PostMTA intercepts the SMTP response code and maps it to an internal bounce classification before deciding whether to retry.
- Hard bounce — permanent failure. The recipient address is invalid, the domain does not exist, or the mailbox is permanently rejected. No retry.
- Soft bounce — temporary failure. The recipient's server is busy, the mailbox is full, or a connection problem occurred. Retried on a schedule.
Hard Bounces
Hard bounces indicate a permanent delivery failure. PostMTA immediately suppresses the recipient address and stops all future delivery attempts to that address.
SMTP codes that trigger hard bounce
| SMTP Code | Enhanced Code | Meaning |
|---|---|---|
550 | 5.1.1 | User unknown — mailbox does not exist. |
550 | 5.1.2 | Host unknown — destination domain does not exist. |
550 | 5.1.6 | Recipient address has been permanently deleted or disabled. |
550 | 5.1.7 | Invalid sender address format. |
550 | 5.1.8 | Bad sender domain (e.g., unresolvable domain in MAIL FROM). |
553 | 5.1.2 | Mailbox name not allowed (malformed address). |
554 | 5.3.0 | Message too large for recipient (permanent refusal). |
550 | 5.7.1 | Message rejected by receiving server policy (e.g., blocklist). |
SMTP response examples
550 5.1.1 User unknown <alice@nonexistent-domain.example.com>
550 5.1.2 Host unknown
550 5.7.1 Message rejected due to policyImmediate suppression
Upon hard bounce detection, PostMTA:
- Adds the recipient address to the workspace suppression list with reason
hard_bounce. - Stops all queued delivery attempts to that address.
- Fires a
message.bouncedwebhook event. - Increments the workspace hard bounce rate counter.
Soft Bounces
Soft bounces indicate a temporary failure. PostMTA retries the message according to the retry schedule until either the message is delivered or all retry attempts are exhausted.
SMTP codes that trigger soft bounce
| SMTP Code | Enhanced Code | Meaning | Retryable |
|---|---|---|---|
450 | 4.1.1 | Recipient mailbox temporarily unavailable. | Yes |
450 | 4.1.2 | Host temporarily unreachable. | Yes |
450 | 4.2.0 | Mailbox temporarily full. | Yes |
450 | 4.3.0 | Mail system full or out of disk space. | Yes |
451 | 4.4.1 | Server busy; try again later. | Yes |
451 | 4.3.2 | Server overload — too many connections. | Yes |
421 | 4.4.2 | Connection timed out. | Yes |
421 | 4.4.7 | Server greeting timeout. | Yes |
SMTP response examples
450 4.2.0 Mailbox temporarily full for <alice@example.com>
451 4.4.1 Server busy; try again later
421 4.4.2 Connection timed outRetry Schedule
Soft bounce retry attempts follow an exponential back-off schedule. After all retry attempts are exhausted, the message is marked assoft_bounced and the recipient is evaluated for dampening.
Retry attempt schedule
| Attempt | Delay after previous failure | Cumulative time | Total attempts |
|---|---|---|---|
| 1 | 5 minutes | 5 minutes | Initial delivery |
| 2 | 30 minutes | 35 minutes | Retry 1 |
| 3 | 2 hours | 2h 35m | Retry 2 |
| 4 | 8 hours | 10h 35m | Retry 3 |
| 5 | 24 hours | 34h 35m | Retry 4 |
| 6 (final) | — | ~34h 35m total | Last attempt |
After attempt 6 fails, the message is classified as soft_bounced. The recipient is placed on dampening hold (see below) and the workspace soft bounce counter is incremented.
Bounce Classification Algorithm
PostMTA applies the following decision tree on every SMTP delivery response:
1. Parse SMTP enhanced status code (e.g., 5.1.1, 4.4.1)
2. Map enhanced code to bounce class:
- Class H1 (hard bounce): enhanced code category 5.1.x, 5.2.x, 5.3.x, 5.7.x
- Class S1 (soft bounce): enhanced code category 4.1.x, 4.2.x, 4.3.x, 4.4.x, 4.5.x
- Class E1 (error): unclassifiable — treat as soft bounce, retry once after 5 min
3. If hard bounce:
a. Suppress recipient immediately.
b. Fire webhook event "message.bounced".
c. Stop processing.
4. If soft bounce:
a. Increment retry counter for this message.
b. If retry_count < max_retries: re-queue with back-off delay.
c. If retry_count >= max_retries: mark soft_bounced, apply dampening.
5. If SMTP session error (connection dropped, TLS failure):
a. Treat as soft bounce.
b. Retry on next scheduling run (up to 3 session-level retries).Dampening Controls
Dampening temporarily or permanently suppresses a recipient after excessive soft bounce failures. PostMTA applies dampening at two levels: per-recipient and per-domain.
Per-recipient dampening
After a soft_bounced final state, PostMTA places the recipient on dampening hold for 4 hours by default. During the hold period, all messages to that address are suppressed without triggering a webhook. After the hold expires, PostMTA attempts one delivery. If it fails again, the hold extends to 24 hours, then 7 days.
Per-domain dampening
If more than 10% of messages to a given destination domain produce soft bounces within a 15-minute window, PostMTA applies domain-level dampening for that domain. During domain dampening:
- All new messages to that domain are queued rather than attempted immediately.
- Delivery retires are paused for 30 minutes.
- A
domain.dampenedevent is fired.
Global dampening controls
Workspace administrators can configure global dampening thresholds and hold durations in the dashboard under Settings → Dampening:
| Setting | Default | Description |
|---|---|---|
| Initial dampening hold | 4 hours | First soft bounce hold duration. |
| Extended hold | 24 hours | Hold after second soft bounce. |
| Final hold | 7 days | Hold after third consecutive soft bounce. |
| Domain dampening threshold | 10% | Domain soft bounce rate triggering domain dampening. |
| Domain dampening duration | 30 minutes | How long domain dampening lasts. |
Bounce Webhook Events
Configure webhook subscriptions for bounce events to build your own suppression list management, alerting, or analytics pipeline.
message.bounced — hard bounce
{
"event": "message.bounced",
"timestamp": "2026-07-18T12:10:00Z",
"data": {
"message_id": "msg_01HX5K9P7N3M4Q6R8T2V4W6Y8",
"from": "hello@mail.example.com",
"to": "alice@nonexistent.example.com",
"bounce_class": "hard",
"smtp_code": "550",
"smtp_enhanced_code": "5.1.1",
"bounce_reason": "User unknown",
"suppressed": true
}
}message.soft_bounced — final soft bounce
{
"event": "message.soft_bounced",
"timestamp": "2026-07-18T22:00:00Z",
"data": {
"message_id": "msg_01HX5K9P7N3M4Q6R8T2V4W6Y8",
"from": "hello@mail.example.com",
"to": "alice@example.com",
"bounce_class": "soft",
"smtp_code": "450",
"smtp_enhanced_code": "4.2.0",
"bounce_reason": "Mailbox temporarily full",
"retry_count": 6,
"dampening_until": "2026-07-19T02:00:00Z",
"suppressed": false
}
}Bounce Rate Alerting
PostMTA monitors your workspace's bounce rate and fires alerts when thresholds are exceeded. These appear in the dashboard and can be forwarded via webhook or email.
| Alert | Threshold | Action |
|---|---|---|
| High hard bounce rate | > 3% hard bounces in 1 hour | Dashboard warning banner + webhook event workspace.high_bounce_rate |
| Critical hard bounce rate | > 8% hard bounces in 1 hour | Automatic sending pause until acknowledged; support notified |
| Domain dampening active | Domain threshold exceeded | Webhook event domain.dampened |
Next Steps
- Sending Email — send messages that trigger bounces
- Webhooks — subscribe to bounce events
- Custom Headers — RFC 7230 compliance for header formatting