Skip to content

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.

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.

  • 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.
# fragment
admin:
enabled: true
auth:
type: bearer
token: file:/run/secrets/queuebox-admin-token

The admin routes are on the management port when server.managementPort is set. Never publish /admin through the ingress. See Security.

  1. List the dead messages that you want to replay.

    SELECT id, topic, key, attempt, updated_at, last_error
    FROM outbox
    WHERE state = 'dead' AND topic = 'order.created'
    ORDER BY updated_at DESC;
  2. 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"]}'
  3. Read the count in the answer.

    {"moved": 2}
  4. Watch the rows reach sent.

    SELECT id, state, attempt, last_error
    FROM outbox
    WHERE 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.

Terminal window
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.

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 a pending or processing row.
  • topics together with destination narrows 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.

For each selected row, the replay sets:

  • state to pending,
  • attempt to 0,
  • scheduled_at to now,
  • last_error, claimed_at, claim_token and lease_expires_at to 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 key becomes 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 on X-Message-Id discards the replay of a row that it already stored. Clear the receipt at the receiver first if the receiver must apply the message again.
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.

The route replays outbox rows only. To requeue a dead inbox row, use the SQL in Dead letters.