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.

Create a free account to save this page, take notes and track your progress. Sign in

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, $backoff and failed() control retries; failed jobs land in failed_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.

Dispatching, chaining and faking jobs
<?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?

  1. To run faster
  2. A job can run more than once (retries after crashes), so repeating it must be safe
  3. Queues require it to compile
  4. To use less memory
Show answer

Answer: A job can run more than once (retries after crashes), so repeating it must be safe

Was this page helpful?