paulund

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 $timeout on jobs that call external services, so a hung request does not block the worker indefinitely.
  • Use the ShouldBeUnique interface if you want to prevent duplicate jobs from being dispatched while one is already pending or running.
  • In tests, set QUEUE_CONNECTION=sync so jobs execute immediately and you can assert their effects without starting a worker.