Skip to main content

Notifications — Technical Reference

← Back to Notifications

Where this lives

Backend

Frontend

Technical implementation checklist

  • myNotifications / markNotificationAsRead / markAllNotificationsAsRead — resolvers wired in notification.resolver.js; NotificationsPanel.tsx queries, displays, and marks items read
  • firebase.service.js / push-notification/ facade / notification-queue.service.js — fully implement server-side push delivery; WebPushRegistrar.tsx (mounted once for authenticated users, next to UsageHeartbeat) now calls registerDeviceToken with platform: 'web' whenever initWebPush() resolves a token, so push is actually delivered to the web client once Firebase web config is present (previously the frontend never called this mutation)
  • notificationSettings / updateNotificationSettings / resetNotificationSettings — resolvers wired in user-notifications.resolver.js; NotificationsSettingsPage.tsx queries and mutates notificationSettings (email/push/SMS channels) directly, and separately queries/mutates a pauseAll/quietHoursEnabled/messagesOnlyMode slice of extendedNotificationSettings for its hub toggles — real GraphQL calls, not static switches
  • unreadNotificationsCount / notificationAdded subscription — NotificationsButton.tsx and NotificationsPanel.tsx subscribe to notificationAdded and refetch the count/list on each event, replacing the previous 30-second polling with push-driven updates

Notification types

The enum has grown well beyond the original social set to cover commerce, moderation, and account-security events:

enum NotificationType {
like # Reaction on your post
comment # Comment on your post
follow # New follower
follow_request # Someone requested to follow you (private account)
mention # Mentioned in a comment or message
tag # Tagged in a post
message # New direct message
message_purchase # Someone bought a paid message
post # Post from someone you follow
post_purchase # Someone bought a paid post
repost # Your post was shared
story_like # Reaction on your story
reaction # Reaction on your story (alt path)
subscription # New subscription to your profile
subscription_renewal_reminder # Upcoming subscription renewal
conversation_subscription # New paid-chat subscription
tip # You received a tip
cashout # Cashout status update
product_purchase # Someone bought your product
order_shipped # Order shipped
order_canceled # Order canceled
dispute_opened # Payment dispute opened
dispute_resolved # Payment dispute resolved
identity_verification_approved # ID verification approved
identity_verification_rejected # ID verification rejected
referral_bonus # Referral bonus earned
collaboration_invite # Invited to collaborate on a post
collaboration_accepted # Collaboration invite accepted
collaboration_rejected # Collaboration invite declined
collaboration_removed # Removed from a collaboration
security_issue # Security alert
system # System notification
system_alert # System notification (alt path)
new_report # New report filed (moderation)
moderation_action # Account moderation action taken
}

DEFAULT_PUSH_TITLES in notification.manager.js maps each type to its push notification title. Social types in GROUPABLE_PUSH_TYPES (like, comment, follow, follow_request, repost, story_like, reaction, tag) have push bursts collapsed into one notification per 5-minute window; transactional/message/security types are never grouped.

Notification model

FieldDescription
notificationTypeType (see enum)
actorId / actorWho triggered the notification
entityType / entityIdRelated content
messageNotification text
isRead / readAtRead state
groupKey / groupCountGrouping (e.g. "5 likes")
metadataExtra data (JSON)

Queries

myNotifications returns the current user's notification list, newest first. Notifications with the same groupKey are grouped — groupCount tells you how many events collapsed into one item (e.g. "Alice, Bob and 3 others liked your post"). Each item also resolves a post field (thumbnail for post-related notifications) and an actor field, both resolved on demand if not eager-loaded.

notification(id) fetches a single notification by ID (authenticated, ownership-checked in the manager).

unreadNotificationsCount is a cheap counter — call it on app mount; the frontend keeps it fresh afterward via the notificationAdded subscription rather than polling.

notificationSettings returns the user's per-channel toggle state — useful for rendering the settings UI.

