Retries and queue design
Delay retries a failed job on a fixed list of delays that you set per queue. This page helps you pick those delays and decide how to split jobs across queues. How retries work is on Jobs, and the defaults are on Queues.
What to keep in mind
- Every failure is retried. 3xx, 4xx (even 400, 404 and 422), 5xx, timeouts, network errors and failed assertions all use the same retry list. Only the monthly request limit stops retries.
- The list is the schedule. There’s no exponential backoff and no jitter, and your endpoint’s
Retry-Afterheader is ignored. - Every attempt is a request. A job that fails every attempt on the default settings uses 4 requests from your monthly quota.
- Each retry is a new job with a new id. Match callbacks by your own reference (how).
Choose retry delays
A job gets 1 attempt plus one per delay. The time it waits in total is the sum of the delays.
| Profile | Retry delays (seconds) | Attempts | Total wait | Good for |
|---|---|---|---|---|
| Quick | 10, 60, 300 | 4 | 6 min 10 s | Internal services that recover fast |
| Default | 60, 1800, 10800 | 4 | 3 h 31 min | Most webhooks and APIs |
| Patient | 300, 3600, 21600 | 4 | 7 h 5 min | Third-party APIs that recover slowly |
| Long | 60, 600, 3600, 14400, 43200 | 6 | 17 h 11 min | Jobs that must get through a long outage |
| None | Retries off | 1 | 0 | Endpoints that must not run twice |
The real time to the last attempt is the total wait plus how long each attempt took (at most its timeout).
- Start with a short delay to absorb a blip, such as a deploy or a dropped connection, then lengthen.
- Make the total wait longer than the outages you expect from that endpoint.
- A queue allows up to 10 retries of up to 86,400 seconds (24 hours) each, so the longest possible chain waits 10 days.
Stop retries you don’t want
Because 4xx responses are retried, a request that can never succeed (a deleted user, an invalid payload) fails on every attempt and ends up in the dead letter queue.
- When your endpoint knows a request is permanently invalid, log it and return a 2xx so Delay stops. The job then shows as Completed, so keep the reason on your side.
- Return a 5xx only for problems that a retry can fix.
- If your endpoint rate-limits Delay with 429, the retry still comes after the next delay in the list. Use Max concurrent jobs and longer delays instead of relying on
Retry-After. - Assertions turn a 2xx into a failure, and that failure is retried too.
Make retries safe
A retry sends exactly the same URL, method, headers and payload. Delay adds no job id or idempotency header, so your endpoint can’t tell a first attempt from a retry on its own.
- Put your own unique key in the payload, for example
"payment_id": "pay_123", and skip work you’ve already done for that key. - A timeout means Delay stopped waiting, not that your endpoint stopped working. The work may have finished, and the retry repeats it. Keep requests fast, or acknowledge first and do the work in the background (endpoint design).
- Replays and bulk retries send the same payload again, so the same key protects those too.
- If an endpoint can’t be made idempotent, turn retries off for its queue. A failure then goes straight to the dead letter queue, and you decide whether to replay it.
Retries and alerts
With Enable email alerts for job failures on, a queue alerts once per job: a warning when the job’s first attempt fails and a retry is scheduled, and a failure alert when it’s dead-lettered. Email goes to every team member, plus Slack or Discord if your team uses them. During an outage on a busy queue, that’s an alert for every failing job. For high-volume queues where single failures don’t matter, consider turning alerts off and checking Dead letters instead. Setup: Alerts.
Split jobs across queues
Settings live on the queue, so group jobs that need the same settings. That usually means one queue per destination:
| Queue | Request timeout | Retry delays | Max concurrent jobs |
|---|---|---|---|
stripe-webhooks | 30 s | 60, 1800, 10800 | 3 |
email-notifications | 10 s | 60, 600 | 5 |
report-exports | 300 s | 300, 3600 | 2 |
analytics-events | 10 s | Retries off | Unlimited |
Give jobs their own queue when they:
- go to an endpoint with a different speed or reliability
- need a longer timeout than the rest
- call a rate-limited API and need Max concurrent jobs
- need their own dead letter retention or alerts
Jobs that go to the same endpoint with the same expectations can share a queue.
Name queues
- Use lowercase, hyphenated names for the destination or purpose:
stripe-webhooks,email-notifications. - The API matches the name you send. A typo creates a new queue with default settings, so keep each name in one constant in your code.
- If a queue needs non-default settings, create it in the dashboard before its first job (Set up a queue).
- To keep staging and production apart, see Teams and roles.
Keep one action per job
Send one job per webhook, email or API call, not one job that loops through 100 of them. A retry repeats the whole job, so a batch that fails at item 60 sends items 1 to 59 again. Single jobs can be retried, replayed and inspected on their own.
Next steps
- Queues: every queue setting with its default and limits.
- Replay failed jobs: recover jobs that ran out of retries.
- Write endpoints Delay can call: status codes, timeouts and idempotency.