Retries + dead-letter
Outbound deliveries fail. The relay decides how to react based on the receiver's response.
Decision table
| Outcome | Action | Counter |
|---|---|---|
| 2xx | delivery success | consecutive_failures resets to 0 |
| 4xx | delivery dead, no retries | consecutive_failures += 1 |
| 5xx / timeout / connection error | retry with backoff; dead at attempt 6 | consecutive_failures += 1 per attempt |
Backoff schedule
Five retries after the first attempt, spaced exponentially. Total worst-case time-to-dead is ~8 hours.
attempt 1 -> +30s -> attempt 2
attempt 2 -> +2m -> attempt 3
attempt 3 -> +10m -> attempt 4
attempt 4 -> +1h -> attempt 5
attempt 5 -> +6h -> attempt 6
attempt 6 -> deadCircuit breaker
Eight consecutive failures on a subscription auto-pauses it. New events that would match a paused subscription are still ingested, but no delivery jobs are dispatched until you POST /v1/subscriptions/{id}/resume. Resuming resets consecutive_failures to 0.
4xx counts toward the breaker too. A subscriber returning 400 for every request will eventually get itself paused; that's by design.
Dead-letter queue
Deliveries with status=dead land in the dead-letter view at GET /v1/dead-letters. Replay one with POST /v1/dead-letters/{id}/replay — it moves back to pending and a fresh attempt is queued. The original attempt history stays intact for audit.