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



<Callout type="info" title="Which to use">
  This page is the task-oriented walkthrough: send a message with a DLQ policy, reject it until it exhausts retries, and consume the diverted message from the DLQ. For the `QueueMessagePolicy` field reference — `maxReceiveCount`/`maxReceiveQueue` defaults and edge cases — see [Dead Letter Policy](./dead-letter-policy).
</Callout>

## 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 `QueueMessagePolicy`: `maxReceiveCount` and `maxReceiveQueue`. Every failed delivery — a `reject()`, a nack, 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 reject. A typo in the DLQ channel name quietly creates an unrelated channel instead of failing loudly.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* Kotlin SDK installed (`implementation("io.kubemq.sdk:kubemq-sdk-kotlin:1.0.1")`)

## Code [#code]

```kotlin title="DeadLetterQueueExample.kt"
package io.kubemq.sdk.examples.queues

import io.kubemq.sdk.client.KubeMQClient
import io.kubemq.sdk.queues.QueueMessagePolicy
import io.kubemq.sdk.queues.queueMessage
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

private const val ADDRESS = "localhost:50000"
private const val CLIENT_ID = "kotlin-queues-dead-letter"
private const val CHANNEL = "kotlin-queues.dead-letter"
private const val DLQ_CHANNEL = "kotlin-queues.dead-letter-dlq"

fun main() = runBlocking {
    val client = KubeMQClient.queues {
        address = ADDRESS
        clientId = CLIENT_ID
    }

    client.use {
        // Send a message with DLQ policy (max 3 receives, then route to DLQ)
        println("Sending message with DLQ policy (max 3 receives)...")
        client.sendQueuesMessage(queueMessage {
            channel = CHANNEL
            body = "DLQ after 3 nacks".toByteArray()
            policy = QueueMessagePolicy(
                maxReceiveCount = 3,
                maxReceiveQueue = DLQ_CHANNEL,
            )
        })

        // Reject the message 3 times
        repeat(3) { attempt ->
            delay(500)
            val resp = client.receiveQueuesMessages {
                channel = CHANNEL
                maxItems = 1
                waitTimeoutMs = 3000
                autoAck = false
            }
            resp.messages.forEach { msg ->
                msg.reject()
                println("Rejected attempt #${attempt + 1}: ${String(msg.body)}")
            }
        }

        // Check the DLQ
        delay(1000)
        val dlqResp = client.receiveQueuesMessages {
            channel = DLQ_CHANNEL
            maxItems = 1
            waitTimeoutMs = 3000
            autoAck = true
        }
        dlqResp.messages.forEach { msg ->
            println("DLQ received: ${String(msg.body)}")
        }

        println("Done.")
    }
}
```

## How It Works [#how-it-works]

* `QueueMessagePolicy(maxReceiveCount = 3, maxReceiveQueue = DLQ_CHANNEL)` configures the DLQ policy.
* After 3 rejections, the message is automatically moved to the DLQ channel.
* The DLQ channel can be consumed separately for manual review or reprocessing.
* This prevents poison messages from blocking the main queue indefinitely.

## Related [#related]

* [Dead Letter Policy](./dead-letter-policy) — field-level reference for `maxReceiveCount` and `maxReceiveQueue`
* [Ack & Reject](/sdks/kotlin/how-to/queues/ack-reject)
* [Queues Reference](/sdks/kotlin/reference/queues)
