Batelys
Lone worker safety, one button at a time
Batelys protects people who work alone. You check in when you arrive, then press a button now and then to confirm you are fine. Miss a check-in — because you fell, collapsed, or simply cannot reach your phone — and an alert leaves on its own, with your position, escalating through your chain until someone responds.
- My role
- Solo — product, architecture and development
- Built with
- TypeScript
- Next.js
- React
- Node.js
- PostgreSQL
- REST API
- Docker
Vision
Why this exists
A builder on a site, a technician in a plant room, a night-shift caretaker. They work alone, and when something goes wrong the problem is not that nobody cares — it is that nobody knows yet. The gap between an accident and someone noticing is the whole thing.
Batelys closes that gap with one mechanism: a dead man's switch. Check in at the start of the shift, confirm you are fine at intervals, and if a confirmation does not arrive, the system assumes the worst and acts. There is also a panic button for when you know something is wrong and want help now.
Everything else — the dashboards, the exports, the reporting, the roster management — is packaging around that single idea. I keep coming back to that when scoping features, because a safety product that becomes complicated is a safety product people stop using correctly.
Objectives
What it has to get right
Constraints stated up front, so the design decisions further down have something to be measured against.
Detect silence, not just emergencies
The people most at risk are the ones who cannot call for help. A missed check-in has to be as loud as a pressed button.
Escalate until someone answers
One notification to one supervisor is not a safety system. It has to climb the chain and end in a phone call.
Work on bad networks and old phones
The environments where this matters have concrete walls, no signal and a five-year-old Android. That is the target, not the edge case.
Collect the minimum
Continuous location tracking of employees is surveillance. Position is captured when an alert fires — not all day.
Interface
What it looks like
/projects/batelys/worker.png
/projects/batelys/incident.png
/projects/batelys/dashboard.png
Features
What it does
Shift check-in
Open the app, confirm presence, and the monitoring window starts. Clocking out ends it — no forgotten sessions raising false alarms at 2am.
Periodic confirmation
A single 'I'm fine' button at a configurable interval, with a grace period and a loud local reminder before anything escalates.
Automatic alert on silence
When the grace period expires the alert fires on its own, carrying the last known position and the time of the last confirmation.
Panic button
Immediate manual alert, no confirmation dialogue, reachable without unlocking into a menu.
Escalation chain
Supervisor first. No acknowledgement inside the window, and it moves to the next contact, then triggers an automated voice call.
Supervisor dashboard
Who is on shift, who is due to check in, and the full timeline of any incident — for the response, and afterwards for the report.
Architecture
How it is put together
This is the only project I have built where the failure mode is a person on the floor and nobody knowing. That reframes everything: availability is a safety property, an alert path is not allowed a single point of failure, and 'it retried and eventually worked' is not good enough if 'eventually' is twenty minutes.
Worker app
Deliberately small surface: check in, confirm, panic. Queues events locally when offline and replays them on reconnect, and warns the worker locally before a missed confirmation escalates.
- React
- PWA
- Background sync
Monitoring service
Holds a deadline per active shift and reacts when one passes. Deadlines are persisted, not held in a process — a restart must never silently drop someone's timer.
- Node.js
- Job scheduling
- PostgreSQL
Escalation engine
A state machine per incident: notify, wait for acknowledgement, escalate, repeat, then place an automated call. Every transition is recorded so an incident can be reconstructed exactly.
- State machine
- Push
- SMS
- Voice API
Supervisor console
Live shift status, incident timelines and exports. Read-heavy and cacheable, and intentionally separate from the alert path so a dashboard outage cannot affect alerting.
- Next.js
- TypeScript
- REST API
Decisions worth defending
Deadlines live in the database, not in memory
The naive version keeps timers in the process. Then the process restarts during a deploy and everyone's monitoring window quietly disappears. Persisted deadlines mean any instance can pick up an overdue check-in.
Alerting and dashboards are separate paths
The console is where features accumulate and regressions get introduced. It must not be able to take alerting down with it.
The client fails towards raising an alert
Ambiguity resolves in favour of alerting. A false alarm costs someone a phone call; a missed alarm costs considerably more.
Location on alert only
Continuous tracking would be simpler to build and much harder to justify. Capturing position when an incident fires gives responders what they need without turning the app into a tracker.
Every escalation step is an audit record
After an incident, the question is always 'who was told, when, and did they respond'. That answer has to come from stored events, not from log grepping.
Challenges
The parts that were actually hard
Problem, what I did about it, and where that landed. Including the ones still open.
Problem
A missed check-in and a phone with no signal look identical from the server's point of view.
Approach
Distinguishing them on the client. The app knows it is offline, warns the worker loudly and locally, and queues the confirmation. The server treats prolonged silence as an incident regardless — but the timeline records which of the two it was.
Outcome
Fewer false alarms from dead zones, without ever using 'probably just bad signal' as a reason not to escalate.
Problem
Mobile operating systems aggressively suspend background apps, which is precisely what a dead man's switch cannot tolerate.
Approach
Moving the authority for the deadline to the server and using the client only to confirm. If the client is killed, the server still knows a confirmation is overdue.
Outcome
Escalation does not depend on the app staying alive — the exact scenario where the worker is unable to interact with it.
Problem
Notification delivery is best-effort. A safety system cannot be best-effort.
Approach
Layered channels with explicit acknowledgement: push, then SMS, then an automated voice call, each requiring a positive response before the chain stops.
Outcome
No single provider outage silences an alert, and 'nobody acknowledged' is itself an event that escalates.
Problem
Alert fatigue quietly kills these systems. Too many false positives and people stop responding.
Approach
Generous grace periods, an unmistakable local reminder before escalation, and one-tap cancellation with a reason — feeding back into interval tuning.
Outcome
Alerts that fire stay credible, which is what makes the escalation chain work at all.
Learned
What it taught me
Availability stops being a performance metric and becomes a safety requirement. It changes how you deploy, how you monitor, and what you are willing to ship on a Friday.
Anything holding a deadline in process memory is a bug waiting for a restart.
Regulation around lone worker protection and around employee geolocation pull in opposite directions, and the resolution is designing for data minimisation from the start rather than bolting on consent later.
Writing down what happens when each dependency fails — network, push provider, SMS gateway, the app itself — surfaced more design problems than any amount of feature planning.
For a product like this, the highest-value engineering is in the boring reliability work nobody sees.
Roadmap
Where it goes next
Shipped, in progress and planned — kept honest about which is which.
Core safety loop
- Shift check-in and periodic confirmation
- Server-side deadlines with persistence
- Panic button
- Push and SMS escalation with acknowledgement
Response and reporting
- Automated voice call as final escalation step
- Supervisor console with live shift status
- Full incident timelines and exports
- Configurable intervals and escalation chains per team
Field readiness
- Offline-first hardening across the worker app
- Fall detection using device sensors
- Data retention and access controls aligned with GDPR
- Independent review of the alert path
Next
Other projects
Hackuten
A CTF platform for learning cybersecurity
Hackuten is a capture-the-flag platform covering several areas of cybersecurity — web, reverse engineering, cryptography, forensics and more. It exists to make the first steps into the field less discouraging: challenges that teach something specific, in an order that makes sense, with infrastructure that does not get in the way.
Read moreOrbyte
An end-to-end encrypted messenger
Orbyte is an encrypted messaging application in the spirit of Session: private conversations that do not require a phone number to start, and a server that is designed to know as little as possible. It is in active development — the parts that are built are built carefully, and the parts that are not are honestly labelled as such.
Read more
