Skip to main content

FCM Push Notifications - Architecture Diagram

System Overview

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                          TATU NOTIFICATION SYSTEM                   ┃
┃                          (Three-Channel System)                     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

                              ┌─────────────┐
                              │   EVENT     │
                              │  OCCURS     │
                              │ (e.g. new   │
                              │  booking)   │
                              └──────┬──────┘


                       ┌─────────────────────────┐
                       │  createNotification()   │
                       │  @firebase/server/      │
                       │   notifications.ts      │
                       └───────────┬─────────────┘

                ┌──────────────────┼──────────────────┐
                │                  │                  │
                ↓                  ↓                  ↓
    ┌──────────────────┐ ┌─────────────────┐ ┌──────────────────┐
    │   FIRESTORE      │ │   FCM PUSH      │ │   EMAIL          │
    │   DOCUMENT       │ │   NOTIFICATION  │ │   (Mailgun)      │
    │                  │ │   (NEW!)        │ │                  │
    │ user-notif.../   │ │                 │ │ Via Cloud Fns    │
    │  notifications/  │ │ To all user     │ │ If enabled in    │
    │  {notifId}       │ │ devices         │ │ preferences      │
    └────────┬─────────┘ └────────┬────────┘ └────────┬─────────┘
             │                    │                    │
             ↓                    ↓                    ↓
    ┌──────────────────┐ ┌─────────────────┐ ┌──────────────────┐
    │  IN-APP          │ │  NATIVE OS      │ │  USER'S          │
    │  NOTIFICATION    │ │  NOTIFICATION   │ │  EMAIL           │
    │  CENTER          │ │                 │ │  INBOX           │
    │                  │ │  iOS/Android    │ │                  │
    │  Web/Mobile      │ │  Notification   │ │  Gmail, etc.     │
    │  useNotifications│ │  Tray           │ │                  │
    └────────┬─────────┘ └────────┬────────┘ └──────────────────┘
             │                    │
             │                    │
             │                    ↓
             │           ┌─────────────────┐
             │           │  USER TAPS      │
             │           │  NOTIFICATION   │
             │           └────────┬────────┘
             │                    │
             │                    ↓
             │           ┌─────────────────┐
             │           │  APP OPENS TO   │
             │           │  DEEP LINK      │
             │           │  (booking/id)   │
             │           └────────┬────────┘
             │                    │
             └────────────────────┘


              ┌──────────────────┐
              │  NOTIFICATION    │
              │  MARKED AS READ  │
              └──────────────────┘

Token Registration Flow

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                      FCM TOKEN REGISTRATION FLOW                    ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

    ┌────────────────┐
    │  MOBILE APP    │
    │  STARTS        │
    └───────┬────────┘


    ┌────────────────┐
    │  USER LOGS IN  │
    └───────┬────────┘


    ┌─────────────────────────┐
    │  Request Notification   │
    │  Permissions            │
    │  (iOS/Android)          │
    └───────┬─────────────────┘


    ┌─────────────────────────┐
    │  Get FCM Token          │
    │  from Device            │
    │  (Expo/Firebase SDK)    │
    └───────┬─────────────────┘


    ┌─────────────────────────┐
    │  useRegisterFCMToken()  │
    │  Hook Called            │
    └───────┬─────────────────┘


    ┌─────────────────────────┐
    │  POST /api/notifications│
    │  /register-fcm-token    │
    │                         │
    │  Body: {                │
    │    token,               │
    │    deviceId,            │
    │    platform             │
    │  }                      │
    └───────┬─────────────────┘


    ┌─────────────────────────┐
    │  Backend Validates      │
    │  & Stores in Firestore  │
    │                         │
    │  users/{userId}         │
    │    .fcmTokens           │
    │      [deviceId] = {     │
    │        token,           │
    │        platform,        │
    │        createdAt,       │
    │        lastUsed         │
    │      }                  │
    └───────┬─────────────────┘


    ┌─────────────────────────┐
    │  ✅ Device Registered   │
    │  Ready to Receive       │
    │  Push Notifications     │
    └─────────────────────────┘

Notification Sending Flow (Detailed)

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                     FCM NOTIFICATION SENDING FLOW                   ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Event Trigger
(e.g., artist confirms booking)


┌─────────────────────────┐
│  onBookingUpdated()     │
│  Cloud Function         │
└──────────┬──────────────┘


┌─────────────────────────┐
│  createNotification()   │
│  Called                 │
│                         │
│  Parameters:            │
│  - userId: "abc123"     │
│  - type: BOOKING_       │
│    CONFIRMED            │
│  - data: {              │
│      bookingId: "xyz",  │
│      artistName: "Jane" │
│    }                    │
└──────────┬──────────────┘


