A message queue such as RabbitMQ decouples a producer that publishes work from one or more consumers that process it asynchronously, so the original HTTP request can return before the work completes.
In simpler words
RabbitMQ lets Nest say do this later instead of making a user wait for slow or unreliable work inside the request.
The same conceptual lens as Redis and file storage: recognize when work does not belong inside the request and response cycle, without installing anything today.
Identify work that belongs in a queue instead of an HTTP handler
Explain producer, consumer, queue, and exchange at a conceptual level
Describe retry and dead-letter behavior for failed jobs
Reason about at-least-once delivery and idempotency
What belongs off the request thread
Definition
Background work is any task whose completion the caller does not need to wait for synchronously, such as sending email, generating a report, or calling a slow third-party API.
In simpler words
If the user does not need the result immediately, do not make them wait for it.
Good candidates: sending a ticket-assigned email, generating a PDF export, calling a flaky third-party webhook, or resizing an uploaded image.
Bad candidates: anything the immediate HTTP response depends on, or work so fast that a queue only adds latency and infrastructure risk.
The controller publishes a small message, such as ticket 123 was created, and returns 201 immediately. A separate consumer process performs the slow part.
The controller never awaits the email send; it only awaits publishing a tiny message.
Delivery guarantees and idempotency
Definition
At-least-once delivery means a message queue may redeliver a message after a failure, so a consumer must be written to tolerate processing the same message more than once without causing duplicate side effects.
In simpler words
A queue may hand a consumer the same job twice. The consumer must not send two emails or trigger two side effects because of that.
Most brokers, including RabbitMQ with manual acknowledgment, guarantee at-least-once, not exactly-once. Consumers must be idempotent by checking whether the work was already done before doing it again.
A dead-letter queue captures messages that fail repeatedly, so a bad job does not loop forever and drown out healthy work.
Retries need backoff and a limit. Infinite immediate retries against a broken consumer will hammer the same downstream failure endlessly.
RabbitMQ versus a raw timer or cron hack
Definition
A message broker persists and routes jobs independently of any single process, whereas an in-process timer or cron job loses its state and schedule whenever that process restarts or scales to multiple instances.
In simpler words
A queue survives a restart and works correctly with several Nest instances. An in-memory timer does not.
A timer-based background job disappears on deploy or restart, and runs once per Nest instance if you scale horizontally, producing duplicate work with no coordination.
RabbitMQ, or a similar broker, durably stores a job until a consumer acknowledges it, and only one consumer among many instances receives each message.
Reach for a queue once work must survive restarts, run once regardless of instance count, or needs retry and backoff semantics, not merely because asynchronous sounds appealing.
Ack, nack, and poison messages
Definition
Acknowledgement (ack) tells the broker the consumer finished the message successfully; negative acknowledgement (nack/reject) tells the broker to requeue or dead-letter it; a poison message is one that repeatedly fails and must not block the queue forever.
In simpler words
Until you ack, RabbitMQ may redeliver the message — which is why idempotency matters.
Ack only after the side effect succeeded (or after you recorded that it is done). Acking before the work finishes can drop work on a crash.
Nack with requeue for transient failures (network blip). After N failures, route to a dead-letter queue for humans to inspect.
Poison messages that crash consumers on every attempt will stall workers if you keep requeueing infinitely — dead-letter is the escape hatch.
Message payloads should be small ids + event type (ticket.created, ticketId), not entire entities that go stale.