2026 · Case study · Phill Morgan
Real-Time SaaS Monitoring Platform
A multi-tenant uptime monitoring service: five probe types, live WebSocket dashboards, sixteen notification channels, escalation chains, public status pages, and multi-currency Stripe billing.
Available for portfolio review on request.
Stack
- Laravel 12
- Astro 5
- React 19
- TypeScript
- Tailwind CSS
- shadcn/ui
- MySQL
- Redis
- Docker
- Laravel Horizon
- Laravel Reverb
- Stripe Cashier
- Recharts
Role & Scope
Solo build. Full-stack: Laravel API, Astro frontend, multi-tenant architecture, Stripe Cashier billing, real-time WebSocket dashboards, sixteen notification drivers.
Overview
A production-grade monitoring SaaS that tracks uptime, performance, and availability across HTTP, TCP, DNS, Ping, and Heartbeat probes. Organisations configure monitors, set alert rules with escalation chains, and receive notifications through any of sixteen integrations, from Slack and PagerDuty to SMS and voice calls.
The platform runs as a Docker Compose stack: a Laravel 12 API, an Astro 5 frontend with React islands, Horizon queue workers, a Reverb WebSocket server, MySQL, and Redis, all behind a Caddy reverse proxy.
Key decisions
Laravel Reverb over Pusher for real-time transport. The obvious choice for a Laravel app with WebSocket needs is Pusher: zero ops, paid per connection. I chose Reverb, Laravel’s first-party self-hosted WebSocket server, for two reasons: cost scales with infrastructure rather than with concurrent connections (material once a SaaS has thousands of dashboard viewers), and keeping the real-time layer inside the same Docker stack means one less external dependency, one less outage failure mode, one less vendor contract. The trade was accepting the ops burden of running a WebSocket server: autoscaling, Redis pub/sub adapter for horizontal scaling, TLS termination. The lift was a week of work against an open-ended vendor cost.
Three separate Horizon queue supervisors over a single queue. A single Horizon queue would have worked. Splitting into three (default for general app work, checks for probe execution with dedicated 5 workers, notifications for alert dispatch with retries) means a spike in monitor checks can’t starve the notification path, and a slow notification channel (SMS provider timing out) can’t back up the probe schedule. The trade is three sets of tuning parameters to maintain and three Horizon supervisor configs; the benefit is that each queue has a single failure domain, which matters when the product itself is uptime-monitoring.
Astro islands over a full SPA for the dashboard. The dashboard has genuinely interactive surfaces (real-time monitor timelines, response-time charts, live status indicators) but also a lot of pages that are mostly content (organisation settings, billing, team admin, incident history). A full React SPA would have meant shipping hundreds of kilobytes of JavaScript to load a billing page. Astro islands let each page server-render fast, and hydrate only the specific components that need interactivity. The trade was a slightly more complex build target and the discipline of deciding what needs to be an island versus static; the benefit was cold-load performance that didn’t regress as the dashboard grew.
Monitoring Probes
Five probe types cover the common monitoring needs:
- HTTP/HTTPS: configurable method, headers, authentication, body content matching, and SSL certificate expiry checks
- TCP: port connectivity for services that don’t speak HTTP
- DNS: record resolution against expected values
- Ping: ICMP connectivity and latency
- Heartbeat: cron job and scheduled task monitoring with configurable grace periods. If a heartbeat doesn’t land inside the expected window, an incident fires
Monitors support grouping, cron-style maintenance windows, and grace periods to suppress false alarms during brief transient failures.
Real-Time Dashboard
Check results broadcast the instant they complete, via Laravel Reverb, the first-party WebSocket server. The Astro frontend subscribes to monitor channels through Laravel Echo, so dashboard metrics, response time charts, and status indicators update without polling.
The frontend uses Astro’s island architecture: pages server-render for a fast initial load, and React components (monitor timelines, Recharts response time graphs, real-time status indicators) hydrate only where interactivity is needed.
Twenty pages cover the full surface: auth flows, monitor management, incidents, alerting, team admin, billing, organisation settings, and public status pages.
Alerting and Escalation
The alerting system is built around rules, channels, and escalation chains:
- Alert rules: trigger on specific events (down, recovered, SSL expiry) with configurable delay to swallow brief flaps, and repeat intervals for ongoing incidents
- Notification channels: sixteen drivers including Slack (webhook and bot token with threads), Discord, Microsoft Teams, Telegram, PagerDuty, OpsGenie, Google Chat, Pushover, Mattermost, SMS, voice calls, email, webhooks, and Zapier
- Escalation chains: multi-step sequences that walk through channels when incidents aren’t acknowledged inside defined timeframes
- Coverage matrix: a visual overview showing which monitors are covered by which rules and channels, so gaps are obvious before they bite
Every notification channel tracks its own health from send success rates over the previous twenty-four hours, so a degraded integration surfaces before it causes a missed alert.
Incident Management
When a monitor fails, an incident is created and tracked through its lifecycle:
- Incidents raise after configured failure thresholds are crossed
- Escalation chains advance through steps until acknowledgement
- Recovery events auto-resolve and fire recovery notifications
- Full history is retained: creation, escalation steps, acknowledgement, resolution
Multi-Currency Billing
Stripe integration via Laravel Cashier manages subscriptions with multi-currency support:
- Plan tiers: feature limits per plan (monitor count, check frequency, retention period, notification channels)
- Multi-currency: pricing per currency with Stripe Price IDs mapped to tiers, for localised checkout
- Checkout: Stripe Checkout sessions with webhook-driven subscription state
- Billing portal: self-service plan changes, payment method updates, invoice history
Background Processing
Laravel Horizon manages three dedicated queue supervisors:
- Default: general application jobs, auto-scaling between one and ten workers
- Checks: monitor probe execution, five dedicated workers
- Notifications: alert dispatch with retry logic and exponential backoff for failed deliveries
The scheduler dispatches checks on their configured intervals and evaluates heartbeat monitors for missed check-ins. Horizon’s dashboard surfaces queue throughput, job failure rates, and worker utilisation.
Infrastructure
The Docker Compose stack runs eight services:
- Caddy: reverse proxy with automatic HTTPS, routing API traffic to Laravel and everything else to the Astro frontend
- App: Laravel API on PHP 8.5
- Worker: Horizon process running all three queue supervisors
- Scheduler: Laravel scheduler dispatching recurring checks
- Reverb: WebSocket server for real-time broadcasting
- Frontend: Astro dev server with React and Tailwind
- MySQL 8.4: primary data store, sixty-two migrations defining the schema
- Redis: queue backend, cache, broadcasting driver, Reverb scaling adapter
Multi-Tenancy and Teams
Organisations are the tenancy boundary. Users belong to organisations with role-based permissions. Team management, invitations, and per-organisation billing live in dedicated admin pages. Public status pages are configurable per organisation, surfacing monitor group statuses to the outside world without any auth.