Appearance
keryx Notification App
keryx is a SafeCall companion Flutter app for receiving building safety notifications after scanning a SafeCall dashboard QR code.
App Identity
- Project path:
keryx/ - Android application ID:
com.sourcectl.keryx - iOS bundle ID:
com.sourcectl.keryx - Display name:
keryx
Provisioning Flow
- An admin opens the SafeCall dashboard.
- The
Ntfy Notificationscard shows the configured ntfy server and SafeCall alert topics. - The admin selects the topics to include and generates a keryx QR code.
- keryx scans the QR code, stores the server URL and selected topics locally, and starts receiving SafeCall alerts.
- After the first scan, the user sees a notification list. Technical details such as topics, server URL, and stream status are hidden from the default UI.
QR Payload
The dashboard generates a versioned JSON payload:
json
{
"type": "safecall.keryx.ntfy.v1",
"ntfy_server_url": "https://notify.example/",
"topics": ["TST000-sos", "TST000-batteries"],
"labels": {
"TST000-sos": "Panic button alerts"
},
"debug_access": false
}keryx rejects QR codes without the expected type, a valid http or https ntfy server URL, or at least one topic. The optional debug_access field defaults to false. When enabled by the dashboard QR generator, the user can view topic names, ntfy message IDs, sent times, priority, tags, and the raw notification JSON. Standard QR codes only expose the notification message and received time on the detail screen.
Topic Catalog
SafeCall exposes these topics through GET /features as ntfy_topics:
sensors: temperature and humidity out-of-range alertssos: panic button alertswatches: watch button alertsbatteries: low battery alertsnotseen: device not-seen alerts
The full ntfy topic name is {server_id}-{topic_id}.
User Experience
Unconfigured users see:
- Title:
No notifications have been enabled yet - Help text directing them to ask a SafeCall admin for a QR code
- Primary action:
Scan QR code
Configured users see:
- A bottom tab bar with
ActiveandArchive Active: unread notifications onlyArchive: notifications marked read or manually archived- A
Mark all notifications readaction that moves every active notification to the archive - A
Delete all archivedaction that hides archived notifications while retaining local tombstones so cached ntfy messages are not reintroduced as active - Per-notification actions to view details, archive, or delete
- ntfy emoji tags in notification cards and OS notifications, such as
rotating_lightdisplayed as 🚨 - An app icon badge count that reflects only active notifications; the badge is cleared while notifications are paused and skipped on Android launchers that do not support numeric badges
- A settings screen with an
Advancedsection for pausing/resuming notifications, scanning a new QR code, and resetting the app
Reset App removes the QR configuration and local notification history. It does not change SafeCall server settings.
Notification Delivery
keryx uses a vendored ntfy Flutter plugin path dependency for native subscriptions:
- Android starts a foreground service so SafeCall alerts can continue while the app is minimized or the screen is off.
- Android persists the ntfy subscription natively and attempts to restart the foreground service after device reboot or app package update. A user force-stop still prevents Android from restarting the app until keryx is opened again.
- Some Android OEM battery managers can stop long-running foreground services. If alerts stop after the app is closed, allow unrestricted battery use for keryx in Android settings.
- iOS can stream while the operating system permits background work. Reliable killed-state delivery requires ntfy server upstream push support; APNs wakes keryx with a
poll_request, then keryx polls the configured ntfy topics for cached messages. - On startup and app resume, keryx polls ntfy's cached message API with
?poll=1&since={lastMessageId}to recover missed cached alerts. - Recent active and archived alerts are stored locally on the device, capped to the latest 200 visible messages. Deleted notifications leave small local tombstones so recovered cached alerts are not reintroduced as active notifications.
ntfy's cache is time-limited by server configuration. keryx can recover messages still present in that cache, but it does not provide unlimited notification history.
iOS Push Prerequisite
Real-time iOS delivery when the app is killed requires the ntfy server to be configured for Firebase/APNs upstream push delivery and message caching. Keryx registers for remote notifications and treats ntfy poll_request pushes as a wake signal to poll the configured topics for missed cached messages.
If the configured ntfy server does not have upstream push support, iOS devices fall back to best-effort stream delivery while the app is alive plus poll-on-resume recovery.
SafeCall still publishes alerts to ntfy topics; no SafeCall server API changes are required for the Keryx app update.
Implementation Note
keryx keeps QR parsing and local persistence in app source, while NtfyStreamService wraps the vendored native ntfy plugin for background subscriptions and uses direct HTTP polling for cached-message recovery.
Publishing
See Publishing keryx for Apple App Store and Google Play release steps.
Validation
bash
bun analyze:keryx
bun test:keryx
bun build:keryx