# Retries & delivery

> How Inboundr retries failed webhook deliveries, and how to make your handler robust.

Inboundr retries failed deliveries automatically so a brief outage on your side
doesn't drop mail.

## What counts as a failure

A delivery attempt fails if the endpoint:

- returns a non-`2xx` status,
- returns a `3xx` redirect (redirects are **not** followed),
- times out (default 10s, configurable 1–30s), or
- can't be reached.

## Backoff schedule

Failed attempts are retried with exponential backoff until they succeed or reach
the endpoint's `maxAttempts` (default 5, max 10):

| Attempt | Delay after previous |
| --- | --- |
| 1 | immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5+ | 2 hours |

After the final attempt, the delivery is marked **dead** and surfaced in the
console so you can inspect the response and retry manually.

## Delivery status

Each email's overall delivery status rolls up its attempts:

| Status | Meaning |
| --- | --- |
| `delivered` | At least one endpoint accepted it and none are failing. |
| `pending` | Attempts are still in flight. |
| `failed` | One or more deliveries failed or went dead. |

## Building a robust handler

<AccordionGroup>
  <Accordion title="Be idempotent" icon="fingerprint">
    A delivery may arrive more than once (a retry can fire after your handler
    succeeded but before the response reached us). De-duplicate on the email
    `id` or the `X-Inboundr-Delivery-Id` header.
  </Accordion>
  <Accordion title="Acknowledge quickly" icon="bolt">
    Return `200` as soon as you've safely stored the event, then do heavy work
    asynchronously. Long processing risks a timeout, which triggers a retry.
  </Accordion>
  <Accordion title="Verify before trusting" icon="shield-check">
    Always [verify the signature](/webhooks/verifying-signatures) before acting
    on a payload.
  </Accordion>
</AccordionGroup>
