A dead-letter queue (DLQ) holds messages that were received too many times without being acknowledged, so one message that always fails doesn’t keep being retried. It’s an ordinary queue that another queue’s redrive policy names.
Set a redrive policy
Create the dead-letter queue first, of the same type, then name it in the source queue’s
redrive, at creation or with UpdateQueue:
curl -sS -X PATCH "$BASE/queues/$QUEUE_ID" \
-H "Authorization: Bearer $EZGH_API_KEY" \
-H "Content-Type: application/json" \
-d "{ \"redrive\": { \"deadLetterQueue\": \"$DLQ_ID\", \"maxReceiveCount\": 5 } }"deadLetterQueueis the DLQ’s ID: a queue in the same project, of the same type (400 queue_type_mismatchotherwise), and not the queue itself.maxReceiveCountis 1 to 1,000."redrive": nullremoves the policy.
Several queues can share one dead-letter queue. A queue that’s another queue’s DLQ can’t be deleted
(409 queue_in_use) until that policy is removed.
With the CLI: ezgh queues update orders --dlq orders-dlq --max-receives 5, and --no-dlq to
remove the policy.
When a message moves
A message is delivered at most maxReceiveCount times. When a receive, or a
push subscription delivery, finds a message that has already been received that
many times, it moves the message to the DLQ instead of delivering it. Every push delivery attempt
counts as a receive.
In the dead-letter queue the message:
- keeps its
messageId, body and attributes, and gains two attributes:ezgh.sourceQueue, the source queue’s ID, andezgh.deadLetteredAt, when it moved (RFC 3339); - is visible at once, with
receiveCountback to 0; - keeps its original
sentAt, and expires atsentAtplus the DLQ’s retention. Give the DLQ a longer retention than its source queues, or messages that took long to fail expire soon after they arrive, or at once.
On a FIFO queue, a dead-lettered message no longer holds up its group: the group continues with its next message.
Inspect messages
PeekMessages lists a queue’s messages in the order they were sent, without receiving them: nothing is claimed or changed, and no receipt handles are returned.
curl -sS "$BASE/queues/$DLQ_ID/messages?limit=20" -H "Authorization: Bearer $EZGH_API_KEY"{
"messages": [
{
"messageId": "01999f3d-02b4-7c1d-8e2f-3a4b5c6d7e8f",
"body": "{\"orderId\": \"o-123\"}",
"attributes": {
"source": "checkout",
"ezgh.sourceQueue": "01999f3c-7a1e-7b2a-9c4d-5e6f7a8b9c0d",
"ezgh.deadLetteredAt": "2026-10-13T17:05:12Z"
},
"sentAt": "2026-10-13T17:02:40Z",
"receiveCount": 0,
"state": "visible"
}
],
"nextCursor": null
}limitis 1 to 100 (20 by default); follownextCursorfor more.stateisvisible,inFlightordelayed; FIFO messages addgroupIdandseq.- Peeking needs
queues.messages.peek, whichQueuesReadOnlyAccessandReadOnlyAccessinclude, and is recorded in Trails as a data event. A queue can be peeked 10 times a second. - It works on any queue, but suits dead-letter queues and debugging. Consumers receive instead.
Redrive messages back
Once you’ve fixed what made them fail, StartRedrive on the dead-letter queue moves its messages back:
curl -sS -X POST "$BASE/queues/$DLQ_ID/redrives" \
-H "Authorization: Bearer $EZGH_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "01999f60-4d5e-7f6a-8b9c-0d1e2f3a4b5c",
"queueId": "01999f3b-9e8d-7c6b-a5f4-e3d2c1b0a9f8",
"destinationQueueId": null,
"status": "running",
"moved": 0,
"skipped": 0,
"startedAt": "2026-10-13T18:00:00Z",
"finishedAt": null
}- Which messages: those dead-lettered before the redrive started. Messages that arrive later stay for the next redrive.
- Where: each back to the queue it came from, or every one to
destinationQueueId, a queue in the same project and of the same type. Naming a destination needsqueues.redrives.starton it as well as on the DLQ. - Skipped, and left in the DLQ: messages in flight in the DLQ, and messages whose source queue
was deleted.
skippedcounts them. - Moved messages get
receiveCount0 and lose theezgh.attributes. They keep their originalsentAt, and expire atsentAtplus their new queue’s retention, so a message older than that retention expires as soon as it moves. - Redrives run in the background at about 500 messages a second. Follow one with
GetRedrive until
statusisdoneandfinishedAtis set; ListRedrives lists a DLQ’s redrives, newest first. - A DLQ runs one redrive at a time: another answers
409 redrive_active.
With the CLI: ezgh queues peek orders-dlq to look at its messages,
ezgh queues redrive start orders-dlq [--to <queue>] to move them, and
ezgh queues redrive get orders-dlq <redrive-id> to follow one.
To get rid of a DLQ’s messages instead, purge it.