• TypeScript 97.9%
  • Dockerfile 2.1%
Find a file
2026-06-17 09:09:47 +02:00
src feat: add /api/push/active/:id liveness probe for reaping dead subscriptions 2026-06-17 09:09:47 +02:00
.dockerignore Initial commit: FCM notification relay 2026-04-20 14:44:26 +02:00
.gitignore Initial commit: FCM notification relay 2026-04-20 14:44:26 +02:00
docker-compose.yml feat: Web Push integration 2026-05-16 21:32:00 +02:00
Dockerfile feat: Web Push integration 2026-05-16 21:32:00 +02:00
LICENSE Use canonical AGPL-3.0 license text for GitHub detection 2026-04-20 14:49:46 +02:00
package-lock.json feat: Web Push integration 2026-05-16 21:32:00 +02:00
package.json feat: Web Push integration 2026-05-16 21:32:00 +02:00
README.md feat: add /api/push/active/:id liveness probe for reaping dead subscriptions 2026-06-17 09:09:47 +02:00
tsconfig.json Initial commit: FCM notification relay 2026-04-20 14:44:26 +02:00

Bulwark Webmail

Bulwark Relay

Push notification relay for Bulwark Webmail. Terminates JMAP PushSubscription pushes from the user's mail server and forwards them to Firebase Cloud Messaging so the mobile app wakes up and fetches new mail over its own JMAP connection.

Designed so self-hosters don't need their own Firebase project, a single hosted instance serves every Bulwark client that opts in. The relay never sees mail content: only opaque FCM tokens, state-id hashes, and timing.

Endpoints

Method Path Purpose
POST /api/push/register Mobile app stores its FCM token against an opaque subscriptionId
POST /api/push/register/web PWA stores a Web Push subscription (endpoint + keys) against an opaque subscriptionId
DELETE /api/push/register/:id Tear down mapping (logout / uninstall)
GET /api/push/verify/:id Poll for the JMAP PushVerification code
GET /api/push/active/:id Liveness probe — { active } if the subscription has forwarded a push (or was just registered); 404 if unknown. Clients use it to reap dead leftover subscriptions without touching live ones
POST /api/push/jmap/:id JMAP server posts PushVerification or StateChange here — relay dispatches FCM or Web Push depending on the stored record
GET /api/push/vapid-public-key Returns the relay's VAPID public key so browsers can subscribe
GET /api/health Liveness probe

What it stores

Per subscriptionId: either an FCM token (mobile) or a Web Push subscription (endpoint + p256dh/auth keys for the PWA), an optional one-shot verification code, an optional free-form accountLabel (max 120 chars), and timestamps. No user identity, no server URL, no mail content. Subscriptions older than 90 days without traffic are evicted automatically.

Run locally

npm install
echo '{...}' > data/fcm-service-account.json   # Firebase service account JSON
npm run dev

Run in Docker

mkdir -p data
cp path/to/fcm-service-account.json data/
docker compose up -d

Environment

Var Default Notes
PORT 3003 HTTP listen port
HOST 0.0.0.0
PUSH_DATA_DIR ./data Where subscriptions.json and the FCM key live
FCM_SERVICE_ACCOUNT_JSON unset Either the full JSON inline or an absolute path — falls back to $PUSH_DATA_DIR/fcm-service-account.json
VAPID_PUBLIC_KEY unset Base64url-encoded P-256 public key for Web Push. Generate with npx web-push generate-vapid-keys
VAPID_PRIVATE_KEY unset Matching private key. Web Push is disabled if either VAPID var is missing
VAPID_SUBJECT mailto:postmaster@localhost mailto: or https: contact the push services can reach if the relay misbehaves (RFC 8292)

License

Licensed under the GNU Affero General Public License v3.0 only. See LICENSE.