Mark Lowel Montealto — Full Stack Developer & DevOps Engineer

Mark Lowel
Montealto

Full Stack Developer & DevOps Engineer

Blog

July 2025

·

2 min read

Laravel Queues & Jobs: Background Processing That Scales

Offload slow tasks (emails, PDF generation, webhooks) from your request cycle using Laravel Queues, Redis, and Horizon — with retry, failure, and monitoring strategies.

Introduction

  • Synchronous request handling blocks users waiting for email sends, image processing, or third-party API calls.
  • Laravel's queue system pushes work to a background worker process — the HTTP response returns immediately.
  • Stack covered: Laravel 11, Redis as the queue driver, Horizon for monitoring.

Queue Drivers

DriverBest for
syncLocal dev (no worker needed)
databaseSimple deployments (no Redis)
redisProduction — fast, reliable
sqsAWS serverless deployments
  • Set QUEUE_CONNECTION=redis in .env.
  • Install Predis or phpredis: composer require predis/predis.

Creating a Job

  • php artisan make:job SendWelcomeEmail.
// app/Jobs/SendWelcomeEmail.php
class SendWelcomeEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 
    public int $tries = 3;          // retry up to 3 times
    public int $backoff = 60;       // wait 60s between retries
 
    public function __construct(private readonly User $user) {}
 
    public function handle(Mailer $mailer): void
    {
        $mailer->to($this->user)->send(new WelcomeMail($this->user));
    }
 
    public function failed(Throwable $exception): void
    {
        Log::error("SendWelcomeEmail failed for user {$this->user->id}", [
            'exception' => $exception->getMessage(),
        ]);
    }
}

Dispatching Jobs

// Dispatch to default queue
SendWelcomeEmail::dispatch($user);
 
// Dispatch to a named queue with a delay
SendWelcomeEmail::dispatch($user)
    ->onQueue('emails')
    ->delay(now()->addMinutes(5));
 
// Dispatch synchronously (bypass queue, useful in tests)
SendWelcomeEmail::dispatchSync($user);

Running Workers

# Basic worker
php artisan queue:work redis --queue=emails,default --tries=3
 
# Process one job and exit (good for Kubernetes Jobs / cron)
php artisan queue:work --once
 
# Daemon mode with graceful timeout
php artisan queue:work --timeout=90 --sleep=3

Queues with Laravel Horizon

  • Horizon: a dashboard + supervisor for Redis queues.
  • composer require laravel/horizon && php artisan horizon:install.
  • Configure worker pools in config/horizon.php:
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue'      => ['emails', 'default'],
            'balance'    => 'auto',
            'processes'  => 10,
            'tries'      => 3,
        ],
    ],
],
  • Start Horizon: php artisan horizon (manage with Supervisor in production).
  • Dashboard at /horizon — monitor throughput, failed jobs, wait times.

Failed Jobs

  • Failed jobs are stored in the failed_jobs table (or Redis).
  • php artisan queue:failed — list failed jobs.
  • php artisan queue:retry {id} — retry a specific job.
  • php artisan queue:retry all — retry all failed jobs.
  • Set up FailedJobsProvider to notify Slack/email on failure.

Job Chaining and Batches

// Chain: job B runs only after job A succeeds
Bus::chain([
    new ProcessUpload($file),
    new GenerateThumbnail($file),
    new NotifyUser($user),
])->dispatch();
 
// Batch: fan-out parallel jobs with a final callback
$batch = Bus::batch([
    new ProcessChunk($chunk1),
    new ProcessChunk($chunk2),
])->then(fn(Batch $b) => NotifyCompletion::dispatch())
  ->catch(fn(Batch $b, Throwable $e) => Log::error($e))
  ->dispatch();

Rate Limiting Jobs

  • Use RateLimiter to prevent hammering third-party APIs.
public function handle(): void
{
    Redis::throttle('stripe-api')->allow(100)->every(60)->then(
        fn() => $this->chargeCard(),
        fn() => $this->release(30) // re-queue after 30s
    );
}

Dockerizing Workers

  • Run a separate worker container alongside the app container.
  • CMD ["php", "artisan", "queue:work", "--sleep=3", "--tries=3"] in a worker Dockerfile.
  • In docker-compose.yml: share the same app image, override the command.

Testing Queued Jobs

Queue::fake();
SendWelcomeEmail::dispatch($user);
Queue::assertPushed(SendWelcomeEmail::class);
Queue::assertPushedOn('emails', SendWelcomeEmail::class);

Conclusion

  • Queues are the primary tool for keeping p99 API response times low.
  • Redis + Horizon is the production standard for Laravel — install it before you need it.
  • Monitor failed jobs actively; silent failures = frustrated users.
  • Next steps: add ShouldBeUniqueUntilProcessing to prevent duplicate jobs for idempotent operations.