laravelmastery
SnippetsArticlesSeriesNews

Queues in production · Lesson 1 of 3

Queues in production · Lesson 1

Dispatch from actions, not controllers

Hand the work offQueues

The job class holds the order id and the attempt. The controller already has enough to do: authorise, validate, redirect. Dispatching the job is a decision about the order, so it belongs with the rest of that decision.

Video

01 One action owns the hand-off

The action receives the order and the attempt key. The controller does not mention the job class.

PayOrder.phpPHP
class PayOrder
{
    public function __invoke(Order $order, string $attempt): void
    {
        MarkOrderPaid::dispatch($order->id, $attempt);
    }
}
Example.phpPHP
class Example
{
    public function store(PayOrderRequest $request, PayOrder $pay): RedirectResponse
    {
        $order = Order::query()->create([
            'number' => $request->validated('number'),
            'status' => 'pending',
        ]);

        $pay($order, (string) Str::uuid());

        return redirect()->route('orders.show', $order);
    }
}

A later test can swap the action, or call it from a command, without copying the dispatch line into a second controller. The job class stays the thing that knows how to mark the order paid.

02 Refuse before a second job is queued

An order that is already paid should not gain another job from a second click. The action can see the row, because the request has just loaded it. Returning here skips the dispatch. The controller still does not mention the job class.

Example.phpPHP
class Example
{
    public function __invoke(Order $order, string $attempt): void
    {
        if ($order->status === 'paid') {
            return;
        }

        MarkOrderPaid::dispatch($order->id, $attempt);
    }
}

This check is the request’s decision. The job’s own check, in Make the job safe to run twice, is the worker’s decision. A retry happens after the request has finished, so the action is no longer on the stack. Both checks stay. One of them is not a substitute for the other.

Recap

You can dispatch from the action that decided to take payment, not from the controller.

  • The controller authorises, validates, and redirects.
  • A second click can return before a second job is queued.
  • The job still checks again, because a retry happens after the action has gone.

Check yourself · 3 questions

Question 1 of 3The action returns early because the order is paid. A retry of the job still runs. Why is the action's check not enough?

The action owns the dispatch. Next, what happens when the provider times out.

← PreviousThe job class

On this page

  • One action owns the hand-off
  • Refuse before a second job is queued
  • 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.