Telegram alert bot with staged rollout
Baguio Outage Radar
A Telegram bot that tells each subscriber only about the power interruptions that affect the areas they saved, built from a public outage data feed and restored with a dry-run-first rollout.
- Status
- Live in a controlled, owner-only stage since November 2026. Wider release not started. Private.
- Source
- Source: private
The problem
Power interruption notices are published for whole feeders and towns, in text that mentions many place names. Residents and property managers only care about their own area.
The goal was a small service that reads the public outage feed, matches it to the areas each subscriber saved, and sends a timely message, without ever spamming or missing an alert after a crash.
The approach
A Node.js and Express service polls the regional electric cooperative’s public JSON feeds for actual and scheduled outages every five minutes and turns each record into a row with a time window, feeder, area, cause and place names.
A place-name dictionary and whole-phrase matching connect rows to each subscriber’s saved areas. A Telegram bot (long polling) handles subscribe, status, test and unsubscribe commands, plus admin commands.
A password-protected admin dashboard manages subscribers. A separate parser turns advisory PDFs or pasted text into rows, outside the automatic alert path.
It runs as a hardened systemd service on a Linux VPS, from versioned release directories behind a symlink.
Architecture
Text alternative: the flow in words
- Every five minutes the poller reads the public feed and builds outage rows.
- The matcher compares each new row with the areas every subscriber saved.
- The send gate applies the dry-run switch and the recipient allowlist.
- The Telegram bot delivers the alert. The outage is only marked as seen after delivery.
- State lives in atomically written files. systemd restarts the service if it fails.
Key technical decisions
Dry run is the default
Unless explicitly switched off, no Telegram traffic is sent. Would-be messages go to a local outbox file that can be reviewed first.
Staged rollout with an allowlist
Executed in order: dry run on the server, a 24-hour soak, a build with a recipient allowlist, then live for the owner only. The activation script runs its checks, rolls back automatically on failure and starts a 48-hour monitor. Wider release has separate written conditions and has not started.
Never lose or repeat an alert
An outage is marked as seen only after its alerts were sent. A per-subscriber sent log prevents repeats, and the first start on a new server records existing outages silently.
Crash, then let systemd restart
Failures exit the process and the service manager restarts it. State files are written atomically, and poll failures raise an admin alert and a recovery notice.
Rollback and backups built in
Install and rollback scripts switch between release directories, a data backup script keeps a rolling set, and rollback can optionally restore a data backup.
Safety and security notes
- The restoration fixed a set of defects found in the recovered original, each with a regression test: unauthenticated admin routes, an open payment webhook, unescaped user text in messages, and over-eager word-level matching.
- The service listens on loopback only, compares the dashboard password in constant time and keeps the admin closed when no password is set.
- systemd hardening limits writes to the data directory: no new privileges, strict protection of the system and home directories, private temp and devices.
- No tokens, hosts, addresses or subscriber information appear here. Sanitised summary only.
Limits and honest caveats
- Not open to the public. The original ran as a live service during June 2026; the restored version has only been live for the owner so far, and wider release is a separate decision.
- Matching quality depends on the feed’s free text, which is why the dry-run outbox is reviewed before going live.