Push Notifications — Code Map
API
| File | Role |
|---|---|
apps/api/src/push-notifications/push-notifications.module.ts | Global; BullMQ queue notifications-push; controllers + processor |
apps/api/src/push-notifications/push-notifications.service.ts | Token lifecycle + send + schedule. Constants: PUSH_QUEUE_NAME = 'notifications-push'. filterDeliveryTokens returns DeliveryToken[] ({ token, expoProjectId }); registerToken validates and stores the device’s EAS project |
apps/api/src/push-notifications/push-notifications.processor.ts | BullMQ worker; uses expo-server-sdk; groups by Expo project, chunks, pairs {ticket, token} per chunk, schedules receipt check |
apps/api/src/push-notifications/push-notifications.controller.ts | POST /devices/register, DELETE /devices/:token |
apps/api/src/push-notifications/notification-prefs.controller.ts | GET/PATCH /users/me/notification-preferences |
apps/api/src/push-notifications/dto/register-device.dto.ts | { expoPushToken, platform: 'ios' | 'android', appId?, locale?, expoProjectId? } |
apps/api/src/push-notifications/notification-copy.ts | Localized push copy builders (pushCopy.*), incl. billingDebtOpened, billingDebtLastDay, billingDebtCancelled, billingCardExpiring, billingCardUnusable (plain-date pay-by days, copy under pushNotifications.copy.billing) and trialBrief (copy under trialsPush) |
apps/api/src/notifications/billing-debt-notifications.service.ts | BillingDebtNotificationsService, sender of the billing category (debt opened / last day / cancelled, card-expiry reminders, card-unusable notice) |
apps/api/src/trials/trial-brief.service.ts | TrialBriefService, sender of the trials category (coach brief before a class with trials) |
DB
| File | Role |
|---|---|
libs/db/src/lib/schema/device-tokens.ts | device_tokens table |
libs/db/src/lib/schema/notification-prefs.ts | notification_prefs (JSONB) |
Shared
| File | Role |
|---|---|
libs/shared/src/lib/schemas/notification-prefs.ts | NOTIFICATION_CHANNELS, NOTIFICATION_CATEGORIES, NotificationPrefsValue, isChannelOptedOut, isChannelEnabled |
libs/shared/src/lib/branding.ts | CONTAINER_EAS_PROJECT_ID, LEGACY_CONTAINER_EAS_PROJECT_IDS, CONTAINER_EAS_PROJECT_IDS, easProjectIdSchema — the projects a container token may legitimately claim |
apps/api/src/notifications/email-types.ts | EMAIL_TYPES, prefCategoryFor — the email-type → preference-category gate table (CARD_EXPIRING, CARD_UNUSABLE, DEBT_OPENED, DEBT_LAST_DAY, DEBT_CANCELLED → billing) |
Mobile / web
- Mobile app calls register on boot + revoke on sign-out. Source lives in the mobile repo (not in this monorepo):
src/hooks/use-push-notifications.tssendsexpoProjectId— the sameprojectIdit passed togetExpoPushTokenAsync— so the API can record provenance. Older builds omit it and keep whatever is already stored. - Web has no push surface (PWA shell only via
apps/web/src/components/pwa/).
Config
| Env | Purpose |
|---|---|
EXPO_ACCESS_TOKEN | Optional but recommended for FCM v1 backend; passed to new Expo({ accessToken }) |
| Redis / BullMQ creds | Inherited from app-wide queue config |