# Replay and dead letters

Find out why an event failed, then redeliver it one at a time or in bulk.

Failures are normal: a deploy, an outage, a bug in a handler. What matters is that nothing is silently lost and that recovery is quick. Ensue keeps every event, so once the cause is fixed you can send it again.

## Event statuses

| Status | Meaning |
|---|---|
| `queued` | Waiting for its first attempt, or for its [delay](/gateway/flows/). |
| `retrying` | An attempt failed; the next one is scheduled. |
| `delivered` | Your service answered `2xx`. |
| `failed` | Gave up. This is a **dead letter**. |
| `cancelled` | Stopped by a person or the API before it finished. |
| `compacted` | Merged into a later event that carries it. See [Subscriptions and compaction](/agents/subscriptions/). |

## Dead letters

An event that exhausts its retries, or gets an answer that cannot be retried, becomes `failed`. It records why it gave up, for example a `500` on every attempt or a connection timeout, so you do not have to reproduce the failure. Dead letters stay until the plan's [retention](/gateway/usage-and-retention/) removes them.

## Retry, redrive and replay

| Action | Use it for | What happens |
|---|---|---|
| **Retry** | An event that is `failed` or `retrying` | Sends it again now, as a single extra attempt. |
| **Redrive** | A dead letter (`failed` only) | Starts it over, with the destination's delivery policy applying again. |
| **Replay** | Sending a **copy**, optionally to a different destination or inbox | Creates a new event for the same request, delivered as it arrived (no filter or transformation), with the original's idempotency key. It is not counted against your quota again. |
| **Cancel** | An event that is `queued` or `retrying` | Stops it. |

If the destination's [circuit breaker](/gateway/circuit-breaker/) is open, a retry would only wait behind it, so retrying a single event returns `409 breaker_open`, and a bulk retry reports how many were `held_by_breaker`. Reset or probe the breaker first.

Every action is recorded on the event's [timeline](/gateway/events-and-timeline/) with who or what did it, so history stays complete.

### One event

Open the event and choose the action. Over the API:

```bash
curl -X POST https://api.getensue.com/v1/events/evt_…/redrive \
  -H "authorization: Bearer ens_<key>"
```

### Many events

Filter the **Events** list by destination, status or time window, then select them and choose an action. Over the API, one call applies an action to up to 500 events:

```bash
curl -X POST https://api.getensue.com/v1/events/bulk \
  -H "authorization: Bearer ens_<key>" -H "content-type: application/json" \
  -d '{ "action": "redrive", "ids": ["evt_…", "evt_…"] }'
```

`action` is `retry`, `redrive`, `replay` or `cancel`. For `replay`, add `destination` to send the copies somewhere else.

## Replay safely

Replays can run a handler twice. A replay carries `idempotency-key` equal to the original event's, and `x-ensue-replay-of` naming it, so a handler that keys on the event ID will recognise it.

> [!IMPORTANT]
> Make your consumer idempotent by keying on `x-ensue-event-id` (or `idempotency-key`). Delivery is at least once, and replays are deliberate repeats.

## After an outage

1. Fix the destination. If the [circuit breaker](/gateway/circuit-breaker/) is open, reset it.
2. In **Events**, filter to `failed` for that destination and time window.
3. Redrive them in bulk, then watch the destination's [metrics](/gateway/events-and-timeline/) while the queue drains at its [rate limit](/gateway/delivery-policy/).