Files
grateful-journal/REMINDER_FEATURE_SETUP.md
2026-04-20 15:23:28 +05:30

11 KiB

Daily Reminder Feature - Complete Setup & Context

Date: 2026-04-20
Status: Enabled & Ready for Testing


Overview

The Daily Reminder feature is a fully implemented Firebase Cloud Messaging (FCM) system that sends push notifications to remind users to journal. It works even when the browser is closed (on mobile PWA).

Key Point: All code was already in place but disabled in the UI. This document captures the setup and what was changed to enable it.


Architecture

Frontend Flow

Files: src/hooks/useReminder.ts, src/hooks/reminderApi.ts, src/pages/SettingsPage.tsx

  1. User opens Settings → clicks "Daily Reminder" button
  2. Modal opens with time picker (ClockTimePicker component)
  3. User selects time (e.g., 08:00) → clicks "Save"
  4. enableReminder() is called:
    • Requests browser notification permission (Notification.requestPermission())
    • Gets FCM token from service worker
    • Sends token to backend: POST /api/notifications/fcm-token
    • Sends settings to backend: PUT /api/notifications/reminder/{userId}
    • Stores time + enabled state in localStorage

Message Handling:

  • listenForegroundMessages() called on app mount (in src/main.tsx)
  • When app is focused: Firebase SDK triggers onMessage() → shows notification manually
  • When app is closed: Service worker (public/sw.js) handles it via onBackgroundMessage() → shows notification

Backend Flow

Files: backend/scheduler.py, backend/routers/notifications.py, backend/main.py

Initialization:

  • start_scheduler() called in FastAPI app lifespan
  • Initializes Firebase Admin SDK (requires FIREBASE_SERVICE_ACCOUNT_JSON)
  • Starts APScheduler cron job

Every Minute:

  1. Find all users with reminder.enabled=true and FCM tokens
  2. For each user:
    • Convert UTC time → user's timezone (stored in DB)
    • Check if current HH:MM matches reminder.time (e.g., "08:00")
    • Check if already notified today (via reminder.lastNotifiedDate)
    • Check if user has written a journal entry today
    • If NOT written yet: Send FCM push via firebase_admin.messaging.send_each_for_multicast()
    • Auto-prune stale tokens on failure
    • Mark as notified today

Database Structure (MongoDB):

users collection {
  _id: ObjectId,
  fcmTokens: [token1, token2, ...],  // per device
  reminder: {
    enabled: boolean,
    time: "HH:MM",                    // 24-hour format
    timezone: "Asia/Kolkata",         // IANA timezone
    lastNotifiedDate: "2026-04-16"   // prevents duplicates today
  }
}

Changes Made (2026-04-20)

1. Updated Frontend Environment (.env.local)

Changed: Firebase credentials from mentor's project → personal test project

VITE_FIREBASE_API_KEY=AIzaSyAjGq7EFrp1mE_8Ni2iZz8LNk7ySVz-lX8
VITE_FIREBASE_AUTH_DOMAIN=react-test-8cb04.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=react-test-8cb04
VITE_FIREBASE_MESSAGING_SENDER_ID=1036594341832
VITE_FIREBASE_APP_ID=1:1036594341832:web:9db6fa337e9cd2e953c2fd
VITE_FIREBASE_VAPID_KEY=BLXhAWY-ms-ACW4PFpqnPak3VZobBIruylVE8Jt-Gm4x53g4aAzEhQzjTvGW8O7dX76-ZoUjlBV15b-EODr1IaY

2. Updated Backend Environment (backend/.env)

Changed: Added Firebase service account JSON (from personal test project)

FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account","project_id":"react-test-8cb04",...}

3. Deleted Service Account JSON File

  • Removed: service account.json (no longer needed — credentials now in env var)

4. Enabled Reminder UI (src/pages/SettingsPage.tsx)

Before:

<div className="settings-item" style={{ opacity: 0.5 }}>
    <label className="settings-toggle">
        <input type="checkbox" checked={false} disabled readOnly />
    </label>
</div>

After:

<button
    type="button"
    className="settings-item settings-item-button"
    onClick={handleOpenReminderModal}
>
    <div className="settings-item-content">
        <h4 className="settings-item-title">Daily Reminder</h4>
        <p className="settings-item-subtitle">
            {reminderEnabled && reminderTime
                ? `Set for ${reminderTime}`
                : "Set a daily reminder"}
        </p>
    </div>
</button>
  • Changed from disabled toggle → interactive button
  • Shows current reminder time or "Set a daily reminder"
  • Clicking opens time picker modal

5. Removed Type Ignore Comment

Before:

