Skip to content

Guide: Background Jobs and Scheduling

This guide moves a slow task (sending a welcome email) out of the request, runs it with a worker, and schedules a nightly cleanup.

For development the default sync driver runs jobs immediately. To really use a queue, use the database driver:

QUEUE_CONNECTION=database
Terminal window
php naluz migrate # creates the jobs and failed_jobs tables
Terminal window
php naluz make:job SendWelcomeEmail
app/Jobs/SendWelcomeEmail.php
namespace App\Jobs;
use App\Models\User;
use Naluz\Mail\Mailer;
use Naluz\Queue\Job;
final class SendWelcomeEmail extends Job
{
protected int $tries = 5;
protected int|array $backoff = [10, 60, 300];
public function __construct(public readonly int $userId) {} // public properties are the job data
public function handle(Mailer $mailer): void // dependencies are injected
{
$user = User::findOrFail($this->userId);
$message = $mailer->message()->to($user->email, $user->name)->subject('Welcome!')->text("Hi {$user->name}");
$mailer->send($message);
}
public function failed(\Throwable $e): void
{
logger()->error('Welcome email failed for {id}', ['id' => $this->userId, 'exception' => $e]);
}
}

Pass IDs and re-load the model inside handle() so the payload stays small and fresh.

SendWelcomeEmail::dispatch($user->id);
dispatch((new SendWelcomeEmail($user->id))->onQueue('mail')->delay(60));
Terminal window
php naluz queue:work --queue=default --sleep=3

A worker finishes the current job on SIGTERM/SIGINT (when pcntl is installed). Run workers under a supervisor in production. See Deployment.

Inspect and retry failures:

Terminal window
php naluz queue:failed
php naluz queue:retry all
routes/console.php
return function (Schedule $schedule): void {
$schedule->command('queue:work --stop-when-empty')->everyMinute()->withoutOverlapping();
$schedule->job(new App\Jobs\PruneOldRecords())->dailyAt('03:00');
};

Add one cron entry on the server:

* * * * * cd /path/to/app && php naluz schedule:run >> /dev/null 2>&1
Terminal window
php naluz schedule:list # tasks and their next run

Use the sync driver in tests, or call the worker once:

SendWelcomeEmail::dispatch($user->id);
// with queue.default=sync the job has already run; assert on ArrayTransport::$sent
  • Payloads are encrypted with APP_KEY; rotating the key without previous_keys sends queued jobs to failed_jobs.
  • Do not put secrets in job properties; pass IDs.