← work / signaldesk
SignalDesk
The internal portal the platform is actually run from
// the problem
Operating Plus234Feed by hand does not scale: content, newsletters, publishing, API customers and pipeline health each needed a surface, and a person on duty needs them in one place. SignalDesk is that control plane.
// context
Built for the person on duty rather than for readers. It sits on three backends (its own API, the newsletter service and the public content API), all over the one Supabase database, so it adds operator surfaces without duplicating the platform.
// what I built
- A portal shell where each feature is a self-contained module with its own routes, API layer and lazy-loaded pages: Content, Newsletter, Intel, Analytics, Operations and Publish, registered in one place so a new module is a registry entry rather than a rewrite.
- Its own FastAPI backend, separate from the public API: 85 endpoints across 13 route modules, JWT auth through Supabase, and admin, manager and viewer roles on privileged routes.
- Newsletter operations with an approval gate (prepare, approve, then a cron sends), plus dry-run and test-send, subscriber counts by acquisition source, per-recipient delivery results, and GA4 UTM attribution showing which campaign drove sessions.
- An Operations view over automation health: success rate, average duration and last status per job, normalised across cron runs and scrapers, with a daily platform-health record and its trend.
- Health that refuses to look fine: a stale health run is reported as stale rather than rendered as healthy, because the failures worth catching all reported success at the time: scrapers returning 200 while inserting nothing, pageviews collapsing after a deploy, push going out at 0% CTR.
- An Intel module for the data API: usage, customers and API-key management.
- Service integrations behind the pages: AI-written drafts and captions, IndexNow submission, UTM tracking, X publishing, URL shortening and cache purging.
// key decisions
- Module registry with lazy-loaded routes
Each module declares its base path, routes and backend in one registry file, and every page is lazy-loaded. Adding a module is a folder and a registry entry rather than a change to shell routing. The cost is one central file that has to stay current.
- The cron sends only what a person approved
An issue is prepared from live data and approved in the portal; the Friday cron sends only approved items and expires unapproved drafts. A missed approval skips an issue rather than sending an unreviewed one. The send page runs a dry-run on load, and a real send needs a typed confirmation.
- A stale health record is shown as stale
The health endpoint only reads what the 3-hourly monitoring job recorded and never re-runs checks. After 7 hours without a run, or on a query error, it returns stale rather than the last green result, because a stale green panel is worse than no panel.
- Reuse existing run tables for Operations
The Operations view merges the existing cron_runs and scrape_runs tables into one normalised list instead of adding new logging infrastructure. It can only report what those jobs already record, which keeps it cheap to maintain.
// results
- 106 API endpoints across 16 route modules today.
- 6 lazy-loaded modules with 26 routes.
- 119 commits between January and October 2026.