Beartropy Logo

Stop Blocking Your Users: Master Laravel Queues in 10 Minutes

Your users are waiting. Every PDF export, every email notification, every image resize—they're all blocking your request cycle while your users stare at a loading spinner. Let's fix that. The Problem...

Guides 16 Feb, 2026 Beartropy Team

Laravel Queues

Your users are waiting. Every PDF export, every email notification, every image resize—they're all blocking your request cycle while your users stare at a loading spinner. Let's fix that.

The Problem: Everything Is Synchronous

By default, Laravel handles everything in real-time. User signs up? Send a welcome email. Right now. In the same request. Your user waits 2-3 seconds for SendGrid to respond.

Multiply that by every notification, report, and background task in your app. Your users are paying the time-tax for work they don't need to see completed instantly.

The Fix: Dispatch and Forget

Laravel's queue system lets you defer work to background processes. The user gets an instant response; the heavy lifting happens behind the scenes.

1// Before: User waits for email to send
2Mail::to($user)->send(new WelcomeEmail($user));
3 
4// After: User gets instant response
5Mail::to($user)->queue(new WelcomeEmail($user));

One word. That's all it takes to transform a blocking operation into a background job.

Creating Your First Job

For anything beyond simple mail queuing, create dedicated job classes:

1php artisan make:job ProcessPodcastUpload

This generates a job class with one critical method—handle():

1class ProcessPodcastUpload implements ShouldQueue
2{
3 use Queueable;
4 
5 public function __construct(
6 public Podcast $podcast,
7 public string $audioPath,
8 ) {}
9 
10 public function handle(): void
11 {
12 // Extract metadata
13 $duration = FFMpeg::fromDisk('local')
14 ->open($this->audioPath)
15 ->getDurationInSeconds();
16 
17 // Generate waveform
18 $waveform = WaveformGenerator::create($this->audioPath);
19 
20 // Update podcast record
21 $this->podcast->update([
22 'duration' => $duration,
23 'waveform_path' => $waveform->store('waveforms'),
24 'status' => 'processed',
25 ]);
26 }
27}

Dispatch it:

1ProcessPodcastUpload::dispatch($podcast, $path);

Your controller returns immediately. The podcast processes in the background.

Picking the Right Driver

Laravel supports multiple queue backends. Choose wisely:

Driver Best For Trade-offs
sync Development, testing No actual queuing—runs inline
database Small apps, getting started Easy setup, limited throughput
redis Production, most apps Fast, reliable, widely supported
sqs AWS infrastructure Infinite scale, pay-per-use

For most Laravel apps, Redis is the sweet spot. Fast, battle-tested, and Laravel Horizon gives you a beautiful dashboard.

The Power of Job Chaining

Complex workflows? Chain jobs together:

1Bus::chain([
2 new OptimizeVideo($video),
3 new ExtractThumbnails($video),
4 new PublishToS3($video),
5 new NotifySubscribers($video),
6])->dispatch();

Each job runs only after the previous one succeeds. If any job fails, the chain stops. Clean, predictable, debuggable.

Handling Failures Gracefully

Jobs fail. Networks drop. APIs timeout. Plan for it:

1class ProcessPayment implements ShouldQueue
2{
3 public $tries = 3; // Retry up to 3 times
4 public $backoff = [10, 60, 300]; // Wait 10s, 1m, 5m between retries
5 
6 public function handle(): void
7 {
8 // Payment processing logic
9 }
10 
11 public function failed(Throwable $exception): void
12 {
13 // Notify support team
14 // Refund if partial charge occurred
15 Log::critical('Payment failed permanently', [
16 'order_id' => $this->order->id,
17 'error' => $exception->getMessage(),
18 ]);
19 }
20}

The failed() method is your safety net—use it for cleanup, notifications, or compensation logic.

Production Essentials

1. Run Multiple Workers

One worker isn't enough for production:

1php artisan queue:work --queue=high,default,low

Prioritize queues. Payment notifications go to high; newsletter sends go to low.

2. Use Supervisor

Workers crash. Supervisor restarts them automatically. Essential config:

1[program:laravel-worker]
2process_name=%(program_name)s_%(process_num)02d
3command=php /var/www/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
4autostart=true
5autorestart=true
6numprocs=4

3. Set Timeouts

Prevent runaway jobs:

1public $timeout = 120; // Kill job after 2 minutes

4. Monitor with Horizon

For Redis queues, Laravel Horizon is non-negotiable:

1composer require laravel/horizon
2php artisan horizon:install
3php artisan horizon

Real-time metrics, retry management, job tags, and a gorgeous UI. Worth it.

Quick Wins You Can Ship Today

  1. Queue all emails: Mail::to()->queue() instead of send()
  2. Defer notifications: Use ShouldQueue on notification classes
  3. Background image processing: Queue Intervention Image or Spatie Media Library jobs
  4. Async webhooks: Don't block on external API calls
  5. Report generation: Export CSVs and PDFs in background jobs

The Bottom Line

Queues transform slow apps into snappy ones. Your users get instant feedback; your servers handle heavy work asynchronously. Start small—queue your emails today. Then expand to reports, notifications, and integrations.

Your users will thank you. Your servers will thank you. Your on-call rotation will definitely thank you.


A practical guide from the Beartropy team

Comments

Leave a comment

0

No comments yet. Be the first to share your thoughts!

Share this post