# Delivery policy

Control retries, backoff, timeouts and delivery rate for each destination.

Each URL destination has a delivery policy: how long to wait for an answer, which answers are worth retrying, how long to wait between tries, and how fast to send. Defaults are sensible; change them when your service has its own limits.

## What counts as a failure

Ensue retries answers that might succeed later, and fails fast on ones that never will.

| Response | Result |
|---|---|
| `2xx` | Delivered. |
| `408`, `409`, `425`, `429`, `5xx`, timeout, network error | Retried. |
| Other `4xx` (for example `400`, `401`, `404`, `422`) | Failed at once. Your service rejected the request; trying again would not help. |
| `3xx` | Failed. Redirects are never followed. |
| `Retry-After: -1` or `x-ensue-retry: never` | Failed at once, on any non-`2xx` answer. Your service can say "never retry this". |

Change which answers retry with `retry.retry_on`, an array drawn from `5xx`, `429`, `408`, `409`, `425`, `timeout` and `network`, or a specific status code.

## Retries and backoff

By default a failed delivery is retried up to **7 times**, after 5 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 2 hours and 4 hours. That is eight attempts across about seven hours. Each wait is spread randomly by up to half in either direction so retries from many events don't land together.

```json
{
  "max_attempts": 12,
  "retry": { "schedule": "exponential", "interval_seconds": 30, "max_window_hours": 24 }
}
```

| Setting | Meaning |
|---|---|
| `max_attempts` | Total attempts before the event becomes a [dead letter](/gateway/replay-and-dead-letters/). |
| `retry.schedule` | `default` (the schedule above), `linear` (the same wait every time) or `exponential` (doubling from `interval_seconds`, capped at one day). |
| `retry.interval_seconds` | The base wait for `linear` and `exponential`. |
| `retry.max_window_hours` | A cut-off after which an event stops retrying, whatever attempts remain. |
| `retry.retry_on` | Which responses retry. |

### Retry-After

If your service answers `429` or `503` with a `Retry-After` header, in seconds or as an HTTP date, Ensue honours it, up to four hours. The header can only push a retry **later**, never earlier.

## Timeouts

`timeout_ms` is how long Ensue waits for an answer: 1,000 to 30,000 milliseconds, default 15,000. If your handler needs longer, accept the event quickly and process it in the background, or use a [deferred acknowledgement](/agents/inbox-and-leases/).

## Rate and concurrency

| Setting | Meaning |
|---|---|
| `max_in_flight` | The most deliveries running at once. |
| `rate_per_second` with `rate_period` | The most deliveries started in each `second`, `minute` or `hour`. |

Events queue in Ensue and drain at the rate you set instead of failing and retrying against a downstream limit. For per-tenant fairness, use [delivery groups](/gateway/delivery-groups/).

> [!TIP]
> Set a rate when a downstream API has its own limits. A queue that drains at 5 a second is far cheaper than a thousand retries against a `429`.

## Pausing a destination

Pausing holds every event for that destination until you resume. Nothing is lost, and nothing is retried while paused.

## Manual retries

Any event can be retried by hand, even after it has given up. A manual retry sends **one** attempt immediately and is not counted against the schedule. See [Replay and dead letters](/gateway/replay-and-dead-letters/).