Skip to main content

Notifications Live

Two channels — Web Push (VAPID) and Expo Push (mobile native) — plus transactional email via the central email lib.

1. Overview

When something happens (new order, message, prescription, broadcast), we attempt push to all subscribed channels. Web Push uses VAPID keys; mobile uses Expo Push. Both fire from src/lib/push.ts with a single helper that fans out.

2. User journey

Opt-in

  1. First visit: no notifications (don't auto-prompt — breaks iOS)
  2. After PWA install OR via Settings → "Enable Notifications" tap
  3. Browser permission prompt → approved → push token registered
  4. From then on, server-initiated notifications appear

Opt-out

  • Settings → blocks at OS level
  • Server still tries; the failed sends are logged

3. Web ↔ Mobile parity

CapabilityWebMobile
Order updates✅ Web Push✅ Expo Push
Reel likes / comments
Request thread reply✅ deep links to /requests?openThread=X
Doctor broadcasts
Email fallback✅ via Email Infra✅ same

4. Key components

  • src/lib/push.ts — central send helper (sendPushToConsumer, sendPushToSeller, sendWebPush, sendExpoPush)
  • public/sw.js — Service Worker for Web Push
  • src/components/PushSubscriber.tsx — registers Web Push (only auto-subscribes if permission already granted)
  • src/components/PostInstallSetup.tsx — tap-triggered permission flow for standalone PWA
  • mobile/lib/notifications.ts — Expo Push registration

5. APIs

EndpointMethodPurpose
/api/notifications/subscribePOSTRegister Web Push subscription
/api/notifications/expo-tokenPOSTRegister Expo push token (mobile)
/api/notifications/preferencesGET, PUTPer-channel preferences (orders, reels, requests, etc.)
/api/admin/notifications/testPOSTAdmin-only: send a test push

6. Database touchpoints

  • PushSubscription — Web Push subscriptions (endpoint, p256dh, auth, consumerId)
  • PushToken — Expo tokens (consumerId / sellerId / doctorId, token)
  • Notification — in-app inbox rows (consumer notifications)
  • SellerNotification — seller's notification bell
  • DoctorNotification — doctor's notification bell
  • DoctorNotifPrefs — per-channel preferences

7. Business logic

Web Push specifics

  • Uses VAPID keys (VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY)
  • urgency: "high" + TTL: 3600 set for Android heads-up
  • Each notification gets a UNIQUE tag (notifType + "_" + Date.now()) to prevent silent replacement
  • Service worker (public/sw.js) cache key: ka26_sw_clean_v10 — bump to force re-fetch on all users

Mobile push specifics

  • Uses Expo Push (no VAPID needed — Expo manages the cert handshake to FCM/APNs)
  • Tokens registered at app launch + on auth state change
  • Notification tap → mobile/app/_layout.tsx addNotificationResponseReceivedListener routes to the right screen

Doctor broadcast

  • doctorBroadcastToPatients() pushes to BOTH patients AND the doctor (was missing patient push before)
  • Thread replies → /requests?openThread={threadId}&requestId={requestId}
  • Orders → /orders/{orderId}
  • Prescriptions → /profile?tab=health

8. Feature flags / env vars

  • VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_EMAIL — Web Push (required)
  • EXPO_ACCESS_TOKEN — for sending Expo push (optional; the SDK works without it for low volumes)

9. Tests

  • Build integrity covers push subscribe routes
  • No dedicated integration test (would need a real push endpoint)
  • Production smoke covers /api/notifications/preferences GET

10. Known gotchas

  • NEVER auto-request notification permission — breaks iOS which requires a user gesture. The PushSubscriber component only auto-subscribes if permission is ALREADY granted.
  • PostInstallSetup uses key ka26_pwa_setup_v2 in localStorage to track whether the user already went through it. Bump the key if you change the flow.
  • Android Chrome can't show heads-up banners for Web Push — it's a Chrome limitation (not us). Only native apps can. We have a React Native app for that.
  • iOS web push requires iOS 16.4+, standalone PWA mode, Safari only. Most iOS users won't see Web Push notifications.
  • Expo Push has a free tier limit but it's ~1000/day, fine for our scale at launch.
  • Email Infrastructure — fallback for users without push
  • Settings — opt-in UI
  • See docs/EMAIL-INFRASTRUCTURE.md in marketplace repo for the email side