KubeMQ
Client SDKsNode.jsHow-to guidesQueues

Dead Letter Policy

Field-level reference for the maxReceiveCount and maxReceiveQueue policy fields that configure dead-letter routing in the Node.js SDK.

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.

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

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

Code

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

  • 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.

Was this page helpful?

On this page