query MyNotifications($limit: Int, $offset: Int) {
myNotifications(limit: $limit, offset: $offset) {
id notificationType message isRead createdAt
actor { username profilePicture }
entityType entityId
groupKey groupCount
post { id media { thumbnailUrl mediaUrl } }
}
}

query UnreadCount { unreadNotificationsCount }

query NotificationSettings {
notificationSettings {
emailNotifications pushNotifications smsNotifications
likesNotifications commentsNotifications followsNotifications
mentionsNotifications messagesNotifications postsNotifications
marketingNotifications securityNotifications
}
}

Mutations

markNotificationAsRead marks a single item read and updates readAt. markAllNotificationsAsRead is a bulk version — call it when the user opens the notification panel.

deleteNotification permanently removes one item. deleteAllNotifications clears the entire history.

createNotification creates a notification for an arbitrary userId — intended for system/admin use, not exposed in the regular UI.

updateNotificationSettings persists channel toggles. resetNotificationSettings reverts all toggles to platform defaults.

mutation MarkRead($notificationId: ID!) { markNotificationAsRead(notificationId: $notificationId) }
mutation MarkAllRead { markAllNotificationsAsRead }
mutation DeleteNotification($id: ID!) { deleteNotification(notificationId: $id) }
mutation DeleteAllNotifications { deleteAllNotifications }

mutation UpdateNotificationSettings($input: NotificationSettingsInput!) {
updateNotificationSettings(input: $input) { success }
}

mutation ResetNotificationSettings { resetNotificationSettings }

Subscription

notificationAdded(userId: ID!) fires for the recipient each time a new notification is created for them. NotificationsButton.tsx and NotificationsPanel.tsx subscribe to it (scoped to the logged-in user's own ID) and refetch the unread count / list on each event — this replaced the previous 30-second poll interval.

subscription OnNotificationAdded($userId: ID!) {
notificationAdded(userId: $userId) { id }
}

Per-channel settings

Each channel is toggled independently:

SettingDescription
emailNotificationsEmail delivery
pushNotificationsFCM push delivery
smsNotificationsSMS delivery
likesNotificationsReactions on posts
commentsNotificationsComments on posts
followsNotificationsNew followers
mentionsNotificationsMentions in content
messagesNotificationsDirect messages
postsNotificationsPosts from followed users
marketingNotificationsPlatform news and updates
securityNotificationsSecurity alerts

Push notifications (FCM)

The backend sends push notifications via Firebase Cloud Messaging, through a provider-agnostic facade (services/push-notification/) that both apns.service.js and services/notification/notification.service.js delegate to:

  1. On login (web) or app launch (iOS), the frontend registers the device token (registerDeviceToken) — on web this is WebPushRegistrar.tsx, gated on Firebase web config
  2. notification.worker.js runs as a background BullMQ job (concurrency 10, up to 100/sec) to dispatch pushes
  3. The backend uses the Firebase Admin SDK, initialized in firebase.service.js from the FIREBASE_PROJECT_ID / FIREBASE_PRIVATE_KEY / FIREBASE_CLIENT_EMAIL env vars
  4. PUSH_NOTIFICATION_PROVIDER (default firebase) selects the delivery provider in the facade; DISABLE_PUSH_NOTIFICATIONS short-circuits all sends to mock responses (used in dev/test)

For iOS, VoIP push for incoming calls is handled separately via real APNs (@parse/node-apn in voip-push.service.js, configured with APNS_PRIVATE_KEY / APNS_KEY_ID / APNS_TEAM_ID / APNS_BUNDLE_ID) rather than through the FCM facade — see registerVoIPToken.

Extended settings

extendedNotificationSettings / updateExtendedNotificationSettings (graphql/types/extended-notification-settings.type.js + resolver) is a separate, much larger surface layered on top of the base channels above — full detail lives in Settings → Notifications since it was built as part of the Settings feature pass rather than this one. Backed by notification-settings.manager.js, which had far more internal keys than were ever exposed via the base NotificationSettings type — most of the extended fields are just newly-exposed pre-existing manager state, not new business logic.