Laravel Tutorial · Beyond the Basics · Lesson 22 of 27
Queues and Jobs
Move slow work like emails, image processing and API calls out of the request with queued jobs; retry failures; and run workers with Horizon or queue:work.
Why queues?
A user shouldn't wait three seconds for an email to send. Jobs describe work to do later; a queue stores them (database, Redis, SQS); workers (php artisan queue:work) run them in the background.
php artisan make:job SendWelcomeEmail
php artisan queue:work --tries=3
- Dispatch with
SendWelcomeEmail::dispatch($user); delay with->delay(now()->addMinutes(5)); choose a queue with->onQueue('emails'). $tries,$backoffandfailed()control retries; failed jobs land infailed_jobs(php artisan queue:retry all).- Make jobs idempotent: a job may run more than once after a crash.
- Horizon gives Redis queues a dashboard and auto-scaling workers.
This example uses the sync driver (jobs run immediately) to show what a job does; in production the same dispatch() call just queues it.
<?php
use Illuminate\Bus\Batch;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Queue;
class SendWelcomeEmail implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public array $backoff = [10, 60]; // seconds between retries
public function __construct(public string $email) {}
public function handle(): void
{
echo " sending welcome email to {$this->email}\n";
}
}
class ResizeImage implements ShouldQueue
{
use Queueable;
public function __construct(public string $path) {}
public function handle(): void
{
echo " resizing {$this->path}\n";
}
}
echo "queue driver: ", config('queue.default'), "\n";
SendWelcomeEmail::dispatch('[email protected]');
SendWelcomeEmail::dispatch('[email protected]')->onQueue('emails')->delay(now()->addMinutes(5));
Bus::chain([new ResizeImage('avatar.png'), new SendWelcomeEmail('[email protected]')])->dispatch(); // run in order
Queue::fake(); // in tests: record dispatched jobs instead of running them
SendWelcomeEmail::dispatch('[email protected]');
Queue::assertPushed(SendWelcomeEmail::class, fn ($job) => $job->email === '[email protected]');
echo "test assertion passed: a welcome email job was queued for [email protected]\n";
Output
queue driver: sync sending welcome email to [email protected] sending welcome email to [email protected] resizing avatar.png sending welcome email to [email protected] test assertion passed: a welcome email job was queued for [email protected]
Run this on your computer — see “Get Started” for setup.
Test yourself
Why should queued jobs be idempotent?
To run fasterA job can run more than once (retries after crashes), so repeating it must be safeQueues require it to compileTo use less memory
Show answer
Answer: A job can run more than once (retries after crashes), so repeating it must be safe