Horizon, Reverb, case-study, Laravel, architecture, scheduling, real-time, PostgreSQL
A No-Show Escalation Engine on Laravel 13 and Reverb
**One idempotent hourly job replaced every manual check for a missed intervention, and by construction it cannot escalate the same student twice no matter how many times it runs.**
## Outcome
One idempotent hourly job replaced every manual check for a missed intervention, and by construction it cannot escalate the same student twice no matter how many times it runs.
## Context
This was contract work for a K–12 education platform built on Laravel 13, Horizon, Reverb, and PostgreSQL. I worked with the client team on the scheduling path staff use to manage student support-room interventions; my scope was the no-show detection, escalation, and live-alerting path.
## Problem
Schools assign students to support-room interventions with a due date. When a student does not show, staff have to notice the miss and escalate to the next consequence tier. Tracked by hand across a whole school, misses slip through and escalations land inconsistently. The platform needed a deadline-driven engine that detects overdue sessions on its own, advances each one through a fixed ladder of consequences, and tells the people watching the board the moment something changes. This is the no-show escalation engine, a self-contained body of detection, escalation, and alerting on Laravel 13 with Horizon and Reverb.
## Constraints
- "Overdue" is defined in each school's local day, not server UTC, so the boundary is timezone and DST sensitive.
- Due dates move: sessions get rescheduled or excused, so any decision computed in advance can be wrong by the time it would fire.
- Double-escalating one missed session is a real-world harm, not just a data glitch, so the engine has to be idempotent under retries and races.
- Staff watch live boards that must reflect a new no-show without a manual refresh.
- Contract work under NDA, so figures here are relative.
## Decisions and trade-offs
**Rejected: a delayed job dispatched at session-creation time**, scheduled to fire at the due date. It reads clean until a due date changes. Rescheduled and excused sessions leave queued jobs that must be found and cancelled, and a job queued days out drifts against a school's local clock across a DST change. The queue becomes a second source of truth to reconcile.
**Rejected: a scheduled broadcast that only nudges staff to act.** It pushes the judgment back onto humans and never creates the escalation record itself, which is the whole point.
**Chosen: an hourly poll that reads current state each run.** `MarkOverdueSupportRoomSessions` runs hourly, `withoutOverlapping` and `onOneServer`, and transitions sessions still scheduled past their due date into no-show using the school's local day. Because it re-reads state every hour, a session rescheduled between runs simply no longer matches. There is no stale job to cancel.
Escalation is a separate action, and idempotency lives there. It takes a row-level `lockForUpdate`, guards that the session is still in the no-show state, advances exactly one rung of the ladder, lunch to after-school to Saturday to in-school to out-of-school to restorative, and throws a dedicated exception when the ladder is exhausted. The lock plus the status guard mean a repeated poll, a retried queue job, or a double click all collapse to a single escalation. There is no backoff to tune because there is nothing unsafe to retry. Every state change then emits a broadcast over Reverb so open boards update in place.
## Implementation
I implemented `MarkOverdueSupportRoomSessions` as an hourly, non-overlapping, single-server job within the client team's scheduling platform. The escalation action uses a row lock and status guard before advancing one tier, then broadcasts each committed change through Reverb for live boards.
## Results
Detection is timezone-correct and self-correcting: it always acts on the current schedule, so reschedules and excuses need no cleanup. Escalation is idempotent by construction rather than by convention, which removes the class of bug where a student gets punished twice for one miss. Staff see no-shows and escalations appear live. The manual tracking of missed interventions dropped to zero, and the engine's behavior is now a walkable design with each trade-off written down: poll over delayed job, locked ladder over hopeful retries, broadcast over inbox.
## Takeaways
When the scheduled fact can change, derive it from current state rather than trusting work queued far in advance. For consequences with real-world impact, make retries safe at the state transition itself.