Skip to content

// Communication & Integration · 05

Message Queue

Producerleads toQueueleads toOne consumer

Drop the work off and move on. One consumer picks up each message.

How it fits together

Message Queue structureOrders APIsends GenerateInvoicereturns 202 at onceInvoice queueeach message, ONE workerremoved on completeWorker 1takes lockack after saveWorker 2takes lockack after saveWorker 3takes lockack after saveDead-letter queue1 send2 receive2 receive2 receiveafter 5 tries
createsuses / callsfails / error path· numbered steps replay in order
  • Orders API creates Invoice queue (1 send)
  • Invoice queue uses / calls Worker 1 (2 receive)
  • Invoice queue uses / calls Worker 2 (2 receive)
  • Invoice queue uses / calls Worker 3 (2 receive)
  • Worker 3 fails / error path Dead-letter queue (after 5 tries)

Spot it

  • Requests time out waiting on slow work
  • Traffic spikes overwhelm a downstream service
  • Work is lost when a worker crashes mid-task

It costs

The caller no longer gets an immediate result, and the broker is one more thing to run.

In code

public sealed class InvoiceWorker(IQueueReceiver queue, IInvoiceStorage storage){    public async Task RunAsync(CancellationToken ct)    {        await foreach (var message in queue.ReceiveAsync(ct))        {            try            {                var command = message.Body.Deserialize<GenerateInvoice>();                var pdf = await InvoicePdf.RenderAsync(command, ct);                await storage.SaveAsync(command.InvoiceId, pdf, ct);                await message.CompleteAsync(ct);            }            catch (Exception) when (message.DeliveryCount >= 5)            {                await message.DeadLetterAsync(ct);            }            catch (Exception)            {                await message.AbandonAsync(ct);            }        }    }}
Go deeperThe full explanation and what to ask for · 1 min readExpand

A queue decouples sender and receiver in time: the producer only needs the broker to be up, and consumers pull at their own pace. Several consumers on one queue compete for messages, so adding workers raises throughput and spikes turn into queue depth instead of errors.

The classic mistake is acknowledging a message before the work is done, which loses it on a crash, or assuming exactly-once delivery. Brokers redeliver when a lock or visibility timeout expires, so handlers must be idempotent and the timeout longer than the work.

A queue means one logical receiver per message, which suits commands. When several parties should each react to the same fact, use publish/subscribe. Pair queues with an outbox for reliable sending and a dead-letter queue for poison messages.

Ask for it

  • competing consumers
  • lock / visibility timeout
  • complete / ack
  • at-least-once delivery
  • queue depth
  • sessions (FIFO)

Move PDF generation off the request: the API sends a GenerateInvoice message to a queue (Kafka, RabbitMQ, SQS, Azure Service Bus) and returns 202 with a status URL; a worker takes messages under a 60s lock, completes only after the file is stored, and dead-letters after 5 deliveries.