Laravel queues that don't wake you up at 3am
Idempotent jobs, sane retries and the Horizon settings I now copy into every project.

Queues are where good intentions go to fail quietly. A job that works on your machine can still retry forty times in production and email a customer forty times.
Make every job idempotent
Assume every job runs twice. Store a key for the side effect and check it before acting:
if (Payment::where('reference', $this->reference)->exists()) {
return;
}
Retry on purpose
- Set
$triesand$backoffon the job, not globally. - Use
retryUntil()for anything that talks to a flaky third party. - Call
$this->fail()when retrying cannot help.
Separate the queues
Mail, webhooks and heavy reports never share a queue. One slow PDF export should not delay a password reset.
Watch it
Horizon's failed-jobs page is not monitoring. Alert on queue wait time instead — it is the first number to move when something is wrong.
Next note
What a year of Laravel Octane in production taught me→