> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lettr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bounces

> Learn how Lettr handles hard and soft bounces, bounce classes, and webhook events so you can protect deliverability and sender reputation.

Bounces occur when an email cannot be delivered to the recipient. Understanding different bounce types and handling them correctly is essential for maintaining good deliverability.

## Bounce Types

### Hard Bounces

Hard bounces are permanent delivery failures. The recipient's mail server has definitively rejected the email, and retrying will not succeed.

**Common causes:**

* Email address doesn't exist
* Domain doesn't exist
* Recipient has blocked the sender
* Mailbox has been deactivated

When a hard bounce occurs, you receive a `message.bounce` webhook event. Hard bounces use bounce classes in the 10–30 and 100 ranges:

```json theme={null}
[{
  "msys": {
    "message_event": {
      "type": "bounce",
      "timestamp": "1705312205",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "invalid@nonexistent.com",
      "bounce_class": "10",
      "error_code": "550",
      "reason": "User Unknown",
      "raw_reason": "550 5.1.1 The email account that you tried to reach does not exist",
      "friendly_from": "you@example.com",
      "subject": "Your Order Confirmation",
      "sending_ip": "192.168.1.1"
    }
  }
}]
```

<Warning>
  Hard-bounced addresses are automatically suppressed and should be removed from your mailing lists. Continuing to send to these addresses damages your sender reputation.
</Warning>

### Soft Bounces

Soft bounces are temporary delivery failures. The email might be deliverable if retried later.

**Common causes:**

* Mailbox is full
* Server is temporarily unavailable
* Message is too large
* Rate limiting by the recipient server

Soft bounces use bounce classes in the 20–70 ranges:

```json theme={null}
[{
  "msys": {
    "message_event": {
      "type": "bounce",
      "timestamp": "1705312205",
      "transmission_id": "12345678",
      "message_id": "abcd-5678-efgh",
      "rcpt_to": "full@example.com",
      "bounce_class": "22",
      "error_code": "452",
      "reason": "Mailbox Full",
      "raw_reason": "452 4.2.2 Mailbox full",
      "friendly_from": "you@example.com",
      "subject": "Your Weekly Update",
      "sending_ip": "192.168.1.1"
    }
  }
}]
```

Lettr automatically retries soft bounces with exponential backoff. If delivery continues to fail, the address may eventually be suppressed.

## Automatic Handling

Lettr automatically handles bounces for you:

<Steps>
  <Step title="Tracks Bounces">
    All bounces are logged and tracked in your dashboard with full details.
  </Step>

  <Step title="Suppresses Hard Bounces">
    Hard-bounced addresses are automatically added to your suppression list.
  </Step>

  <Step title="Retries Soft Bounces">
    Soft bounces are automatically retried with exponential backoff.
  </Step>

  <Step title="Sends Webhooks">
    Bounce events are sent to your configured webhook endpoints in real-time.
  </Step>
</Steps>

## Bounce Classes

Lettr uses numeric bounce classes to categorize bounces. These correspond to the `bounce_class` field in webhook events:

| Bounce Class | Category                  | Type   | Description                          | Recommended Action |
| ------------ | ------------------------- | ------ | ------------------------------------ | ------------------ |
| `1`          | Undetermined              | Varies | Bounce could not be classified       | Review manually    |
| `10`         | Invalid Recipient         | Hard   | Email address does not exist         | Remove from list   |
| `20`         | Soft Bounce               | Soft   | General temporary failure            | Automatic retry    |
| `21`         | DNS Failure               | Soft   | Domain DNS lookup failed temporarily | Automatic retry    |
| `22`         | Mailbox Full              | Soft   | Recipient's mailbox is over quota    | Wait and retry     |
| `25`         | Admin Failure             | Soft   | Mailbox admin-related issue          | Review and retry   |
| `30`         | Generic Bounce (no RCPT)  | Hard   | Rejected without valid recipient     | Remove from list   |
| `40`         | Generic Bounce            | Soft   | Temporary generic failure            | Automatic retry    |
| `50`         | Mail Block (General)      | Soft   | Blocked by receiving server          | Investigate        |
| `51`         | Mail Block (Spam Related) | Soft   | Blocked as spam by receiving server  | Review content     |
| `52`         | Mail Block (Content)      | Soft   | Blocked due to content filtering     | Review content     |
| `60`         | Auto-Reply                | N/A    | Automatic reply (vacation, etc.)     | No action needed   |
| `70`         | Transient Failure         | Soft   | Temporary infrastructure issue       | Automatic retry    |
| `100`        | Relay Denied              | Hard   | Relay not permitted                  | Remove from list   |

<Note>
  Hard bounce classes (10, 30, 100) result in automatic suppression. Soft bounce classes may lead to suppression if delivery continues to fail after retries.
</Note>

## Handling Bounces

### Webhook Integration

The most reliable way to handle bounces is through webhooks. Set up a webhook endpoint to receive `message.bounce` events:

