Add your Android app (use package name from app.json)
Download google-services.json to apps/app/
For server-side sending, set up FCM credentials in your backend:Supabase: Upload your Firebase service account JSON in the Supabase Dashboard under Integrations → Firebase.Convex: Add your Firebase service account JSON as an environment variable in the Convex Dashboard.
The legacy FCM Server Key is deprecated. Firebase now uses HTTP v1 API with OAuth 2.0 service account authentication. All server credentials should be configured in your backend dashboard, not in client-side environment variables.
const { pushToken } = useNotificationStore()// Send pushToken to your backend
Send from Node.js backend:
import { Expo } from 'expo-server-sdk'const expo = new Expo()await expo.sendPushNotificationsAsync([{ to: pushToken, title: 'New Message', body: 'You have a new message', data: { screen: 'Messages' },}])
When users deny permission, a helpful alert offers to open Settings:
import { showPermissionDeniedAlert } from '@/services/notifications'// Called automatically by togglePush() and requestPermission()// Or call manually:showPermissionDeniedAlert()
The alert includes:
Clear explanation of what permission enables
“Open Settings” button (deep links to app settings)
Push token is automatically synced to your backend:
// In services/notifications.ts - automatically calledconst { pushToken } = useNotificationStore()// Token is synced to:// - Supabase: push_tokens table with user association// - Convex: user.pushToken field// - Or custom backend via env.backendUrl/push-tokens// Backend can use token to send notificationsawait fetch(`${backendUrl}/notifications/send`, { method: 'POST', body: JSON.stringify({ token: pushToken, title: 'New Message', body: 'You have a new message!', })})
const { setBadgeCount, unreadCount } = useNotificationStore()// Set badge to unread countsetBadgeCount(unreadCount)// Clear badgesetBadgeCount(0)// Automatically cleared when marking notifications as readmarkAllAsRead() // Also clears badge