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.