Skip to content
Muhammet Şafak
tr
Asked by: Gökhan Answered:

How do I handle poison-pill messages and a Dead Letter Queue in the queue?


Question

On Laravel Queue (Redis), a job throws a Fatal Error every time it runs because of a bug in a third-party library, and it crashes the worker. Laravel auto-retries it and it crashes again; this poison message blocks the whole queue. How do I set up a workflow that separates these bad messages from the main queue, moves them to a Dead Letter Queue for inspection, and alerts the admins?

Answer

Short answer: a job that throws every time and is auto-retried forever locks the queue and crashes the worker — that’s a poison pill. The fix is to bound the retries and route the bad job to a DLQ instead of re-queueing it.

What you’re hitting is classic: the job blows up at the same spot, Laravel says “let me try again,” it blows up again, and the queue can’t move. You have to break the loop.

  1. Bound the retries. Put tries (or maxExceptions) and a backoff on the job. Now it’s attempted 3-5 times, not infinitely. Once it hits the cap, Laravel stops re-queueing it.
  2. failed_jobs is your built-in DLQ. When the cap is reached, Laravel moves the job to the failed_jobs table automatically — no extra infrastructure to build, that table is your Dead Letter Queue. Define a failed() method on the job and put your cleanup/compensation logic there.
  3. Wire up alerting. Write a JobFailed listener that fires a Slack/Sentry notification the moment a job lands in failed_jobs. A silently failing job is the worst case; admins should know instantly. After inspection you replay the fixed job with queue:retry.
  4. Handle Fatal Errors separately. Laravel’s normal retry doesn’t cover a Fatal Error that kills the worker outright (not an exception). Supervisor restarts the worker, but if the same job gets pulled again the loop never closes. For this, keep a “seen this job id N times” counter (in Redis) and force the job into failed_jobs once it crosses the threshold.
  5. Separate queues by risk, make jobs idempotent. Run risky job types on their own queue so one bad job can’t starve all the others. And write jobs to be idempotent so retries are safe — running twice shouldn’t pile up side effects.

Bottom line: capped retries + the failed_jobs DLQ + alerting + replay via queue:retry. Never let any job retry unbounded; unbounded retry turns a single poison message into a weapon that halts the whole system.

Related Reading

Tags: #data#queue#laravel
Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind