Supabase Push Notifications — Research

For Agents

Pure research note. No implementation has started. Captures viable transports, what plugs into the existing CRM stack, what needs to be built greenfield, iOS PWA gotchas, and open decisions to settle before planning. Topic surface: adding push to the mobile PWA (mobile-native-feel) — not the iOS native app (which is currently planning-only).

Headline Finding

Supabase has no native “push notifications” product. It provides the primitives — Postgres for tokens/subscriptions, Edge Functions for delivery, Database Webhooks for triggers — but the actual transport is bring-your-own.

That means picking a transport (Web Push / Expo Push / FCM-APNs direct), wiring the webhook → function delivery path ourselves, and handling subscription lifecycle (registration, expiration, cleanup).

Transport Options

OptionFit for CRMNotes
Web Push (VAPID + Service Worker)Strong — fits the existing PWARecommended starting point. Browser-native, no third-party SDK, works in iOS Safari 16.4+ standalone PWAs
Expo PushOnly useful once iOS native app existsUnified iOS/Android abstraction — currently iOS app is planning-only, so deferred
FCM / APNs directMore work, only if Web Push proves insufficientHigher complexity, dual platform credentials, deeper SDK integration

Phase A scope

Web Push for the mobile PWA is the shortest path to value. Everything below assumes Phase A unless noted.

What Already Exists in the Codebase

These components plug into a push delivery pipeline:

ComponentLocationRole in push pipeline
vite-plugin-pwa (Workbox)web/vite.config.tsAlready enabled. Would need to switch from generateSWinjectManifest strategy to add custom push + notificationclick handlers
notify edge functionweb/supabase/functions/notify/index.tsWebhook-driven, already writes to notification table. Push delivery either extends this or becomes a sibling send-push function triggered by the same/sibling webhook
notification tableSupabaseAlready exists, RLS in place
notification_preference tableSupabaseAlready exists, RLS in place
NotificationBellweb/src/components/...In-app UI, complementary not replacement
NotificationPopoverweb/src/components/...In-app UI
NotificationItemweb/src/components/...In-app UI
useNotifications hookweb/src/lib/hooks/Existing data hook for in-app feed
iOS PWA meta tagsweb/index.htmlapple-mobile-web-app-capable + display: standalone already set — required for iOS push

What Needs to Be Built (Greenfield)

1. Generate VAPID keys (one-time)
   → Public key in client bundle
   → Private key in Supabase secret (never in repo)

2. New table: push_subscriptions
   ├─ user_id        (FK → person/user)
   ├─ subscription   (jsonb — endpoint, keys.p256dh, keys.auth)
   ├─ created_at     (timestamptz)
   └─ last_seen_at   (timestamptz)
   With RLS: only the owning user can read/write their own rows

3. Service worker handlers
   ├─ self.addEventListener('push', ...)
   └─ self.addEventListener('notificationclick', ...)
   In `web/public/sw.js` (or wherever injectManifest lands the SW source)

4. Client hook: usePushNotifications
   ├─ Reads existing subscription state
   ├─ subscribe() — MUST be called from explicit user gesture (button click)
   ├─ unsubscribe()
   └─ Persists to push_subscriptions via supabase upsert

5. Edge Function: send-push (or extension of `notify`)
   ├─ Imports `npm:web-push` library
   ├─ Loads VAPID private key from secret
   ├─ Fetches user's push_subscriptions rows
   ├─ Promise.allSettled() over endpoints
   └─ DELETE on 410 Gone — essential for cleanup of expired subscriptions

6. DB Webhook
   On INSERT into `notification` → fires send-push function
   (Already a webhook on notification → notify; either extend it or add sibling)

7. Mobile detection hook (currently MISSING in codebase per research)
   → Conditionally surface the subscribe prompt only on mobile
   → Nothing in `web/src/mobile/lib/` matches today; closest is `useIsMobile`
     but it's matchMedia-based — confirm that's sufficient for the prompt gate

iOS PWA Gotchas

iOS Web Push only works under specific conditions

  • iOS 16.4+ — older iOS Safari has no Web Push at all
  • Only after Add-to-Home-Screen (standalone display mode). Doesn’t work in regular Safari tab
  • apple-mobile-web-app-capable must be set (already done in web/index.html)
  • display: standalone in manifest (already done)

CF Access ZTNA + iOS Standalone Cookie Spike — UNRESOLVED

The known cookie issue flagged in Locked Decisions (and pre-mortem) directly threatens push subscription. Symptom: pushManager.subscribe() fires successfully, but the subsequent Supabase upsert to push_subscriptions may fail silently if cookies misbehave under iOS standalone PWA.

This must be validated before committing to a plan. If the cookie issue is real, push subscriptions will appear to register from the user’s perspective but never actually persist server-side, leading to delivery failures with no error surface.

Other Gotchas

GotchaMitigation
DB webhook 1000ms timeoutPush fan-out must be fast, OR enqueue and let a background job deliver. For a single user with 1-2 devices, direct fan-out is fine; for broadcast pushes, queue
Subscription rotEndpoints expire and return 410 Gone — must delete on 410 in the edge function, otherwise push_subscriptions will balloon with dead rows and waste delivery attempts
Service worker caching cross-user leakSW must NOT cache user-scoped API responses. Browser caches are per-origin, not per-user, so a logged-in/logged-out switch could leak. Restrict caching to static assets + uses {credentials: 'include'} semantics carefully
userspace/ has no PWA setupPush doesn’t apply to the client portal until vite-plugin-pwa is added there. Out of scope for Phase A
Subscribe must be user-gesture-gatedpushManager.subscribe() requires user activation. Calling it on page load triggers permission denial in many browsers. Hook design must enforce gesture origin (button click only)

Open Decisions Before Planning

  1. Scope — Web PWA only (Phase A) or also iOS native APNs (Phase B)? Recommendation: A first.
  2. Notification types — Which subset of existing notifications should push? Extend notification_preference with a push_enabled boolean per type? Or a global setting?
  3. Gating — Mobile-only or desktop too? Current PWA serves both; push permission UX differs (mobile users in-app vs desktop browser tab).
  4. Validate CF Access ZTNA + iOS standalone cookie spike — must happen first or the rest is wasted work.

Architecture Sketch (Phase A)

Sources

Status

FieldValue
PhaseResearch only — no implementation started
Date2026-05-09
BlockerValidate CF Access ZTNA + iOS standalone cookie issue before planning
Next stepSettle the four open decisions above; then write a spec
  • scheduled-reminders — Built on the centralized notification system this note researched; documents CUSTOM_REMINDER, the send-push EF default_push fallback fix, and why reminders bypass emit_notification (the event_fanout silence)
  • mobile-notifications-ui — In-app notification surfaces (bell + drawer) that the snooze affordance hooks into
  • mobile-native-feel — As-built mobile PWA (would be the surface receiving push)
  • mobile-day-rituals — Day rituals that could trigger push (morning prompts, evening wrap-up)
  • levandor-crm — Project overview / architecture reference
  • integrations — External integrations (push transport sits alongside these)
  • security — CF Access ZTNA auth — relates to the cookie blocker
  • tech-debt — May surface the Mobile-detection-hook gap as a new entry