┌─────────────────────────────────────────────────┐
│  STEP 1: Create Firestore Notification         │
│  ────────────────────────────────────────       │
│  Collection: user-notifications/{userId}        │
│              /notifications/{notifId}           │
│                                                 │
│  Document: {                                    │
│    id: "notif_123",                            │
│    userId: "abc123",                           │
│    type: "BOOKING_CONFIRMED",                  │
│    data: { bookingId: "xyz", ... },            │
│    isRead: false,                              │
│    createdAt: 1234567890                       │
│  }                                              │
└──────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│  STEP 2: Update User Notification Info         │
│  ────────────────────────────────────────       │
│  Collection: user-notifications/{userId}        │
│                                                 │
│  Update: {                                      │
│    newNotificationIds: [... + "notif_123"],    │
│    lastUpdated: 1234567890                     │
│  }                                              │
└──────────┬──────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│  STEP 3: Send FCM Push (Non-Blocking)         │
│  ────────────────────────────────────────       │
│  sendPushNotification()                         │
│  ↓                                              │
│  1. Fetch user's FCM tokens from Firestore     │
│     users/{userId}.fcmTokens                    │
│                                                 │
│  2. Build notification payload                  │
│     buildNotificationPayload() →                │
│     {                                           │
│       title: "Booking Confirmed",              │
│       body: "Jane confirmed your booking",     │
│       data: { bookingId, screen: "booking" }   │
│     }                                           │
│                                                 │
│  3. Extract all device tokens                   │
│     ["token1", "token2", "token3"]             │
│                                                 │
│  4. Send to Firebase Cloud Messaging           │
│     messaging().sendEachForMulticast()          │
│     ├─ Device 1 (iOS)     → ✅ Success         │
│     ├─ Device 2 (Android) → ✅ Success         │
│     └─ Device 3 (iOS)     → ❌ Invalid token   │
│                                                 │
│  5. Clean up failed tokens                      │
│     Remove "token3" from user document         │
└─────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────┐
│  RESULT: Notification Delivered                 │
│  ────────────────────────────────────────       │
│  ✅ Firestore: Document created                │
│  ✅ Device 1: Native notification shown        │
│  ✅ Device 2: Native notification shown        │
│  ❌ Device 3: Token removed (was invalid)      │
└─────────────────────────────────────────────────┘

Multi-Device Support

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃              ONE USER, MULTIPLE DEVICES SCENARIO                    ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

                        ┌─────────────────┐
                        │  USER: John     │
                        │  ID: "user_123" │
                        └────────┬────────┘

                                 │ Has 3 devices:

        ┌────────────────────────┼────────────────────────┐
        │                        │                        │
        ↓                        ↓                        ↓
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│  iPhone 13    │       │  iPad Pro     │       │ Android Phone │
│               │       │               │       │               │
│ Device ID:    │       │ Device ID:    │       │ Device ID:    │
│ "iphone_xyz"  │       │ "ipad_abc"    │       │ "android_def" │
│               │       │               │       │               │
│ FCM Token:    │       │ FCM Token:    │       │ FCM Token:    │
│ "token_1"     │       │ "token_2"     │       │ "token_3"     │
│               │       │               │       │               │
│ Platform:     │       │ Platform:     │       │ Platform:     │
│ "ios"         │       │ "ios"         │       │ "android"     │
└───────────────┘       └───────────────┘       └───────────────┘

                    Stored in Firestore as:

    users/user_123 {
      fcmTokens: {
        "iphone_xyz": {
          token: "token_1",
          platform: "ios",
          deviceId: "iphone_xyz",
          createdAt: 1234567890,
          lastUsed: 1234567891
        },
        "ipad_abc": {
          token: "token_2",
          platform: "ios",
          deviceId: "ipad_abc",
          createdAt: 1234567892,
          lastUsed: 1234567893
        },
        "android_def": {
          token: "token_3",
          platform: "android",
          deviceId: "android_def",
          createdAt: 1234567894,
          lastUsed: 1234567895
        }
      }
    }

                    When notification sent:

                        Notification sent to
                        ALL 3 devices simultaneously

                ┌──────────────┼──────────────┐
                ↓              ↓              ↓
         Push to iPhone   Push to iPad   Push to Android
            ✅ Success      ✅ Success      ✅ Success

         User sees notification on whichever device
         they're currently using (or all of them!)

Token Lifecycle

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                        FCM TOKEN LIFECYCLE                          ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

1. TOKEN CREATION
   ────────────────
   User logs in → App requests permissions → Device provides token

   ┌────────────────┐
   │ Token Created  │
   │ "token_abc123" │
   └───────┬────────┘


   ┌────────────────┐
   │ Registered     │
   │ with Backend   │
   └───────┬────────┘



2. TOKEN USAGE
   ────────────
   Notifications sent → Token used → lastUsed timestamp updated

   ┌────────────────┐
   │ Active Token   │
   │ lastUsed: now  │
   └───────┬────────┘



3. TOKEN EXPIRATION (Automatic Cleanup)
   ──────────────────
   FCM send fails → Token marked invalid → Removed from Firestore

   ┌────────────────┐
   │ Send Failed    │
   │ (invalid token)│
   └───────┬────────┘


   ┌────────────────┐
   │ Auto-Cleanup   │
   │ Token Removed  │
   └───────┬────────┘



