Queues & Jobs

Push slow work off the request cycle — email, image processing, API calls — into background workers.

Users should never wait for work that does not need to block their response. Sending a welcome email, resizing an upload, calling a slow third-party API — queue it. The request returns instantly; a worker process picks the job up moments later.

Anatomy of a job

A class implementing ShouldQueue with a handle() method. Its constructor arguments are serialised, so pass IDs or models (Laravel re-fetches models via the SerializesModels trait), not giant payloads or closures.

class SendWelcome implements ShouldQueue
{
    use Queueable;
    public function __construct(public User $user) {}
    public function handle(): void { Mail::to($this->user)->send(new Welcome()); }
}

SendWelcome::dispatch($user);

Reliability knobs

  • $tries / backoff — retry transient failures with delay.
  • $timeout — kill a job that hangs.
  • uniqueId() via ShouldBeUnique — prevent duplicates.
  • Batches & chains — orchestrate groups of jobs with a completion callback.

Example

Example · php
<?php

namespace App\Jobs;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};

class ChargeOrder implements ShouldQueue, ShouldBeUnique
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;
    public int $timeout = 30;

    public function __construct(public Order $order) {}

    // No two charge jobs for the same order run concurrently.
    public function uniqueId(): string
    {
        return 'charge-order-' . $this->order->id;
    }

    // Exponential-ish backoff between retries (seconds).
    public function backoff(): array
    {
        return [10, 30, 60, 120];
    }

    public function handle(\App\Contracts\PaymentGateway $gateway): void
    {
        // Idempotent: bail if already charged (retries are safe).
        if ($this->order->isPaid()) {
            return;
        }
        $charge = $gateway->charge($this->order->total_cents, $this->order->token);
        $this->order->markPaid($charge->id);
    }

    public function failed(\Throwable $e): void
    {
        $this->order->markPaymentFailed($e->getMessage());
    }
}

// Dispatch AFTER the surrounding transaction commits:
// ChargeOrder::dispatch($order)->afterCommit()->onQueue('payments');

When to use it

  • An e-commerce site dispatches a SendOrderConfirmationEmail job to the queue after checkout so the HTTP response is returned immediately without waiting for the mail server.
  • A SaaS platform delays an UpgradePlanJob by 10 minutes using ->delay(now()->addMinutes(10)) to allow a grace period before provisioning the upgraded tier.
  • A failing third-party API call is retried automatically up to 5 times with $tries = 5 and exponential back-off defined in backoff(), without any manual retry logic.

More examples

Create and dispatch a job

Artisan generates the job class with a handle() stub and ShouldQueue interface already applied, ready for queue dispatch.

Example · bash
php artisan make:job SendOrderConfirmationEmail
# Creates app/Jobs/SendOrderConfirmationEmail.php

Define a queueable job

Implements ShouldQueue with three retries and exponential back-off; SerializesModels safely serializes the Order model for the queue payload.

Example · php
class SendOrderConfirmationEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries   = 3;
    public int $timeout = 60;

    public function __construct(private Order $order) {}

    public function handle(Mailer $mailer): void
    {
        $mailer->to($this->order->user)
               ->send(new OrderConfirmationMail($this->order));
    }

    public function backoff(): array
    {
        return [30, 60, 120]; // seconds between retries
    }
}

Dispatch with delay and chain

dispatch() queues the job immediately; delay() defers it; bus_chain() ensures the three jobs run in sequence, each dependent on the previous succeeding.

Example · php
// Dispatch immediately
SendOrderConfirmationEmail::dispatch($order);

// Dispatch after 5 minutes
SendOrderConfirmationEmail::dispatch($order)
    ->delay(now()->addMinutes(5));

// Chain: run second job only after first succeeds
bus_chain([
    new ProcessPayment($order),
    new FulfillOrder($order),
    new SendOrderConfirmationEmail($order),
])->dispatch();

Discussion

  • Be the first to comment on this lesson.