```javascript theme={null}
app.post('/webhooks/lettr', express.json(), async (req, res) => {
  res.sendStatus(200);

  for (const event of req.body) {
    const eventType = Object.keys(event.msys)[0];
    const data = event.msys[eventType];

    if (data.type === 'bounce') {
      const bounceClass = parseInt(data.bounce_class);

      // Log the bounce for analysis
      await logBounce({
        email: data.rcpt_to,
        bounceClass: bounceClass,
        errorCode: data.error_code,
        reason: data.raw_reason,
        timestamp: new Date(parseInt(data.timestamp) * 1000)
      });

      // Hard bounce classes: 10 (invalid recipient), 30 (no RCPT), 100 (relay denied)
      if ([10, 30, 100].includes(bounceClass)) {
        // Remove from mailing list immediately
        await db.subscribers.update({
          where: { email: data.rcpt_to },
          data: {
            status: 'bounced',
            bounceClass: bounceClass,
            lastBounceAt: new Date()
          }
        });

        // Notify relevant team members for spam-related blocks
        if ([50, 51, 52].includes(bounceClass)) {
          await notifyTeam({
            type: 'reputation_concern',
            email: data.rcpt_to,
            reason: data.raw_reason
          });
        }
      }
    }
  }
});
```

### Querying Email Events

You can retrieve events for specific emails using the API:

```bash theme={null}
curl "https://app.lettr.com/api/emails/12345678" \
  -H "Authorization: Bearer lttr_YOUR_API_KEY"
```

The response includes all events for that email, including bounces:

```json theme={null}
{
  "message": "Email retrieved successfully.",
  "data": {
    "results": [
      {
        "event_id": "evt-abc-123",
        "type": "injection",
        "timestamp": "2024-01-15T10:30:00.000Z",
        "request_id": "12345678",
        "rcpt_to": "recipient@example.com",
        "subject": "Your Order Confirmation",
        "friendly_from": "you@example.com"
      },
      {
        "event_id": "evt-def-456",
        "type": "bounce",
        "timestamp": "2024-01-15T10:30:05.000Z",
        "request_id": "12345678",
        "rcpt_to": "recipient@example.com",
        "reason": "User Unknown",
        "raw_reason": "550 5.1.1 The email account that you tried to reach does not exist",
        "error_code": "550"
      }
    ],
    "total_count": 2
  }
}
```

## Bounce Rate Guidelines

Monitor your bounce rates to maintain good deliverability:

| Bounce Rate | Status   | Action                     |
| ----------- | -------- | -------------------------- |
| \< 2%       | Healthy  | Maintain current practices |
| 2-5%        | Warning  | Review list hygiene        |
| > 5%        | Critical | Clean list immediately     |

<Note>
  ISPs closely monitor bounce rates. A bounce rate consistently above 5% can result in your emails being blocked or filtered to spam.
</Note>

## Preventing Bounces

<AccordionGroup>
  <Accordion title="Use Double Opt-In">
    Require subscribers to confirm their email address before adding them to your list. This ensures addresses are valid and owned by the person who signed up.
  </Accordion>

  <Accordion title="Validate on Collection">
    Use email validation at the point of collection. Check for common typos, invalid formats, and disposable email addresses.
  </Accordion>

  <Accordion title="Clean Lists Regularly">
    Remove subscribers who haven't engaged in 6-12 months. Inactive addresses are more likely to bounce over time.
  </Accordion>

  <Accordion title="Process Bounces Immediately">
    Set up webhook handlers to process bounce events in real-time. Don't wait for batch processing.
  </Accordion>

  <Accordion title="Monitor Engagement">
    Track open and click rates. Low engagement often precedes bounces as users abandon email addresses.
  </Accordion>
</AccordionGroup>

## Delayed Delivery

Sometimes emails are temporarily delayed rather than bounced. These are tracked as `message.delay` events:

```json theme={null}
[{
  "msys": {
    "message_event": {
      "type": "delay",
      "timestamp": "1705312205",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "recipient@example.com",
      "reason": "421 Server temporarily unavailable",
      "friendly_from": "you@example.com",
      "subject": "Your Order Confirmation",
      "sending_ip": "192.168.1.1",
      "num_retries": "1"
    }
  }
}]
```

Delayed emails are automatically retried. You don't need to take action unless delays persist, which may indicate a delivery issue.

## Out-of-Band Bounces

Some bounces arrive asynchronously after the initial delivery attempt appears successful. These are called out-of-band (OOB) bounces and are tracked as `message.out_of_band` events:

```json theme={null}
[{
  "msys": {
    "message_event": {
      "type": "out_of_band",
      "timestamp": "1705314000",
      "transmission_id": "12345678",
      "message_id": "abcd-1234-efgh",
      "rcpt_to": "recipient@example.com",
      "bounce_class": "22",
      "reason": "Mailbox Full",
      "raw_reason": "552 5.2.2 Mailbox full",
      "error_code": "552"
    }
  }
}]
```

Handle OOB bounces the same way as regular bounces — update your records and suppress hard bounces.

## Related Topics

<CardGroup cols={2}>
  <Card title="Webhook Events" icon="bell" href="/learn/webhooks/event-types">
    Complete reference for all webhook event types
  </Card>

  <Card title="Handling Webhooks" icon="code" href="/learn/webhooks/handling">
    Best practices for webhook implementation
  </Card>

  <Card title="Sending Best Practices" icon="paper-plane" href="/learn/sending/best-practices">
    Tips for improving deliverability
  </Card>

  <Card title="Analytics" icon="chart-line" href="/learn/analytics/introduction">
    Monitor your email performance
  </Card>
</CardGroup>
