Creating and dispatching jobs — make:job, dispatch(), dispatchAfterResponse()
Concept
Job configuration controls retry behavior, timeout, and queue selection. These properties are set on the job class itself or overridden at dispatch time.
$tries: Maximum number of attempts including the first. public int $tries = 3 → if the job throws 3 times, it's marked as failed.
$maxExceptions: Alternative to $tries — limits the number of exceptions before failing, even if retries remain.
$timeout: Maximum seconds a job can run. If exceeded, the worker is killed and the job is released back to the queue. Requires pcntl extension. Default: 60 seconds.
$backoff: Seconds to wait before retrying after a failure. Can be an array for exponential backoff: public array $backoff = [1, 5, 10] → wait 1s, then 5s, then 10s.
$retryUntil(): Method instead of $tries — retry until a specific time. return now()->addHours(2) — keep retrying for 2 hours.
$failOnTimeout: If true, timeout counts as a failure toward $tries. Default: false.
Releasing back to queue: $this->release(int $seconds = 0) — manually put the job back in the queue with optional delay. The attempt count is incremented. Use when you know you should retry later (e.g., external API rate limit response).
$this->fail($exception): Mark job as failed immediately without retrying.
failed(Throwable $exception): Hook called when all retries are exhausted. Use for cleanup, notifications.
Code Example
<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// Retry configuration
public int $tries = 5; // up to 5 attempts
public int $timeout = 30; // 30 seconds max
public bool $failOnTimeout = true; // timeout counts as failure
// Exponential backoff between retries
public function backoff(): array
{
return [1, 5, 30, 60, 120]; // 1s, 5s, 30s, 1m, 2m between retries
}
public function __construct(private readonly int $orderId) {}
public function handle(): void
{
$order = \App\Models\Order::findOrFail($this->orderId);
try {
$result = app(\App\Services\PaymentGateway::class)->charge($order);
} catch (\App\Exceptions\RateLimitedException $e) {
// API rate-limited — release back to queue for 60 seconds
$this->release(60);
return;
} catch (\App\Exceptions\CardDeclinedException $e) {
// Card declined — no point retrying
$this->fail($e);
return;
}
$order->update(['status' => 'paid', 'payment_id' => $result->id]);
}
// Called after all retries are exhausted
public function failed(\Throwable $exception): void
{
$order = \App\Models\Order::find($this->orderId);
$order?->update(['status' => 'payment_failed']);
\Illuminate\Support\Facades\Mail::to($order?->customer)
->send(new \App\Mail\PaymentFailedNotification($order));
}
}
// retryUntil instead of $tries
class SyncCrmJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function retryUntil(): \Illuminate\Support\Carbon
{
return now()->addHours(6); // keep retrying for 6 hours
}
public function handle(): void
{
// Try to sync with CRM — retry until 6h window expires
}
}