Skip to content

🎉 Push Notification Integration - Complete Summary

✅ Phases 1-3 Completed

All three phases of Firebase Cloud Messaging (FCM) integration are complete and ready for testing.


📦 What You Get

Database Layer

  • DeviceToken Model - Stores FCM tokens with user/device metadata
  • Alembic Migration - Creates notify.device_tokens table with optimized indexes
  • Push Templates - Transaction/security alerts already in template seed

Backend Logic

  • Firebase Service - Singleton wrapper for FCM Admin SDK
  • Celery Tasks - Async push notification sending with retry logic
  • Template Integration - Push channel support in notify pipeline

API Endpoints

  • Device Registration - POST /api/v1/notifications/devices/register
  • List Tokens - GET /api/v1/notifications/devices/
  • Manage Tokens - Activate/deactivate/delete operations
  • Authentication - JWT-protected endpoints

🚀 Quick Start (3 Steps)

1️⃣ Setup Firebase

# Create Firebase project at https://console.firebase.google.com
# Download service account JSON
# Set environment variables:
export FIREBASE_PROJECT_ID="your-project-id"
export FIREBASE_CREDENTIALS_PATH="/app/firebase-credentials.json"

2️⃣ Install & Migrate

pip install firebase-admin
alembic upgrade head

3️⃣ Test Registration

curl -X POST http://localhost:8000/api/v1/notifications/devices/register \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "fcm_token": "YOUR_FCM_TOKEN",
    "platform": "mobile",
    "device_name": "Test Device"
  }'

📁 Files Created/Modified

New Files

  • utils/notify/firebase.py - Firebase service wrapper
  • routers/device_tokens.py - Device management endpoints
  • alembic/versions/564f2g8a1h3b_*.py - Database migration
  • PUSH_NOTIFICATIONS_SETUP.md - Complete setup guide (this doc)

Modified Files

  • models/notify.py - Added DeviceToken model
  • controller/notify.py - Extended queue_template_notification()
  • utils/notify/tasks.py - Enhanced push task implementation
  • core/config.py - Added Firebase configuration
  • main.py - Registered device_tokens router

🔗 Architecture

┌──────────────────────────────────────────────────────────────┐
│                    REST API (FastAPI)                         │
├──────────────────────────────────────────────────────────────┤
│  POST /api/v1/notifications/devices/register                 │
│  ↓                                                            │
│  Device Token Management Endpoints (routers/device_tokens)   │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│              Notification Controller                          │
├──────────────────────────────────────────────────────────────┤
│  queue_template_notification():                              │
│  - Accepts "push" channel                                    │
│  - Formats template with placeholders                        │
│  - Stores message in queue (status="pending")                │
│  - Triggers high-priority async send                         │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│              Celery Worker (Async Tasks)                      │
├──────────────────────────────────────────────────────────────┤
│  send_message() → send_via_service()                         │
│  ↓                                                            │
│  Extracts push-specific data from message.udf:               │
│  - title                                                      │
│  - push_data (context/metadata)                              │
│  - image_url                                                 │
│  ↓                                                            │
│  async send_push_notification()                              │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│          Firebase Service (utils/notify/firebase.py)          │
├──────────────────────────────────────────────────────────────┤
│  firebase_service.send_notification():                       │
│  - Validates FCM token                                       │
│  - Sends to Firebase Admin SDK                               │
│  - Handles errors & token invalidation                       │
│  - Returns message ID or error                               │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│        Firebase Cloud Messaging (Google Infrastructure)       │
├──────────────────────────────────────────────────────────────┤
│  - Stores subscription                                       │
│  - Routes to mobile/web device                               │
│  - Delivers via platform-specific protocol                   │
│  - Handles retries & backoff                                 │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│           Mobile App / Web App (Phase 4)                      │
├──────────────────────────────────────────────────────────────┤
│  Firebase SDK receives notification                          │
│  ↓                                                            │
│  System displays to user                                     │
│  ↓                                                            │
│  App can handle notification payload                         │
└──────────────────────────────────────────────────────────────┘

💾 Database Schema

device_tokens Table

CREATE TABLE notify.device_tokens (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES core.identity(id) ON DELETE CASCADE,
    fcm_token VARCHAR UNIQUE NOT NULL,
    platform VARCHAR NOT NULL,  -- 'mobile' or 'web'
    device_name VARCHAR,         -- e.g., "iPhone 14", "Chrome"
    is_active BOOLEAN DEFAULT TRUE,
    last_used_at TIMESTAMP WITH TIMEZONE,
    created_on TIMESTAMP WITH TIMEZONE DEFAULT NOW(),
    updated_on TIMESTAMP WITH TIMEZONE DEFAULT NOW()
);

-- Indexes for performance
CREATE INDEX ix_notify_device_tokens_user_id ON notify.device_tokens(user_id);
CREATE INDEX ix_notify_device_tokens_fcm_token ON notify.device_tokens(fcm_token);
CREATE INDEX ix_notify_device_tokens_platform ON notify.device_tokens(platform);
CREATE INDEX ix_notify_device_tokens_is_active ON notify.device_tokens(is_active);

