spokospace/filament-ops-notify
spokospace/filament-ops-notify is a Laravel/Filament package that adds operational notifications to admin panels, helping teams surface alerts, status updates, and system events directly in the dashboard so issues are noticed, triaged, and handled faster without leaving the panel.
This is an ops-alerting package: it gives you a single OpsMessage::send() entry point, an ops notification channel, a burst guard for noisy events, and a digest job that batches suppressed alerts. Alerts land in chat destinations (channels/topics) and optionally in the database for the in-app panel.
Start in src/ in this order:
OpsMessage — the facade/entry point. Read send() first; that is the call you will use 95% of the time.BurstGuard — the dedupe/burst logic. This is where the subtle behaviour lives (TTL, held counts, digest handoff).ops notification channel — only needed if you route alerts through Notifiable models rather than OpsMessage directly.ops-notify:test command — your day-one debugging tools.Minimum viable use, from anywhere in the app (controller, job, listener):
use OpsMessage;
OpsMessage::send('Deploy finished', event: 'deploy.finished');
OpsMessage::send(
'Invoice sync failed',
event: 'billing.sync.failed',
destination: 'ops-alerts',
);
Two rules to internalise before you wire it up:
array driver gives every PHP process its own counters.send() is genuinely non-throwing: an unreachable cache logs and sends anyway, so a duplicate beats a lost alert. You no longer need to wrap every call site in try/catch or dispatch a ShouldQueue job just to protect the host request. Saving an inquiry will not 500 because Redis blinked.First real use case: replace your ad-hoc Slack::send(...) / Log::critical(...) pairs at the top of the funnel (build failures, payment errors, contact-form submissions) with one OpsMessage::send() per event and let the burst guard handle the noise.
Fire-and-forget from the happy path. Because delivery failures are swallowed and logged, put the call inline in the action that matters — after the inquiry is persisted, at the end of a failed deploy job, in the failed() hook of a queue job. Don't fan it out to a separate queue just for safety.
Burst guard for noisy events. Use the guard for anything that can fire in clusters (a flapping integration, a queue backlog, a broken webhook):
OpsMessage::send('Webhook rejected', event: 'webhook.rejected', destination: 'integrations');
The guard suppresses beyond the window; suppressed rows are held and rolled into a digest. Two behaviours to rely on after this release:
One destination per logical stream. The burst key now includes the destination, so the same event sent to two chats/topics gets two independent counters and two correct digests. Previously these shared a counter and could send the digest to the wrong place. If you previously worked around that by suffixing the event name, drop the workaround.
Notification channel for per-user routing. Use the ops channel when the recipient is a Notifiable (an on-call user, a team model). It now shares the same cache guard as OpsMessage, so a dead cache no longer aborts Laravel's loop over notifiables and drops database/mail for everyone after the first recipient. In practice: keep ops in via() alongside database and mail, and trust that a cache outage degrades to "no chat ping" rather than "no notification at all".
Digests as a first-class workflow. Treat the digest as the low-priority, aggregated counterpart to the immediate alert. Titles in the digest are matched against the truncated event name, filtered by destination, and bounded to the window — so long event names list correctly, and a digest that runs late does not pull rows from the next window. If you use long, descriptive event names, keep them stable; they are the join key.
Panel resend as an operational tool. Resend now only reports "Queued" when something was actually queued. A burst-held or failed resend keeps the row Failed with its Resend button live — which means the panel is trustworthy as a status board during an incident, not just a "click and hope" button.
CI / deploy smoke test. Wire ops-notify:test --queue into a post-deploy check. It exits non-zero when nothing was queued, so it fails the step instead of printing a false success.
send() in your own try/catch and dropping the alert.BurstGuard::pullHeld() is gone, replaced by heldCount() + clearHeld(). Only relevant if you subclass or call BurstGuard directly — normal use is unaffected. No config changes, no migrations, nothing to run on upgrade.src/ reliability pass with no breaking surface for normal use. Schedule it normally; the only thing worth watching post-deploy is whether your cache outage log starts talking.How can I help you explore Laravel packages today?