KubeMQ
Client SDKsElixirHow-to guidesQueues

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

main.exs
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_count times, it moves to the DLQ
  • The attributes.re_routed and attributes.re_routed_from_queue fields track the routing history

Was this page helpful?

On this page