// @ts-ignore — intentionally unused, reminder is disabled (coming soon)
const handleReminderToggle = async () => {

After:

const handleReminderToggle = async () => {

Critical Code Files

File Purpose
src/hooks/useReminder.ts enableReminder(), disableReminder(), reenableReminder(), getFcmToken(), listenForegroundMessages()
src/hooks/reminderApi.ts saveFcmToken(), saveReminderSettings()
backend/scheduler.py send_reminder_notifications(), _process_user(), _send_push(), init_firebase()
backend/routers/notifications.py POST /fcm-token, PUT /reminder/{user_id} endpoints
public/sw.js Service worker background message handler
src/pages/SettingsPage.tsx UI: time picker modal, reminder state mgmt
src/main.tsx Calls listenForegroundMessages() on mount
backend/main.py Scheduler initialization in app lifespan

How to Test

Prerequisites

  • Backend .env has Firebase service account JSON
  • Frontend .env.local has Firebase web config + VAPID key
  • UI is enabled (button visible in Settings)

Steps

  1. Restart the backend (so it picks up new FIREBASE_SERVICE_ACCOUNT_JSON)

    docker-compose down
    docker-compose up
    
  2. Open the app and go to Settings

  3. Click "Daily Reminder" → time picker modal opens

  4. Pick a time (e.g., 14:30 for testing: pick a time 1-2 minutes in the future)

  5. Click "Save"

    • Browser asks for notification permission → Accept
    • Time is saved locally + sent to backend
  6. Monitor backend logs:

    docker logs grateful-journal-backend-1 -f
    

    Look for: Reminder sent to user {user_id}: X ok, 0 failed

  7. At the reminder time:

    • If browser is open: notification appears in-app
    • If browser is closed: PWA/OS notification appears (mobile)

Troubleshooting

Issue Solution
Browser asks for notification permission repeatedly Check Notification.permission === 'default' in browser console
FCM token is null Check VITE_FIREBASE_VAPID_KEY is correct; browser may not support FCM
Scheduler doesn't run Restart backend; check FIREBASE_SERVICE_ACCOUNT_JSON is valid JSON
Notification doesn't appear Check reminder.lastNotifiedDate in MongoDB; trigger time must match exactly
Token registration fails Check backend logs; 400 error means invalid userId format (must be valid ObjectId)

Environment Variables Reference

Frontend (.env.local)

VITE_FIREBASE_API_KEY              # Firebase API key
VITE_FIREBASE_AUTH_DOMAIN          # Firebase auth domain
VITE_FIREBASE_PROJECT_ID           # Firebase project ID
VITE_FIREBASE_MESSAGING_SENDER_ID  # Firebase sender ID
VITE_FIREBASE_APP_ID               # Firebase app ID
VITE_FIREBASE_VAPID_KEY            # FCM Web Push VAPID key (from Firebase Console → Messaging)
VITE_API_URL                       # Backend API URL (e.g., http://localhost:8001/api)

Backend (backend/.env)

FIREBASE_SERVICE_ACCOUNT_JSON  # Entire Firebase service account JSON (minified single line)
MONGODB_URI                    # MongoDB connection string
MONGODB_DB_NAME                # Database name
API_PORT                       # Backend port
ENVIRONMENT                    # production/development
FRONTEND_URL                   # Frontend URL for CORS

Next Steps

For Production

  • Switch back to mentor's Firebase credentials (remove personal test project)
  • Update .env.local and backend/.env with production Firebase values

Future Improvements

  • Add UI toggle to enable/disable without removing settings
  • Show timezone in Settings (currently auto-detected)
  • Show last notification date in UI
  • Add snooze button to notifications
  • Let users set multiple reminder times

Resetting to Disabled State

If you need to disable reminders again:

  1. Revert .env.local and backend/.env to mentor's credentials
  2. Revert src/pages/SettingsPage.tsx to show "Coming soon" UI
  3. Add back @ts-ignore comment

Technical Notes

Why This Approach?

  • FCM: Works on web, mobile, PWA; no polling needed
  • Service Worker: Handles background notifications even when browser closed
  • Timezone: Stores user's IANA timezone to support global users
  • Duplicate Prevention: Tracks lastNotifiedDate per user
  • Smart Timing: Only notifies if user hasn't written today (no spam)

Security Considerations

  • Firebase service account JSON should never be in git (only in env vars)
  • FCM tokens are device-specific; backend stores them securely
  • All reminder data is encrypted end-to-end (matches app's crypto design)

Known Limitations

  • Reminder check runs every minute (not more frequent)
  • FCM token refresh is handled by Firebase SDK automatically
  • Stale tokens are auto-pruned on failed sends
  • Timezone must be valid IANA format (not GMT±X)

Quick Reference Commands

Check backend scheduler logs:

docker logs grateful-journal-backend-1 -f | grep -i "reminder\|firebase"

View user reminders in MongoDB:

docker exec grateful-journal-mongo-1 mongosh grateful_journal --eval "db.users.findOne({_id: ObjectId('...')})" --username admin --password internvps

Clear FCM tokens for a user (testing):

docker exec grateful-journal-mongo-1 mongosh grateful_journal --eval "db.users.updateOne({_id: ObjectId('...')}, {\$set: {fcmTokens: []}})" --username admin --password internvps

Support

For questions about:

  • Reminders: Check daily_reminder_feature.md in memory
  • FCM: Firebase Cloud Messaging docs
  • APScheduler: APScheduler documentation
  • Firebase Admin SDK: Firebase Admin SDK for Python docs