t4mer@notebook

// article · rabbitmq · laravel · 22 july 2026 · 3 min

Understanding RabbitMQ after years of using Laravel queues

Jobs, queues, and brokers are not the same sentence.

For a long time, “the queue” meant Laravel’s queue system. Redis, database, maybe SQS. A job class, a ShouldQueue, a worker process, retry_after, and the occasional failed_jobs row I would look at when something felt off.

RabbitMQ is not a driver you forget about. It is a broker with its own vocabulary, and that vocabulary exists because the problems are real.

This is the note I needed when I stopped treating messaging as a Laravel feature and started treating it as infrastructure.

Laravel queues hide a lot, on purpose

That is not an insult. A good framework should hide the boring parts.

In Laravel you usually get:

  • a job serialized onto a backend
  • a worker that pops it
  • retries with backoff
  • a failed job table

You can ship a lot of production software without knowing whether you are doing competing consumers, pub/sub, or a work queue. Redis lists will carry you surprisingly far.

RabbitMQ makes you name the parts:

  • producer
  • exchange
  • binding
  • queue
  • consumer
  • ack / nack

Once you see those, Laravel’s queue starts looking like one opinionated topology: a direct-ish work queue with retries bolted on in the application.

The mental model that finally stuck

A message does not “go to a queue” in the way a Laravel job goes to redis. A message goes to an exchange. The exchange, using bindings and routing keys, decides which queues receive a copy.

That one sentence explains a surprising amount of confusion:

  • Why can two consumers see the same logical event? Because two queues were bound.
  • Why did nothing consume my message? Because the routing key did not match a binding.
  • Why is a fanout exchange “broadcast”? Because that is its whole job.

Acknowledgements are not retries

This bit cost me time.

In Laravel, if a job throws, the worker typically releases it or marks it failed according to the job’s tries. You think in application exceptions.

In RabbitMQ, if a consumer dies without acking, the broker can redeliver. That is not the same as tries = 3. It is a statement about whether the broker still considers the message in-flight.

Nack with requeue, nack without requeue, dead-letter exchanges - these are policy. If you do not choose a policy, you will get a default you did not mean.

A practical rule I use now:

  1. Ack only after the side effect you care about has happened, or has been recorded as happened.
  2. If the side effect is not idempotent, you do not have a retry strategy. You have a duplicate-risk strategy.

Laravel jobs have the same problem. RabbitMQ just refuses to let you ignore it.

What I still use Laravel queues for

Not everything should be a broker topology.

If the work is “send this email after the request”, Laravel on Redis is simple and honest. If the work is “several independently deployed services need to react to an order without sharing a database”, a broker starts to earn its keep.

The mistake is fashion. I have seen RabbitMQ introduced because it sounded distributed. I have also seen Redis queues asked to be an integration bus. Both are ways of using the wrong sentence for the problem.

A small map

NeedI would start with
App-internal background workLaravel queue (Redis)
Work that must survive worker death with explicit routingRabbitMQ work queue
Multiple independent consumers of the same eventRabbitMQ with a fanout or topic exchange
Cloud-native, less operational brokerSQS, with eyes open about ordering

I am still learning the sharp edges: prefetch, poison messages, and the difference between “the queue is deep” and “the consumer is slow”. Related: RabbitMQ redelivery.