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
- First visit: no notifications (don't auto-prompt — breaks iOS)
- After PWA install OR via Settings → "Enable Notifications" tap
- Browser permission prompt → approved → push token registered
- 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
| Capability | Web | Mobile |
|---|---|---|
| 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 Pushsrc/components/PushSubscriber.tsx— registers Web Push (only auto-subscribes if permission alreadygranted)src/components/PostInstallSetup.tsx— tap-triggered permission flow for standalone PWAmobile/lib/notifications.ts— Expo Push registration
5. APIs
| Endpoint | Method | Purpose |
|---|---|---|
/api/notifications/subscribe | POST | Register Web Push subscription |
/api/notifications/expo-token | POST | Register Expo push token (mobile) |
/api/notifications/preferences | GET, PUT | Per-channel preferences (orders, reels, requests, etc.) |
/api/admin/notifications/test | POST | Admin-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 bellDoctorNotification— doctor's notification bellDoctorNotifPrefs— per-channel preferences
7. Business logic
Web Push specifics
- Uses VAPID keys (
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY) urgency: "high"+TTL: 3600set 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.tsxaddNotificationResponseReceivedListenerroutes to the right screen
Doctor broadcast
doctorBroadcastToPatients()pushes to BOTH patients AND the doctor (was missing patient push before)
Deep link conventions
- 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/preferencesGET
10. Known gotchas
- NEVER auto-request notification permission — breaks iOS which requires a user gesture. The
PushSubscribercomponent only auto-subscribes if permission is ALREADYgranted. - PostInstallSetup uses key
ka26_pwa_setup_v2in 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.
11. Related
- Email Infrastructure — fallback for users without push
- Settings — opt-in UI
- See
docs/EMAIL-INFRASTRUCTURE.mdin marketplace repo for the email side