# Dead Letter Policy (/sdks/nodejs/how-to/queues/dead-letter-policy)



<Callout type="info" title="Which to use">
  This page is the field-level reference for the `policy` object's `maxReceiveCount`/`maxReceiveQueue` fields, shown here via the queue-stream API. For the end-to-end task — sending a message, exhausting retries, and consuming from the resulting DLQ — see [Dead Letter Queue](./dead-letter-queue).
</Callout>

## Overview [#overview]

`maxReceiveCount` and `maxReceiveQueue` are fields on the `policy` object passed to `createQueueMessage()`. Together they define a message's dead-letter routing — set once at send time, they travel with the message; the *producer*, not the consumer, decides the retry ceiling. This page shows the fields via the queue-stream (`streamQueueMessages`) API; the fields themselves are identical to the standard queue API.

**Gotchas:** the receive count increments on *every* failed delivery — an explicit `nackAll()`, an expired transaction, or a visibility timeout — not just deliberate rejections, so set the ceiling above your normal retry budget. The dead-letter channel is an ordinary queue with no special behavior: nothing drains it for you, so monitor it and build a reprocessing path or failures pile up silently.

## Prerequisites [#prerequisites]

* KubeMQ server running on `localhost:50000`
* Node.js SDK installed (`npm install kubemq-js`)

## Code [#code]

```typescript title="dead-letter-policy.ts"
/**
 * Example: Queue Stream — Dead-Letter Queue Policy
 *
 * Demonstrates the dead-letter queue (DLQ) pattern via the stream API.
 * A message is sent with maxReceiveCount=3 and maxReceiveQueue pointing
 * to a DLQ channel. When the message is rejected (nacked) more than
 * 3 times, it is automatically moved to the DLQ.
 *
 * Prerequisites:
 *   - KubeMQ server running on localhost:50000
 *
 * Run: npx tsx examples/queues-stream/dead-letter-policy.ts
 */
import { KubeMQClient, createQueueMessage } from 'kubemq-js';

async function main(): Promise<void> {
  const client = await KubeMQClient.create({
    address: 'localhost:50000',
    clientId: 'js-queues-stream-dead-letter-policy-client',
  });

  try {
    const upstream = client.createQueueUpstream();

    await upstream.send([
      createQueueMessage({
        channel: 'js-queues-stream.dead-letter-policy',
        body: 'Flaky job that will fail',
        policy: {
          maxReceiveCount: 3,
          maxReceiveQueue: 'js-queues-stream.dead-letter-policy-dlq',
        },
      }),
    ]);

    console.log('Sent message with DLQ policy (maxReceiveCount=3)');

    for (let attempt = 1; attempt <= 4; attempt++) {
      const handle = client.streamQueueMessages({
        channel: 'js-queues-stream.dead-letter-policy',
        maxMessages: 1,
        waitTimeoutSeconds: 3,
      });

      await new Promise<void>((resolve) => {
        handle.onMessages((msgs) => {
          if (msgs.length === 0) {
            console.log(`Attempt ${attempt}: no messages (moved to DLQ)`);
          } else {
            const first = msgs[0]!;
            console.log(
              `Attempt ${attempt}: received "${new TextDecoder().decode(first.body)}" (receiveCount=${first.receiveCount}) — rejecting`,
            );
            handle.nackAll();
          }
          setTimeout(() => {
            handle.close();
            resolve();
          }, 500);
        });

        handle.onError(() => {
          handle.close();
          resolve();
        });
      });
    }

    console.log('\nChecking dead-letter queue...');
    const dlqHandle = client.streamQueueMessages({
      channel: 'js-queues-stream.dead-letter-policy-dlq',
      maxMessages: 10,
      waitTimeoutSeconds: 3,
      autoAck: true,
    });

    dlqHandle.onMessages((msgs) => {
      console.log(`DLQ contains ${msgs.length} message(s):`);
      for (const msg of msgs) {
        console.log(`  "${new TextDecoder().decode(msg.body)}" (receiveCount=${msg.receiveCount})`);
      }
    });

    dlqHandle.onError((err) => {
      console.error('DLQ error:', err.message);
    });

    await new Promise((resolve) => setTimeout(resolve, 3000));
    dlqHandle.close();
    upstream.close();
  } finally {
    await client.close();
  }
}

main().catch(console.error);

```

## Field reference [#field-reference]

* **`maxReceiveCount`** (`number`) — the number of failed receives allowed before the broker reroutes the message. `maxReceiveCount: 3` means the message survives 3 rejections/nacks; the next failed receive triggers the move. Each attempt's `receiveCount` on the received message reflects the current count.
* **`maxReceiveQueue`** (`string`) — the destination channel name for diverted messages. Leaving it unset means messages that exceed `maxReceiveCount` are discarded rather than rerouted.
* Both fields are part of the `policy` object passed to `createQueueMessage()` at send time — there's no way to change the policy after the message is queued.

For the walkthrough of sending, exhausting retries, and consuming from the resulting DLQ, see [Dead Letter Queue](./dead-letter-queue).

## Related [#related]

* [Dead Letter Queue](./dead-letter-queue) — task-oriented walkthrough for DLQ routing
* [Node.js SDK Reference](/sdks/nodejs/reference)
* [Send & Receive](/sdks/nodejs/tutorials/send-receive)
* [Ack All](/sdks/nodejs/how-to/queues/ack-all)
