3 min read
Queue
Queues let you defer time-consuming tasks — sending emails, processing uploads, generating reports — so that your user gets an immediate response. Laravel's queue system abstracts away the underlying driver; the same job code works whether you are using a database, Redis, Amazon SQS, or RabbitMQ.
Creating a Job
Generate a job class with Artisan:
php artisan make:job SendWelcomeEmail
namespace App\Jobs; use App\Models\User; use App\Mail\WelcomeEmail; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Support\Facades\Mail; class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue; public function __construct( private User $user, ) {} public function handle(): void { Mail::to($this->user->email)->send(new WelcomeEmail($this->user)); } }
Dispatching Jobs
// Dispatch immediately (onto the queue) SendWelcomeEmail::dispatch($user); // Dispatch after a delay SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5)); // Dispatch synchronously (useful in tests) SendWelcomeEmail::dispatchSync($user);
Queue Connections
Configure your connection in .env:
QUEUE_CONNECTION=redis
Common connections and their trade-offs:
| Connection | Persistence | Scaling | Best for |
|---|---|---|---|
database |
Yes | Single server | Development, small applications |
redis |
Yes | Multi-server | Most production deployments |
sqs |
Yes | Serverless | AWS-based applications |
sync |
N/A | N/A | Tests (jobs run immediately, no worker needed) |
Retries and Failure Handling
Jobs can fail — an external API might time out, or a database write might conflict. Tell Laravel how many times to retry and how long to wait:
class ProcessPayment implements ShouldQueue { use Dispatchable, InteractsWithQueue; public int $tries = 3; public int $backoff = 60; // Seconds between retries public function __construct( private int $orderId, ) {} public function handle(): void { $order = Order::findOrFail($this->orderId); $this->chargeCustomer($order); } public function failed(\Throwable $exception): void { // Notify the team or log the failure Log::error('Payment failed permanently', [ 'order_id' => $this->orderId, 'error' => $exception->getMessage(), ]); } }
Monitoring Failed Jobs
Failed jobs are stored in a failed_jobs table by default. Inspect and retry them with Artisan:
# List failed jobs php artisan queue:failed # Retry a specific failed job php artisan queue:retry 1234-5678-abcd # Retry all failed jobs php artisan queue:retry all # Clear the failed jobs table php artisan queue:flush
Running the Worker
In development, start a single worker:
php artisan queue:work
In production, use a process manager like Supervisor to keep the worker running and restart it automatically if it crashes:
[program:laravel-worker] process_name=%(program_name)s_%(process_number)02d command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 autostart=true autorestart=true user=www-data redirect_stderr=true
Tips
- Serialise only what the job needs. Pass an ID rather than a full model instance when possible — models are serialised and deserialised, which adds overhead and can cause issues if the model changes between dispatch and execution.
- Set a
$timeouton jobs that call external services, so a hung request does not block the worker indefinitely. - Use the
ShouldBeUniqueinterface if you want to prevent duplicate jobs from being dispatched while one is already pending or running. - In tests, set
QUEUE_CONNECTION=syncso jobs execute immediately and you can assert their effects without starting a worker.