Dead Letter Queue
Route failed KubeMQ queue messages to a dead-letter queue using the Elixir SDK.
Overview
A dead-letter queue (DLQ) gives a poison message somewhere to go instead of looping through consumers forever. When a message keeps failing — a malformed payload, a downstream outage, a handler bug — retrying it forever wastes consumer cycles and blocks everything behind it. A DLQ takes that decision out of your hands: past a set number of failed attempts, the broker diverts the message to a separate channel instead of retrying it again.
Routing runs on two settings inside KubeMQ.QueuePolicy: max_receive_count and max_receive_queue. Every failed delivery — a message left unacknowledged, or an expired visibility window — increments the receive count; past the threshold, the broker reroutes the message to the DLQ instead of redelivering it. The DLQ itself is an ordinary queue, consumed like any other channel.
Gotchas: the DLQ doesn't drain itself — a dedicated consumer must watch it. The count increments on any failed delivery, not just deliberate rejections — a slow consumer that lets the visibility window lapse counts the same as an explicit nack. A typo in the DLQ channel name quietly creates an unrelated channel instead of failing loudly.
Prerequisites
- KubeMQ server running on
localhost:50000 - Elixir SDK installed (
{:kubemq, "~> 1.0"}in mix.exs)
Code
channel = "elixir-queues.dead-letter-queue"
dlq_channel = "elixir-queues.dead-letter-queue.dlq"
{:ok, client} = KubeMQ.Client.start_link(address: "localhost:50000", client_id: "elixir-queue-dlq")
msg = KubeMQ.QueueMessage.new(
channel: channel,
body: "Process me (max 2 attempts)",
policy: KubeMQ.QueuePolicy.new(
max_receive_count: 2,
max_receive_queue: dlq_channel
)
)
{:ok, _} = KubeMQ.Client.send_queue_message(client, msg)
IO.puts("Sent message with max_receive_count=2, DLQ='#{dlq_channel}'")
IO.puts("After exceeding max receives, message moves to '#{dlq_channel}'")
IO.puts("Checking DLQ channel...")
case KubeMQ.Client.receive_queue_messages(client, dlq_channel,
max_messages: 10,
wait_timeout: 3_000
) do
{:ok, result} ->
IO.puts("DLQ messages: #{result.messages_received}")
Enum.each(result.messages, fn m ->
IO.puts(" Body: #{m.body}")
if m.attributes do
IO.puts(" Re-routed: #{m.attributes.re_routed}")
IO.puts(" From: #{m.attributes.re_routed_from_queue}")
end
end)
{:error, _} ->
IO.puts("No DLQ messages yet")
end
KubeMQ.Client.close(client)How It Works
QueuePolicy.new(max_receive_count: 2, max_receive_queue: dlq)configures dead-letter routing- After the message is received (and not acknowledged)
max_receive_counttimes, it moves to the DLQ - The
attributes.re_routedandattributes.re_routed_from_queuefields track the routing history
Related
Was this page helpful?