# Dead Letter Queue (/sdks/elixir/how-to/queues/dead-letter-queue)



## Overview [#overview]

A &#x2A;*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 [#prerequisites]

* KubeMQ server running on `localhost:50000`
* Elixir SDK installed (`{:kubemq, "~> 1.0"}` in mix.exs)

## Code [#code]

```elixir title="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 [#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

## Related [#related]

* [Dead Letter Policy](/sdks/elixir/how-to/queues/dead-letter-policy)
* [Queues Pattern Overview](/learn/queues/)
