How to Implement Delayed Queues in RabbitMQ Using Dead‑Letter Exchanges

This guide explains common use cases for delayed tasks, describes two RabbitMQ delayed‑queue implementations—including TTL with dead‑letter exchanges and the rabbitmq‑delayed‑message‑exchange plugin—provides detailed configuration steps, code examples in PHP, and demonstrates how to run and verify the solution.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
How to Implement Delayed Queues in RabbitMQ Using Dead‑Letter Exchanges

Use Cases for Delayed Tasks

IoT systems need to timeout commands that receive no response.

Orders are automatically cancelled if payment is not received within 30 minutes.

Send registration emails to new members after a 1‑minute delay.

Two Ways to Implement RabbitMQ Delayed Queues

Use message expiration (TTL) combined with a dead‑letter exchange (DLX) to forward expired messages to a delay queue.

Install the rabbitmq-delayed-message-exchange plugin (available from RabbitMQ 3.5.7) to provide native delayed‑message support.

Dead‑Letter Mechanism for Delayed Queues

RabbitMQ does not have a built‑in delayed‑queue feature; it must be achieved by combining message TTL and a dead‑letter exchange. When a message’s TTL expires, it becomes a dead letter and is routed to the DLX.

Message TTL (Time To Live)

TTL defines how long a message lives. It can be set per‑queue or per‑message using the expiration field (milliseconds) or the queue argument x-message-ttl. The smaller of queue TTL and message TTL applies.

$body = 'Tinywan expiration!';
$msg = new AMQPMessage($body);
$msg->set("delivery_mode", AMQPMessage::DELIVERY_MODE_PERSISTENT);
// Set timeout (expiration) in milliseconds
$msg->set("expiration", 30000); // 30 seconds

When the message is published with {expiration: '30000'}, RabbitMQ holds it for 30 seconds. The message is only discarded or turned into a dead letter when it reaches the head of the queue and its expiration has passed.

Dead‑Letter Exchange (DLX)

A dead‑letter exchange receives messages that become dead letters. It is declared like any other exchange. When a message expires, is rejected without requeue, or the queue length limit is reached, the message is sent to the DLX specified by the queue argument x-dead-letter-exchange. The routing key can be set with x-dead-letter-routing-key; if omitted, the original routing key is used.

Configuring a Dead‑Letter Queue

First declare the DLX and its queue, then bind them:

Exchange: dlx.exchange
Queue: dlx.queue
RoutingKey: #
# "#" means any routing key will be routed to this queue.

Next, add the dead‑letter settings to the target queue:

arguments.put("x-dead-letter-exchange", "dlx.exchange");

Now, when a message expires, is rejected, or the queue overflows, it is automatically routed to the dead‑letter queue.

Using the DLX for Scheduled Tasks

By publishing a message with an expiration and consuming from the DLX‑bound queue, you can implement a scheduled task. When the original queue’s message expires, it becomes a dead letter and is delivered to the DLX queue, where a consumer processes it at the intended time.

Practical Example (PHP)

Sending a Delayed Message

public static function delayQueueSend($param = [])
{
    $connection = RabbitMqConnection::getConnection();
    $channel = $connection->channel();
    // Define a fanout exchange for waiting messages
    $channel->exchange_declare('waitSendExchange', 'fanout', false, false, false);
    // Define a fanout exchange for expired messages
    $channel->exchange_declare('expireExchange', 'fanout', false, false, false);
    // Declare the expired queue
    $channel->queue_declare('expireQueue', false, false, false, false);
    // Declare the waiting queue with DLX pointing to the expired exchange
    $channel->queue_declare('waitSendQueue', false, false, false, false, false,
        new AMQPTable(["x-dead-letter-exchange" => "expireExchange"]));
    $channel->queue_bind('waitSendQueue', 'waitSendExchange');
    $channel->queue_bind('expireQueue', 'expireExchange');

    $body = 'Tinywan expiration!';
    $msg = new AMQPMessage($body);
    $msg->set("delivery_mode", AMQPMessage::DELIVERY_MODE_PERSISTENT);
    // Set expiration (30 seconds)
    $msg->set("expiration", 30000);
    $channel->basic_publish($msg, 'waitSendExchange');
    echo " [x] Sent " . date('Y-m-d H:i:s') . ': ' . $body . "
";
    $channel->close();
    $connection->close();
}

Receiving the Delayed Message (Blocking Mode)

public static function delayQueueReceive()
{
    $connection = RabbitMqConnection::getConnection();
    $channel = $connection->channel();
    $channel->exchange_declare('waitSendExchange', 'fanout', false, false, false);
    $channel->exchange_declare('expireExchange', 'fanout', false, false, false);
    $channel->queue_declare('expireQueue', false, false, false, false);
    $channel->queue_declare('waitSendQueue', false, false, false, false, false,
        new AMQPTable(["x-dead-letter-exchange" => "expireExchange"]));
    $channel->queue_bind('waitSendQueue', 'waitSendExchange');
    $channel->queue_bind('expireQueue', 'expireExchange');

    echo " [*] Waiting for message. To exit press CTRL+C " . PHP_EOL;
    $callback = function ($msg) {
        echo " [x] Receive " . date('Y-m-d H:i:s') . ':' . $msg->body . "
";
    };
    // Consume from the expired queue
    $channel->basic_consume('expireQueue', "", false, true, false, false, $callback);
    while (count($channel->callbacks)) {
        $channel->wait();
    }
    $channel->close();
    $connection->close();
}

Important Notes

If the expireExchange is of type direct, you must provide a binding key (the dead‑letter routing key) when binding queues; otherwise, declare the exchange as fanout to avoid specifying a binding key.

The fanout exchange broadcasts messages to all bound queues, which is suitable for simple delayed‑task scenarios.

Execution Result

After running the sender, you will see a "Sent" log with a timestamp. The receiver then prints a "Receive" log when the delayed message is delivered from the dead‑letter queue after the configured timeout.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendMessagingdelayed queuedead-letter-exchange
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.