A messaging platform that sends SMS, WhatsApp messages, and voice broadcasts has an obvious failure mode to design against: a backlog on one channel shouldn't be able to starve the others. If a large SMS campaign is mid-send, a WhatsApp message a different customer is waiting on shouldn't be stuck behind it in the same queue.
Adcoar Reach's answer is to give each channel — voice, SMS, WhatsApp — its own dedicated queue and its own worker supervisor under Laravel Horizon, rather than one shared queue for everything. Each job declares which channel's queue it belongs to right in its own constructor, so the routing lives next to the job it's routing, not in some separate config a future change could drift out of sync with.
The gap that named queues don't automatically close
Naming three queues explicitly is the easy part. The harder part is remembering that anything which doesn't explicitly declare a queue falls through to Laravel's default queue — and that queue needs its own supervisor watching it just as much as the named ones do. It's easy to reason about the channels you deliberately designed for and forget about the plumbing that was always there by default.
That's exactly what happened while this was being built and tested: the default queue had no supervisor configured at all, and nobody had reason to notice, because Horizon itself reported everything as "running" — it was telling the truth about the three channel supervisors, which really were fine. Password reset emails and outbound webhook calls, both of which fall through to default rather than declaring a channel, sat unprocessed in Redis indefinitely. It surfaced during a routine password-reset test, not from a monitoring alert — which is itself the finding worth sitting with: a queue can be completely stalled while every dashboard signal available says the system is healthy.
Building a monitor that would have caught it anyway
The fix for the immediate bug was one line — add a supervisor for default. The fix for the class of bug was a scheduled health check that doesn't trust "Horizon says it's running" as sufficient: it separately checks whether each queue's backlog has crossed a threshold and whether failed jobs are piling up, and alerts if either happens, regardless of what Horizon's own status page claims. The same three signals are also surfaced live on an internal dashboard, so checking on a queue's health doesn't mean waiting for an hourly alert email.
The general lesson translates well beyond queues: a supervisor that's running is not the same claim as work that's getting done, and a monitoring check is only as good as its willingness to distrust the thing it's monitoring.