Every RabbitMQ team has a way to get messages out of a dead-letter queue. Usually it is whichever tool was at hand the first time it happened. This page puts the usual candidates next to each other, with the trade-offs stated plainly, so the choice is a choice and not an accident.
x-death.x-death unless something removes it, and consumers that count retries reject it on arrival.Ships with RabbitMQ, enabled with rabbitmq-plugins enable rabbitmq_shovel rabbitmq_shovel_management. A dynamic shovel consumes from a source queue and publishes to a destination, on the same broker or another one. With ack-mode: on-confirm it is loss-free; with src-delete-after: queue-length it stops after the messages that were there when it started. The Move messages button is a shovel with defaults.
Strong: zero code, runs inside the broker, survives your laptop closing, works across brokers. Weak: it moves everything to one destination. No selection, no per-message original route, x-death stays on, no rate limit, no record beyond the broker log. Right tool when you trust every message in the queue and they all belong in the same place.
The CLI of the management plugin (the classic Python script, or the newer v2 rewrite). rabbitmqadmin get queue=orders.dlq count=50 shows messages; with the right ackmode it consumes them, and rabbitmqadmin publish puts a message somewhere else. Chaining the two in a shell loop is a replay of sorts.
Strong: already installed wherever the management plugin is, scriptable, good for looking at a handful of messages. Weak: get goes through the HTTP API, which is fine for dozens and slow for thousands; the get-then-publish loop is two separate operations with no confirm-before-ack between them, so a crash in the middle loses or duplicates; headers and properties have to be carried over by hand in your script, including removing x-death; and there is no audit unless you keep the shell history. Right tool for inspecting, not for moving.
A small Go tool that writes the messages of a queue to files, one file per message, optionally with properties and headers as JSON next to the body. It fetches with basic.get and, depending on the flag, either leaves messages in the queue or acknowledges them.
Strong: the simplest way to get a snapshot of a DLQ onto disk for analysis, for a bug report, or as a backup before you purge. Weak: it is a dump tool. Putting messages back is a separate step with a separate tool, headers included; nothing about routing, throttling or selection; the file-per-message layout does not scale to tens of thousands. Right tool for "save these before we do anything else".
A Go CLI that taps exchanges, subscribes to queues, publishes, and can record messages to files and replay them with rabtap pub. It understands the message format it saved, so properties and headers survive the round trip, and it can replay with timing. Closer to a Swiss-army knife for AMQP than a DLQ tool.
Strong: scriptable, keeps message metadata, can replay a recorded set to an exchange with the original routing key, good for reproducing traffic in a test environment. Weak: it is a command line; selecting the 40 out of 300 means writing the filter yourself, the replay is a replay of a recording rather than of what is in the queue right now, and there is no audit trail beyond your shell. Right tool for engineers who like the terminal and want repeatable, scripted replays.
Twenty lines of pika, the Java client or Spring AMQP: basic.get without auto-ack, publish with confirms and mandatory, ack after the confirm, nack-requeue what you skip, strip the death headers. The replay article has the code and the four mistakes every first version makes.
Strong: does exactly what you need, no new dependency, per-message original route from x-death. Weak: written at 3 a.m. by the person on call, rarely with confirms, rarely with a log, rarely reviewed, and rediscovered by the next person on call. Right tool once it has been written properly and checked in.
A UI that runs next to the broker and does the script above with a record of everything: browse the DLQ without consuming, see why each message died, group by cause, select some or the first N, replay to the original exchange and routing key or to a queue, with x-death and retry headers stripped, publisher confirms, a rate limit and a stop button, and an audit log per replay with the per-message outcome. Edit a payload before replay, export a selection to a file, discard or purge with a reason.
Strong: the six questions above answered by default, by anyone on the team, not only by whoever wrote the script. Weak: another container to run, and the source is not open. Community is free for one cluster; several clusters, roles and alerting are paid.
| Shovel / Move | rabbitmqadmin | dump-queue | rabtap | Own script | Warren | |
|---|---|---|---|---|---|---|
| Select which messages | no | by count | by count | with your filter | if written | yes, by cause or hand |
| Original route per message | no | if scripted | no | from recording | if written | yes, from x-death |
| Strip x-death / retry headers | no | if scripted | no | no | if written | yes |
| Loss-free (confirm before ack) | with on-confirm | no | n/a | partly | if written | yes |
| Rate limit, stop | no | sleep in loop | n/a | replay timing | if written | yes |
| Edit before replay | no | by hand | edit the file | edit the file | if written | yes, marked in the log |
| Who, when, what, outcome | broker log | shell history | no | shell history | if logged | audit log per message |
| Needs the management plugin | yes | yes | no | no | no | yes |
| Runs where | in the broker | your shell | your shell | your shell | your shell | a container next to the broker |
| Cost | included | included | free | free | your time | free for one cluster |
Most teams end up with two of these, not one. A dump tool or rabtap for snapshots and for reproducing traffic in a test environment, and either a checked-in script or Warren for the actual replay in production. The shovel stays for the case it is built for: everything from here to there, now.
Whatever you pick, decide before the incident, write it in the runbook, and make sure the person on call at 3 a.m. can find it.