Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Laravel: Queues and Horizon

Any slow or non-essential piece of work in a Laravel request can become a Job, pushed onto a queue and processed by a separate worker process, so the user’s request finishes immediately instead of waiting on it.

php artisan make:job SendWelcomeEmail
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    public function __construct(private User $user) {}

    public function handle(): void
    {
        Mail::to($this->user)->send(new WelcomeEmail($this->user));
    }
}

// dispatching it
SendWelcomeEmail::dispatch($user);
php artisan queue:work

That last command is a worker process, typically kept running by a process manager like Supervisor, pulling jobs off the queue (Redis, in most production setups) and executing them.

Horizon adds a dashboard on top of Redis queues: throughput, failed jobs, retry controls, and per-queue metrics, without needing a separate monitoring tool.

composer require laravel/horizon
php artisan horizon:install
php artisan horizon

Failed jobs retry automatically with backoff, and permanently failed ones land in a failed_jobs table for inspection rather than vanishing silently.

When to reach for this

Anything that shouldn’t block a web request: sending email, processing an uploaded file, calling a slow third-party API, generating a report. If a user would notice the delay, it belongs on a queue.

When it’s the wrong fit

Work that genuinely needs to complete before the response, like validating a form before saving it. Queuing that just adds latency and complexity where synchronous code was already correct.

Under the hood: Jobs are serialized (usually with PHP’s native serialize()) before being stored in the queue, which is why job classes should only hold simple, serializable properties like a model ID rather than large objects or open resources; the worker process deserializes and reconstructs the job from scratch when it runs.