Skip to content

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

  1. An admin opens the SafeCall dashboard.
  2. The Ntfy Notifications card shows the configured ntfy server and SafeCall alert topics.
  3. The admin selects the topics to include and generates a keryx QR code.
  4. keryx scans the QR code, stores the server URL and selected topics locally, and starts receiving SafeCall alerts.
  5. 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 alerts
  • sos: panic button alerts
  • watches: watch button alerts
  • batteries: low battery alerts
  • notseen: 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 Active and Archive
  • Active: unread notifications only
  • Archive: notifications marked read or manually archived
  • A Mark all notifications read action that moves every active notification to the archive
  • A Delete all archived action 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_light displayed 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 Advanced section 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