Since RabbitMQ (and AMQP) have no specialized functionality for delay adding a message to the queue a dead-letter exchange can be used instead. This works by having two separate exchanges, one "worker exchange" and one "delay exchange", and one queue for each exchange. For each exchange and queue pair a binding has to be setup that specifies the same routing key for both exchanges.
The consumer listens to the worker queue and any delayed message is added to the delay queue with the specified routing key and a expiration time. Instead of per-message expiration the delay queue can also specify a x-message-ttl that applies to all messages going in to it. When the message expires it will be reaped by RabbitMQ and put into the specified dead-letter exchange, the worker exchange in this case, and the routing key will direct it to the worker queue for consumption.
To use this as a delayed retry functionality you can also set the delay exchange as dead-letter exchange for the worker exchange. This means that if you reject or nack the message without specifying that it should be requeued it will instead end up in it's dead-letter exchange (and hence the delay queue) where it will reside until the specified ttl or expiration time expires and it will be put back into the worker queue.
As a message is being dead-lettered a x-death header is added with information about the queues it has been dead-lettered from. The x-death header contains a list of maps with the following values:
queueandexchange- The queue and exchange it was dead-lettered from.count- The number of times it was dead-lettered from this particular queue with this particular routing key (the exchange doesn't matter).reason-rejected,expiredandmaxlenis the allowed values.routing-keys- A list of routing keys that had been assigned to the message when it was dead-lettered.time- The time when it was dead-lettered.original-expiration- The original expiration value of the message (which is removed from the message to avoid it expiring again in the dead-letter queue).