Queue
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\Job as 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.