4. TOKEN REMOVAL (Manual)
   ──────────────────
   User logs out → App calls removeToken → Removed from Firestore

   ┌────────────────┐
   │ User Logs Out  │
   └───────┬────────┘


   ┌────────────────┐
   │ Token Removed  │
   │ No more pushes │
   └────────────────┘

TIMELINE:
─────────────────────────────────────────────────────────────────
Day 1:   [CREATE]  Token registered
Day 2:   [USE]     Notification sent
Day 3:   [USE]     Notification sent
...
Day 30:  [EXPIRE]  Token becomes invalid (rare)
         [CLEANUP] Auto-removed on next failed send
OR
Day 15:  [REMOVE]  User logs out, token removed

Error Handling Flow

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                      ERROR HANDLING & RESILIENCE                    ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Scenario 1: FCM Service Down
─────────────────────────────
createNotification()
├─ ✅ Firestore write succeeds
├─ ❌ FCM send fails
└─ ✅ Error logged, notification ID still returned
Result: In-app notification still works!

Scenario 2: Invalid Token
──────────────────────────
sendPushNotification()
├─ ✅ Fetches tokens from Firestore
├─ ❌ FCM rejects token (invalid/expired)
├─ ✅ Auto-cleanup removes bad token
└─ ✅ Other tokens still receive notification
Result: Other devices still get notification!

Scenario 3: No Tokens Registered
─────────────────────────────────
sendPushNotification()
├─ ✅ Checks for tokens
├─ ℹ️  No tokens found
└─ ✅ Logs and returns (no error)
Result: Graceful handling, no crash!

Scenario 4: Network Error
──────────────────────────
registerToken()
├─ ❌ Network request fails
├─ ❌ Hook returns error
└─ ✅ User sees error message
Result: User can retry registration!

┌─────────────────────────────────────────────────┐
│  KEY PRINCIPLE: Non-Blocking Operations        │
│  ────────────────────────────────────────       │
│  FCM sending is fire-and-forget.               │
│  Failures don't break core functionality.      │
│  In-app notifications always work as backup.   │
└─────────────────────────────────────────────────┘

Component Integration Map

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                      COMPONENT RELATIONSHIPS                        ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

BACKEND
───────
firebase/server/
  ├─ notifications.ts
  │  └─ createNotification() ──────────┐
  │                                    │
  └─ fcm/                              │
     ├─ sendPushNotification.ts ◄──────┤
     │  └─ Uses: buildNotificationPayload
     └─ buildNotificationPayload.ts    │

                    Uses               │
                     ↓                 │

API ENDPOINTS                          │
─────────────                          │
pages/api/notifications/               │
  ├─ register-fcm-token.ts ◄───────────┼── Called by mobile app
  ├─ remove-fcm-token.ts ◄─────────────┼── Called by mobile app
  └─ get-notifications.ts ◄────────────┼── In-app notifications


FRONTEND HOOKS                         │
──────────────                         │
hooks/notifications/                   │
  ├─ useNotifications.ts               │
  │  └─ In-app notification center     │
  │                                    │
  └─ useFCMTokenRegistration.ts ◄──────┼── Mobile app uses this
     ├─ useRegisterFCMToken()          │
     ├─ useRemoveFCMToken()            │
     └─ useFCMTokenAutoRegistration()  │


TYPES                                  │
─────                                  │
types/models/                          │
  ├─ users.ts ◄─────────────────── Stores FCM tokens
  │  └─ FCMToken interface            │
  │  └─ BaseUserInfo.fcmTokens        │
  │                                    │
  └─ notifications/ ◄──────────────────┘
     ├─ index.ts
     ├─ notificationTypes.ts ──────── Notification type enum
     └─ notificationConfig.tsx ─────── Display config

Benefits Visualization

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃              BEFORE vs AFTER FCM IMPLEMENTATION                     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

BEFORE (In-App Only)
────────────────────

User closes app

Booking request comes in

Notification created in Firestore

❌ User doesn't know
❌ Has to open app to check
❌ Might miss time-sensitive booking


AFTER (With FCM)
────────────────

User closes app

Booking request comes in

Notification created in Firestore

FCM push sent immediately ──────→ 📱 Device vibrates/sounds

✅ User sees notification
✅ Taps to open app
✅ Goes directly to booking
✅ Can respond quickly


RELIABILITY COMPARISON
──────────────────────

Without FCM:
  Polling every 5 minutes ──→ 5 minute delay
  App must be open ──────→ Easy to miss
  No offline queue ──────→ Notifications missed

With FCM:
  Instant delivery ──────→ Real-time
  Works when closed ─────→ Never miss
  Automatic retry ───────→ Reliable
This architecture provides:
  • ✅ Real-time push notifications
  • ✅ Multi-device support
  • ✅ Automatic token cleanup
  • ✅ Non-blocking operations
  • ✅ Graceful error handling
  • ✅ Deep linking support
  • ✅ Platform-specific optimizations