Replay dead letters
Copy page
This guide replays outbox messages through the POST /admin/replay route.
It assumes that you know how QueueBox marks a message dead.
If not, read Dead letters first.
Choose the route or the SQL
Section titled “Choose the route or the SQL”POST /admin/replay |
SQL | |
|---|---|---|
| Selects | sent and dead outbox rows |
Any row that your WHERE clause names |
| Filters | ids, topic, topics, destination, creation time | Anything |
| Resolves a destination to its topics | Yes, through the configured routes | No |
| Inbox rows | No | Yes |
| Needs | admin.enabled and admin credentials |
Write access to the database |
| Leaves a record | A log line with the filter and the count | Your own audit |
Use the route when an operator replays by destination or by time without database access. Use the SQL for an inbox row, or for a selection that the route filters cannot express.
Before you start
Section titled “Before you start”- Correct the cause of the failure. A replay against a destination that is still broken produces new dead messages.
- Enable the admin routes with authentication. They are off by default.
# fragmentadmin: enabled: true auth: type: bearer token: file:/run/secrets/queuebox-admin-tokenThe admin routes are on the management port when server.managementPort is set.
Never publish /admin through the ingress.
See Security.
Replay dead messages by id
Section titled “Replay dead messages by id”-
List the dead messages that you want to replay.
SELECT id, topic, key, attempt, updated_at, last_errorFROM outboxWHERE state = 'dead' AND topic = 'order.created'ORDER BY updated_at DESC; -
Send their ids to the route.
Terminal window curl -X POST http://localhost:9090/admin/replay \-H "Authorization: Bearer $ADMIN_TOKEN" \-H "Content-Type: application/json" \-d '{"ids": ["3f2b1c1e-8d4a-4c1b-9a53-2d0f6f1e7a10", "9b7e5d2c-1a3f-4e8b-b6c4-0f1e2d3c4b5a"]}' -
Read the count in the answer.
{"moved": 2} -
Watch the rows reach
sent.SELECT id, state, attempt, last_errorFROM outboxWHERE id IN ('3f2b1c1e-8d4a-4c1b-9a53-2d0f6f1e7a10', '9b7e5d2c-1a3f-4e8b-b6c4-0f1e2d3c4b5a');
Replay everything for one destination in a time window
Section titled “Replay everything for one destination in a time window”Use this form after a destination lost data and needs every message of a period again.
curl -X POST http://localhost:9090/admin/replay \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "destination": "billing-service", "createdAfter": "2026-09-24T08:00:00Z", "createdBefore": "2026-09-24T12:00:00Z" }'QueueBox resolves the destination to the topics that route to it.
It uses the configured routes and their first-match rule, exactly as the poller does.
It then moves the sent and dead rows of those topics in the window.
The filter
Section titled “The filter”Every field is optional, but the request needs at least one. The route combines the fields with AND.
| Field | Type | Selects |
|---|---|---|
ids |
Array of UUID strings | Rows with these id values. |
topic |
String | Rows with this exact topic. |
topics |
Array of strings | Rows with any of these topics. |
destination |
String | Rows whose topic routes to this destination name. |
createdAfter |
ISO 8601 instant | Rows with created_at strictly after this time. |
createdBefore |
ISO 8601 instant | Rows with created_at strictly before this time. |
- The route always adds
state IN ('sent', 'dead'). No filter can move apendingorprocessingrow. topicstogether withdestinationnarrows the destination’s topics to the ones you list. It never adds a topic. When the two sets do not overlap, the route moves nothing.- A destination that no topic routes to moves nothing.
- The route ignores an unknown field. Check the spelling of each field name, because a misspelt field filters nothing.
What a replay changes
Section titled “What a replay changes”For each selected row, the replay sets:
statetopending,attemptto 0,scheduled_atto now,last_error,claimed_at,claim_tokenandlease_expires_atto null.
The row keeps its id, its sequence, its payload and its headers.
The poller claims it on its next cycle and delivers it to the destination that its topic routes to now.
- Order. A replayed row with a
keybecomes the head of its key again. It holds back later pending rows of that key until it is sent or dead. It reaches the destination after the rows that passed it. See Ordering. - Identity. A replayed row keeps its
X-Message-Id, because it is the same outbox row. A receiver that deduplicates onX-Message-Iddiscards the replay of a row that it already stored. Clear the receipt at the receiver first if the receiver must apply the message again.
The answers
Section titled “The answers”| Status | Body | Meaning |
|---|---|---|
| 200 | {"moved": n} |
The replay moved n rows. 0 means that nothing matched. |
| 400 | {"error": "A replay needs at least one filter."} |
The request has no filter. |
| 400 | {"error": "Invalid request: ..."} |
The body is not valid JSON, or a field has the wrong type. |
| 401 | {"error": "..."} |
The credentials are missing or wrong. |
| 413 | {"error": "Request body exceeds ... bytes"} |
The body is larger than admin.maxPayloadBytes (default 65536). |
| 404 | None | admin.enabled is false, or the request went to the data port while a management port is set. |
QueueBox logs every replay with its filter and the count. An operator who loses the answer can find the replay in the log.
Replay a dead inbox message
Section titled “Replay a dead inbox message”The route replays outbox rows only. To requeue a dead inbox row, use the SQL in Dead letters.