Dead Letter Queues — Where Failed Messages Go to Be Studied
One Hoplink link points at a page whose title contains broken characters. The "fetch-title" job crashes on it every time. Retrying cannot help: the message is simply bad. Without a plan, it is retried until the limit, then thrown away, and nobody learns why the preview is missing. A dead letter queue (DLQ) keeps it instead.
The rule
Retry what might pass; park what keeps failing; then look.
Every consumer has a retry budget, max_retries (3 by default). Once a message has failed that many times:
- Without a DLQ, it is discarded. The failure disappears.
- With a DLQ, it is moved to another queue you name, where it waits intact for you.
{
"queues": {
"consumers": [{
"queue": "jobs",
"max_retries": 5,
"dead_letter_queue": "jobs-dlq"
}]
}
}Create the second queue first with wrangler queues create jobs-dlq. A DLQ is an ordinary queue, so you choose what happens next:
- Inspect. Look at the messages in the dashboard to see the body and understand the failure.
- Alert. Attach a small consumer to
jobs-dlqthat records each failure in D1 and notifies the team. - Replay. After fixing the bug, send the parked messages back to
jobsto run again.
Deciding when to give up belongs in the consumer too. Some errors are transient — a timeout, a 503, a 429 — and retrying later can succeed. Others are permanent — a malformed body, a link that no longer exists — and retrying only wastes the budget. For permanent errors, record the problem and ack() so the message stops cycling, or let it reach the DLQ where a person will see it.
The shortcut
For every error in a consumer, sort it with one question: "Could the exact same message succeed later?" Yes → retry() with backoff. No → handle it now (record and ack) or let it go to the DLQ. Every queue that does must-happen work gets a DLQ.
First delivery
The consumer throws while parsing the title. It is retried.
Trap: a DLQ that nobody watches. Messages pile up quietly, and the failure is as invisible as if they had been deleted.
Drills
Answer fast — trust the rule.
A message fails more times than max_retries and the consumer has no DLQ. What happens?
Which error is most likely permanent, so retrying will not help?
What is a dead letter queue, technically?
After fixing the bug behind 200 parked messages, what should you do?
Which setup gives the team the best chance of noticing failures?
Remember
- Retry what might pass; park what keeps failing; then look.
- Shortcut: "could the same message succeed later?" Yes → retry with backoff; no → record, ack or DLQ.
- Trap: a DLQ nobody watches is just a slower delete.
