Reader Stacks

Dispatching a Background Queue Job in Laravel

A dispatched job doesn't run at all until a queue worker process is actually running to pick it up — the sync driver is the one exception, executing jobs immediately and mainly useful for local development.

Dispatching work to a background queue job — sending an email, processing an image, calling a slow external API — keeps the user-facing request fast by deferring the actual work to a separate process, but it needs an actively running queue worker to ever execute at all.

Creating a job

php artisan make:job ProcessOrderExport
class ProcessOrderExport implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(private Order $order) {}

    public function handle(): void
    {
        // generate the export, potentially a slow operation
        $export = new OrderExportService();
        $export->generate($this->order);
    }
}

SerializesModels ensures the $order model is serialized as just its ID when the job is queued, then re-fetched fresh from the database when the job actually runs — this is what prevents shipping a large, potentially stale model instance through the queue driver itself.

Dispatching the job

ProcessOrderExport::dispatch($order);

The critical requirement: an actual running queue worker

php artisan queue:work

Dispatching a job only adds it to the queue's storage (database, Redis, etc.) — it does not execute at all until a separate queue:work process is actively running to pick it up and process it; a common point of confusion for a first queue implementation is dispatching jobs that appear to simply never run, when in fact no worker process was ever started.

The sync driver: immediate execution, mainly for local development

// .env
QUEUE_CONNECTION=sync

With the sync driver, a dispatched job runs immediately, synchronously, in the same request — genuinely useful for local development when running a separate queue worker process is inconvenient, but it defeats the entire purpose of queuing in a real environment, since the calling request still waits for the job to fully complete.

Delaying a job's execution

ProcessOrderExport::dispatch($order)->delay(now()->addMinutes(10));

Dispatching to a specific named queue

ProcessOrderExport::dispatch($order)->onQueue('exports');
php artisan queue:work --queue=exports,default

Named queues let different job types be processed with different priority or by different dedicated workers — listing exports,default processes the exports queue first, falling back to default only once it's empty, useful for making sure a high-priority job type isn't stuck behind a backlog of lower-priority ones.

Handling a job that fails

class ProcessOrderExport implements ShouldQueue
{
    public int $tries = 3;

    public function failed(\Throwable $exception): void
    {
        Log::error('Order export failed: '.$exception->getMessage());
        Notification::send($this->order->customer, new ExportFailedNotification());
    }
}

$tries sets how many times Laravel automatically retries a failing job before giving up — failed() runs only once all retry attempts are exhausted, the right place for a final fallback action like notifying someone the job never succeeded.

Running the queue worker in production, via a process manager

A queue worker process needs to be kept running continuously and restarted automatically if it crashes — Supervisor is the standard tool for this on a typical Linux server, configured to keep queue:work running and to restart it if the process ever stops unexpectedly.