Laravel Queues Explained: How to Handle Background Jobs the Right Way
If your app does anything that takes more than a second — sending emails, processing files, calling external APIs — you should not be doing it in the request cycle. Laravel’s queue system is the solution, and it’s more approachable than most developers think.
Why Queues Matter
Without queues, every slow operation blocks the user:
User clicks "Send Invoice"
→ App calls PDF generator (2s)
→ App calls email service (1s)
→ App waits for response (1s)
→ User finally sees "Done" after 4 seconds
With queues:
User clicks "Send Invoice"
→ App dispatches a job (5ms)
→ User sees "Done" immediately
→ Queue worker processes PDF + email in background
Same result. Completely different user experience.
Setting Up Laravel Queues
1. Configure your queue driver
In your .env file:
QUEUE_CONNECTION=database
For production, use redis — it’s faster and more reliable than the database driver. For local development, database is fine.
2. Create the jobs table (if using database driver)
php artisan queue:table
php artisan migrate
3. Create your first job
php artisan make:job SendInvoiceEmail
This generates app/Jobs/SendInvoiceEmail.php:
<?php
namespace App\Jobs;
use App\Models\Invoice;
use App\Mail\InvoiceMail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
class SendInvoiceEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Invoice $invoice) {}
public function handle(): void
{
Mail::to($this->invoice->user->email)
->send(new InvoiceMail($this->invoice));
}
}
4. Dispatch the job
// In your controller
SendInvoiceEmail::dispatch($invoice);
// With a delay (send after 5 minutes)
SendInvoiceEmail::dispatch($invoice)->delay(now()->addMinutes(5));
// On a specific queue
SendInvoiceEmail::dispatch($invoice)->onQueue('emails');
Handling Failures
Jobs fail. Networks go down, APIs return errors, memory runs out. Laravel gives you tools to handle this gracefully.
Retry attempts
class SendInvoiceEmail implements ShouldQueue
{
// Retry up to 3 times
public int $tries = 3;
// Wait 60 seconds between retries
public int $backoff = 60;
}
The failed() method
public function failed(\Throwable $exception): void
{
// Notify the user, log the error, send a Slack alert...
Log::error('Invoice email failed', [
'invoice_id' => $this->invoice->id,
'error' => $exception->getMessage(),
]);
}
Checking failed jobs
# See all failed jobs
php artisan queue:failed
# Retry a specific failed job
php artisan queue:retry {id}
# Retry all failed jobs
php artisan queue:retry all
Multiple Queues: Prioritization
Not all jobs are equal. A password reset email is more urgent than a weekly report. Use multiple queues to prioritize:
// High priority job
SendPasswordResetEmail::dispatch($user)->onQueue('high');
// Low priority job
GenerateWeeklyReport::dispatch()->onQueue('low');
Run workers with priority order:
php artisan queue:work --queue=high,low
The worker processes all high jobs before touching low ones.
Running the Queue Worker
Local development
php artisan queue:work
Production (with Supervisor)
In production, you need a process manager to keep the worker running. Supervisor is the standard choice.
; /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
Horizon: Queue Monitoring for Redis
If you’re using Redis, install Laravel Horizon for a beautiful dashboard to monitor your queues:
composer require laravel/horizon
php artisan horizon:install
php artisan horizon
It gives you real-time metrics: throughput, job duration, failure rates, and more.
Quick Reference
| Scenario | Solution |
|---|---|
| Send email in background | ShouldQueue job |
| Retry on failure | $tries + $backoff |
| Urgent vs non-urgent jobs | Multiple named queues |
| Keep worker running | Supervisor |
| Monitor Redis queues | Laravel Horizon |
| Delay a job | ->delay(now()->addMinutes(5)) |
Queues are one of those features that seem complex until you use them once. After that, you’ll find yourself reaching for them constantly. Start with a simple job, run it in the background, and watch your app feel instantly faster.