# 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):** ```js 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 ```env 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) ```env 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:** ```tsx
``` **After:** ```tsx ``` - 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:** ```tsx // @ts-ignore — intentionally unused, reminder is disabled (coming soon) const handleReminderToggle = async () => { ``` **After:** ```tsx 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`) ```bash 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:** ```bash 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:** ```bash docker logs grateful-journal-backend-1 -f | grep -i "reminder\|firebase" ``` **View user reminders in MongoDB:** ```bash 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):** ```bash 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