laravelmastery
SnippetsArticlesSeriesNews

Queues in production · Lesson 1 of 3

Queues in production · Lesson 1

The job class

Hand the work offQueues

The request recorded order 1042 and dispatched the work. A job is a class that implements ShouldQueue. The constructor holds the data the worker needs later. handle() does the work. Pass the order id, not a pile of request input, so the payload stays small and obvious.

Video

01 Pass the id and load the row in handle()

SerializesModels can reload a model for you, and it fails the job when that row has been deleted. Passing the id makes the same choice visible in handle().

MarkOrderPaid.phpPHP
class MarkOrderPaid implements ShouldQueue
{
    use Queueable;

    public function __construct(public int $orderId, public string $attempt)
    {
    }

    public function handle(): void
    {
        $order = Order::query()->findOrFail($this->orderId);

        $order->forceFill([
            'status' => 'paid',
            'paid_attempt' => $this->attempt,
        ])->save();
    }
}

The queue stores the constructor arguments. Do not put a card number, a token or a raw request in there. failed_jobs keeps that payload after a failure, and Horizon shows it to anyone who can open the dashboard.

02 Put payment work on its own queue

A slow report on default should not sit in front of a payment. onQueue() is called from the constructor, so every dispatch of this class uses the same name. The queue name is not part of the payload you have to remember at the call site.

Example.phpPHP
class Example
{
    public function __construct(public int $orderId, public string $attempt)
    {
        $this->onQueue('payments');
    }
}

A worker that only listens to default will never take the job. Locally that means queue:work --queue=payments,default. In production the Horizon supervisor has to list payments, which is the last lesson in this series. The constructor still holds the id and the attempt. Those are the only values the failed job row needs.

Recap

You can pass an id and an attempt into a job, and keep a card number out of the payload.

  • ShouldQueue plus handle() is the worker.
  • SerializesModels will reload a model, and fail if the row has gone. An id makes that choice visible.
  • onQueue('payments') in the constructor means every dispatch uses that queue.

Check yourself · 3 questions

Question 1 of 3The constructor receives the whole request. What ends up in failed_jobs?

The payload is the order id and the attempt. Next, put the dispatch next to the decision.

← PreviousKeep the request short

On this page

  • Pass the id and load the row in handle()
  • Put payment work on its own queue
  • Recap
  • Check yourself

Queues in production

  • 1 Overview

Hand the work off

  • 2 Keep the request short
  • 3 The job class
  • 4 Dispatch from actions, not controllers

When it goes wrong

  • 5 Retries, backoff and timeouts
  • 6 Failed jobs you can replay
  • 7 Unique jobs and WithoutOverlapping
  • 8 Batches and then()

Run it for real

  • 9 Make the job safe to run twice
  • 10 Horizon in production

Queues in production

Lessons

  1. 01Overview

Hand the work off

  1. 02Keep the request short
  2. 03The job class
  3. 04Dispatch from actions, not controllers

When it goes wrong

  1. 05Retries, backoff and timeouts
  2. 06Failed jobs you can replay
  3. 07Unique jobs and WithoutOverlapping
  4. 08Batches and then()

Run it for real

  1. 09Make the job safe to run twice
  2. 10Horizon in production

Get the Friday email

One snippet and the week’s Laravel news, in your inbox every Friday morning. Unsubscribe whenever you like.

laravelmastery

Practical Laravel learning for working developers, made in the UK.

Learn

  • Snippets
  • Articles
  • Series

Stay current

  • News
  • Friday email
  • RSS feed

Company

  • About
  • Privacy
  • Terms
© 2026 Laravel Mastery. Independent and not affiliated with Laravel Holdings Inc.Designed and built by Half Shell Studios

We remember the videos you watch, where you left off, and which series lessons you mark as read, against this connection’s address. Nothing is stored until you agree.