🎯 Key Features

✨ Automatic Token Management

  • Register tokens via /devices/register endpoint
  • Automatically reactivate duplicate tokens
  • Track device metadata (platform, name)
  • Mark inactive when token expires

⚡ Async Message Processing

  • Queued via Celery for reliable delivery
  • Circuit breaker pattern for provider failures
  • Exponential backoff for retries
  • Dead letter queue for failed messages

🔐 Security

  • FCM tokens encrypted in database (like SMS/email fields)
  • All endpoints JWT-authenticated
  • User can only manage their own tokens
  • Firebase credentials via environment variables

📊 Template System

  • Reuse existing template infrastructure
  • Support multiple languages (en, pt, fr)
  • Dynamic placeholder substitution
  • Store titles, data, images in message payload

📖 Usage Patterns

Send Transaction Alert

queue_template_notification(
    db=session,
    channel="push",
    template_name="INTERNAL_TRANSFER",
    receiver=device_token,  # FCM token from device_tokens table
    sender="CodeForge Banking",
    placeholders={
        "debit_name": "John",
        "credit_name": "Jane",
        "amount": "500.00",
        "currency": "USD",
        "debit_account": "123456789",
        "credit_account": "987654321",
        "transaction_reference_id": "TXN123456"
    },
    service="push",
    priority=True,  # Send immediately
    language="en"
)

Push to Multiple Users

# Get all active tokens for a segment
tokens = db.query(DeviceToken).filter(
    DeviceToken.is_active == True,
    DeviceToken.platform == "mobile"
).all()

# Send to each
for token in tokens:
    queue_template_notification(
        db=session,
        channel="push",
        template_name="PROMOTION",
        receiver=token.fcm_token,
        sender="Promotions",
        placeholders={...},
        service="push"
    )

# Or use Firebase topic-based approach (Phase 4)
firebase_service.subscribe_to_topic(
    [t.fcm_token for t in tokens],
    "promotion_alerts"
)

🔄 Message Flow Example

1. User completes transaction
2. API calls queue_template_notification()
   - channel="push"
   - template_name="INTERNAL_TRANSFER"  
   - receiver="{FCM_TOKEN}"
   - placeholders={transaction details}
3. Message created in database (status="pending")
4. Celery worker picks up message
   - Calls send_message(message_id)
   - Fetches message from DB
5. send_via_service() routes to Firebase
   - Extracts title, body, data from template
   - Calls send_push_notification()
6. Firebase sends notification
   - Validated FCM token
   - Sends via Firebase Admin SDK
7. Message status updated ("sent" or "failed")
8. User receives notification on device
   - Firebase SDK processes
   - System displays alert

📋 Checklist - Next Steps

Before Production

  • [ ] Obtain Firebase project credentials
  • [ ] Set environment variables
  • [ ] Run alembic migration
  • [ ] Install firebase-admin package
  • [ ] Test device registration endpoint
  • [ ] Test push notification sending

Phase 4 - Mobile/Web Integration

  • [ ] Setup Flutter Firebase messaging
  • [ ] Setup React service worker
  • [ ] Register tokens on app launch
  • [ ] Handle notification display
  • [ ] Test end-to-end flow

Monitoring & Operations

  • [ ] Setup logging for FCM operations
  • [ ] Monitor failed messages
  • [ ] Track token expiration rate
  • [ ] Monitor Celery queue health

📚 Documentation

  • Setup Guide: PUSH_NOTIFICATIONS_SETUP.md
  • Code: See inline comments in firebase.py, device_tokens.py
  • API Docs: Auto-generated at /docs (FastAPI swagger)

🎓 Key Technologies

Technology Purpose
Firebase Admin SDK Backend push notification delivery
FCM (Firebase Cloud Messaging) Google's cross-platform messaging
Celery Async task queue for message processing
Redis Celery broker & result backend
SQLAlchemy Device token persistence
FastAPI RESTful device management APIs

💡 Design Decisions

  1. Why Firebase over OneSignal?
  2. Free tier (no cost for startup)
  3. Native Flutter/React integration
  4. Better Android/iOS support
  5. No additional vendor lock-in

  6. Why Celery for push?

  7. Reliable, horizontal scaling
  8. Retry logic with exponential backoff
  9. Monitoring & dead letter queue
  10. Consistent with SMS/Email pattern

  11. Why separate device_tokens table?

  12. Tracks multiple devices per user
  13. Enables platform-specific targeting
  14. Efficient token lifecycle management
  15. Better querying for analytics

  16. Why queue messages?

  17. Decouples from external API
  18. Handles temporary FCM unavailability
  19. Provides audit trail
  20. Enables retry strategies

✉️ Support

Questions? Check: 1. PUSH_NOTIFICATIONS_SETUP.md - Complete setup guide 2. utils/notify/firebase.py - Inline documentation 3. controller/notify.py - Queue implementation 4. Firebase Docs - Official guide


Status: ✅ Ready for Phase 4 (Mobile/Web Integration)
Last Updated: April 10, 2026
Implementation Time: ~2 hours for Phases 1-3