Queues decouple HTTP requests from time-consuming tasks like sending email, generating PDFs, or calling external APIs. Users get fast responses while workers process jobs in the background.
Jobs and Queue Configuration
Generate jobs with php artisan make:job SendWelcomeEmail. Implement ShouldQueue to push instances onto the queue instead of running synchronously.
Configure QUEUE_CONNECTION in .env—database, redis, sqs, or sync for local debugging.
Step 1: Create a queued job
class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function __construct(public User $user) {} public function handle(): void { Mail::to($this->user)->send(new Welcome($this->user)); } }
Dispatch with SendWelcomeEmail::dispatch($user) after registration. Chain or batch jobs for workflows with Bus::chain([…]).
SendWelcomeEmail::dispatch($user);
Step 2: Configure Redis queue
Set QUEUE_CONNECTION=redis and run a worker: php artisan queue:work redis –tries=3. Redis offers better throughput than database driver for busy apps.
Run multiple workers supervised by systemd or Horizon. Match –queue priorities when separating emails from imports.
QUEUE_CONNECTION=redis
php artisan queue:work --tries=3
Step 3: Job middleware and retries
public $tries = 5; public $backoff = [10, 30, 60]; controls retry timing. failed() handles permanent failure logging or notifications.
Use middleware like WithoutOverlapping to prevent duplicate processing of the same invoice.
public function backoff(): array { return [10, 60, 300]; }
Step 4: Delayed and scheduled dispatch
ProcessOrder::dispatch($order)->delay(now()->addMinutes(5)); debounces rapid events. Schedule recurring jobs in routes/console.php with Schedule::job.
Timezone awareness matters for delay() when servers run UTC but business rules use local time.
NotifyUser::dispatch($user)->delay(now()->addHours(1));
Horizon Dashboard and Supervision
Laravel Horizon provides a Redis queue dashboard with throughput graphs, failed job inspection, and balanced worker pools.
Horizon requires Redis and stores metrics in Redis keys configured in config/horizon.php.
Step 1: Install Horizon
composer require laravel/horizon && php artisan horizon:install && php artisan migrate. Access dashboard at /horizon protected by gate.
Define Horizon::auth in HorizonServiceProvider to limit dashboard access to ops team emails.
Horizon::auth(function ($request) {
return $request->user()?->is_admin;
});
Step 2: Supervisor configuration
Run php artisan horizon in production under Supervisor with autorestart=true. Horizon manages worker processes per queue configuration.
Deploy hook should run horizon:terminate gracefully so workers reload code after release without killing active jobs mid-handle.
command=php /var/www/artisan horizon
Step 3: Queue balancing
Configure production environment in horizon.php with auto balancing across queues: default, emails, webhooks. scale process counts per queue load.
Separate long-running import queues from quick notification queues so slow jobs do not starve fast ones.
'queue' => ['default', 'emails', 'imports'],
Step 4: Metrics and alerts
Horizon snapshots job throughput and wait times. Export metrics to Datadog or Prometheus via custom listeners when SLOs require paging.
Set alert thresholds when queue wait exceeds seconds for customer-facing email flows.
php artisan horizon:snapshot
Failed Jobs and Idempotency
Failed jobs land in failed_jobs table. Retry with php artisan queue:retry all or inspect exceptions in Horizon UI.
Design jobs to be idempotent because retries may run handle() more than once.
Step 1: Handle failures
Implement failed(Throwable $e) to notify Slack or mark parent records as failed. Log context like order ID for support.
Use $this->fail($e) to mark job failed without retry when business rules detect unrecoverable data.
public function failed(Throwable $e): void {
Log::error('job failed', ['id' => $this->orderId]);
}
Step 2: Retry and flush
php artisan queue:retry 5 retries one job by ID. queue:flush deletes all failed jobs after fixing root cause.
Never blindly retry payment capture jobs without idempotency keys—duplicate charges harm customers.
php artisan queue:retry all
Step 3: Idempotent job design
Check processing flags before work: if ($order->exported_at) return; Perform external API calls with idempotency-key headers.
Database unique constraints prevent duplicate side effects when two workers race the same job.
if ($this->order->refresh()->shipped_at) { return; }
Step 4: Testing queued jobs
Bus::fake() asserts jobs were dispatched without running them. Queue::fake() for lower-level assertions.
Use sync driver in unit tests when you need handle() to run immediately.
Bus::fake();
// action
Bus::assertDispatched(SendWelcomeEmail::class);
Aadyanex operates Laravel queues with Horizon monitoring, idempotent job design, and on-call runbooks for failed job recovery at scale.
