Push Notifications Guide
Complete guide to sending push notifications with Moontraze.
Overview
Moontraze uses your own Firebase project for push notifications — this is called BYOF (Bring Your Own Firebase).
Why BYOF?
- Your Firebase project, your data, your rules
- No shared rate limits with other developers
- Complete isolation — your notifications never mix with others
- You control Firebase settings, quotas, and analytics
- Moontraze never touches your Firebase data
Cost: 100% free — FCM has no message limits.
Roles:
- Owner — Can configure Firebase, send and receive
- User — Can only receive
Architecture
┌──────────────────────────────────────────────┐
│ YOUR Firebase Project │
│ ├── Android app (com.your.app) │
│ │ └── google-services.json → Flutter app │
│ └── Service Account JSON → uploaded once │
│ to Moontraze (encrypted at rest) │
└──────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Moontraze Backend │
│ Stores encrypted credentials per project │
│ Sends push via YOUR Firebase on request │
└──────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Your Users' Devices │
│ Receive push via YOUR Firebase │
└──────────────────────────────────────────────┘Setup
Step 1: Create a Firebase Project
- Go to Firebase Console (opens in a new tab)
- Click Add project → give it a name (e.g.
my-app-prod) - Google Analytics is optional — you can skip it
- Click Create project
Note: Free Spark plan is enough. No paid tier required.
Step 2: Add Your Android App
- In your Firebase project → click Add app → choose Android
- Enter your Android package name — must match your Flutter app's
applicationIdexactly- Example:
com.yourcompany.yourapp
- Example:
- (Optional) Add SHA-1 for Google Sign-In
- Click Register app
- Download
google-services.json - Place it at:
android/app/google-services.jsonin your Flutter project
⚠️ Critical: The
package_nameinsidegoogle-services.jsonMUST exactly match your app'sapplicationIdinandroid/app/build.gradle.kts. Mismatch will crash the app atFirebase.initializeApp().
Step 3: Generate Service Account Key
- In Firebase Console → click ⚙️ Project Settings
- Go to Service Accounts tab
- Click Generate new private key
- Confirm → a JSON file downloads
⚠️ Keep it safe! Never commit this file to Git. Never share it publicly. It grants full admin access to your Firebase project.
Step 4: Upload Service Account to Moontraze
- Open Moontraze Dashboard → your project
- Go to Notification Setup tab
- Upload the service account JSON file
- Moontraze encrypts it with AES-256-GCM and stores it securely
- A verification runs automatically — you'll see a green "Firebase Connected" status
Note: If verification fails, check that your service account has the Firebase Cloud Messaging API enabled in Google Cloud Console.
Step 5: Enable Push Notifications
In Moontraze Dashboard → Notifications tab → toggle Enable FCM to ON.
Client Integration
Flutter Setup
Add Firebase to your app:
# pubspec.yaml
dependencies:
firebase_core: ^3.6.0
firebase_messaging: ^15.1.3
moontraze_flutter: ^1.0.0Android Configuration
android/build.gradle.kts (project level):
plugins {
// ... existing plugins
id("com.google.gms.google-services") version "4.4.2" apply false
}android/app/build.gradle.kts (app level):
plugins {
id("com.android.application")
id("kotlin-android")
id("dev.flutter.flutter-gradle-plugin")
id("com.google.gms.google-services") // ✅ Add this
}
android {
defaultConfig {
minSdk = 21 // FCM requires API 21+
}
}android/app/src/main/AndroidManifest.xml (inside <application>):
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="high_importance_channel" />Permissions (before <application>):
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.VIBRATE"/>Flutter Code
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:moontraze_flutter/moontraze_flutter.dart';
// Top-level background handler
@pragma('vm:entry-point')
Future<void> _firebaseBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('Background notification: ${message.notification?.title}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Firebase init (reads google-services.json on Android)
await Firebase.initializeApp();
// 2. Register background handler
FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundHandler);
// 3. Initialize MoonSDK
final moon = MoonSDK(MoonConfig(
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
));
// 4. Request notification permission
final settings = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
// 5. Get FCM token
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
// 6. Register token with Moontraze
await moon.notifications.registerToken(
token,
deviceInfo: {'platform': 'android'},
);
}
// 7. Handle token refresh
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
await moon.notifications.registerToken(
newToken,
deviceInfo: {'platform': 'android'},
);
});
}
// 8. Foreground message handler
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('Foreground: ${message.notification?.title}');
// Show local notification with flutter_local_notifications if needed
});
// 9. Notification tap handler
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final data = message.data;
// Deep link based on data['type'], data['postId'], etc.
});
runApp(MyApp());
}Logout — Unregister Token
await moon.notifications.unregisterToken(currentToken);React Native Setup
npm install @react-native-firebase/app @react-native-firebase/messagingPlace google-services.json in:
android/app/google-services.jsonandroid/build.gradle:
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.2'
}
}android/app/build.gradle:
apply plugin: 'com.google.gms.google-services'Code
import messaging from '@react-native-firebase/messaging';
import MoonSDK from 'moontraze-sdk';
// Register background handler (must be outside component)
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
console.log('Background message:', remoteMessage);
});
async function setupNotifications() {
const moon = new MoonSDK({
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
});
// Request permission
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (!enabled) return;
// Get token
const token = await messaging().getToken();
// Register with Moontraze
await moon.notifications.registerToken(token, {
platform: 'android',
});
// Handle token refresh
messaging().onTokenRefresh(async (newToken) => {
await moon.notifications.registerToken(newToken, {
platform: 'android',
});
});
// Foreground handler
messaging().onMessage(async (remoteMessage) => {
console.log('Foreground:', remoteMessage.notification?.title);
});
// Tap handler
messaging().onNotificationOpenedApp((remoteMessage) => {
const data = remoteMessage.data;
// Navigate based on data.type
});
}Sending Notifications
All send methods require a projectId. Notifications are delivered via your Firebase project.
Send to All Users in a Project
await moon.notifications.sendToProject({
projectId: 'YOUR_PROJECT_ID',
title: 'New Feature Released',
body: 'Check out what we just shipped!',
});Send to a Specific User
await moon.notifications.sendToUser({
projectId: 'YOUR_PROJECT_ID',
userId: 'USER_ID',
title: 'Order Shipped',
body: 'Your order is on the way',
});Send to a Topic
Topics are useful for segmented audiences (e.g. premium_users, beta_testers).
await moon.notifications.sendToTopic({
projectId: 'YOUR_PROJECT_ID',
topic: 'premium_users',
title: 'Premium Feature',
body: 'Exclusive for premium users',
});Note: Devices must subscribe to the topic using
FirebaseMessaging.instance.subscribeToTopic('premium_users').
Send with Custom Data (Deep Links, Images, etc.)
The data field is delivered to the client and can carry any payload (FCM 4KB limit).
await moon.notifications.sendToUser({
projectId: 'YOUR_PROJECT_ID',
userId: 'USER_ID',
title: 'New comment on your post',
body: 'Sarah replied to your post',
data: {
type: 'post', // ✅ for tap routing
postId: 'clx123abc',
commentId: 'clx456def',
imageUrl: 'https://cdn.example.com/post.jpg', // ✅ for rich notification
deepLink: 'myapp://post/clx123abc',
},
});Client side:
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final data = message.data;
if (data['type'] == 'post') {
Navigator.pushNamed(context, '/post', arguments: data['postId']);
}
});Schedule for Later
await moon.notifications.schedule({
projectId: 'YOUR_PROJECT_ID',
userId: 'USER_ID', // optional — omit for project-wide
title: 'Reminder',
body: "Don't forget!",
sendAt: new Date('2026-12-31T10:00:00Z'),
});History
const history = await moon.notifications.getHistory({
projectId: 'YOUR_PROJECT_ID',
limit: 50,
});Each entry contains:
title,body,datastatus(sent|failed|scheduled)successCount,failureCountcreatedAt
Delete History
// Delete a single notification
await moon.notifications.deleteHistory('NOTIFICATION_ID');
// Clear all history for a project
await moon.notifications.clearHistory({ projectId: 'YOUR_PROJECT_ID' });Preferences
User notification preferences (per user, applies across all projects):
const prefs = await moon.notifications.getPreferences();
await moon.notifications.updatePreferences({
pushEnabled: true,
emailEnabled: false,
marketingEnabled: false,
securityEnabled: true,
quietHoursEnabled: true,
quietHoursStart: '22:00',
quietHoursEnd: '08:00',
timezone: 'Asia/Kolkata',
});Note: Moontraze stores preferences but does not automatically apply quiet hours — your backend must respect them when sending.
REST API (Alternative to SDK)
If you prefer HTTP directly:
Register Token
POST /api/notifications/register
Authorization: Bearer <jwt>
Content-Type: application/json
{
"token": "FCM_TOKEN",
"projectId": "YOUR_PROJECT_ID",
"deviceInfo": {
"platform": "android",
"deviceId": "unique-device-id"
}
}Unregister Token
POST /api/notifications/unregister
Authorization: Bearer <jwt>
Content-Type: application/json
{
"token": "FCM_TOKEN"
}Send to User
POST /api/notifications/send-to-user/USER_ID
Authorization: Bearer <jwt>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"title": "New comment",
"body": "Someone commented on your post",
"data": {
"type": "comment",
"postId": "abc123"
}
}Send to Project (All Users)
POST /api/notifications/send-to-project/YOUR_PROJECT_ID
Authorization: Bearer <jwt>
Content-Type: application/json
{
"title": "Announcement",
"body": "We just launched feature X"
}Send to Topic
POST /api/notifications/send-to-topic
Authorization: Bearer <jwt>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"topic": "premium_users",
"title": "Premium only",
"body": "Exclusive content"
}Schedule
POST /api/notifications/schedule
Authorization: Bearer <jwt>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"title": "Reminder",
"body": "Don't forget!",
"sendAt": "2026-12-31T10:00:00.000Z"
}Get History
GET /api/notifications/history?projectId=YOUR_PROJECT_ID&limit=50
Authorization: Bearer <jwt>Delete History
# Single
DELETE /api/notifications/history/NOTIFICATION_ID
Authorization: Bearer <jwt>
# All
DELETE /api/notifications/history?projectId=YOUR_PROJECT_ID
Authorization: Bearer <jwt>Error Responses
Firebase Not Configured
{
"error": "Firebase not configured for this project. Please upload your service account credentials in Project Settings → Notifications.",
"code": "FIREBASE_NOT_CONFIGURED"
}Fix: Upload service account JSON in Notification Setup tab.
Invalid Service Account
{
"error": "Failed to decrypt Firebase credentials for project xxx. Please re-upload your service account JSON."
}Fix: Re-upload the service account JSON. This usually means the ENCRYPTION_SECRET was rotated on the server.
Invalid Token
Moontraze auto-deactivates tokens that Firebase reports as invalid. No action needed — the client will get a new token via onTokenRefresh.
Best Practices
- Request permission only when needed — Explain why before prompting
- Handle token refresh — Tokens rotate periodically; always register the new one
- Unregister on logout — Prevents sending to the wrong user
- Don't spam users — Respect frequency and relevance
- Use
datafor routing, not UI — Keep payload small (FCM 4KB limit) - Meaningful titles/bodies — Truncated at ~50 chars on lock screen
- Test on real devices — Emulators can miss FCM delivery
- Respect quiet hours — Check user preferences before sending
- Never commit service account JSON — Use environment variables on your backend
- Rotate service account keys — Every 90 days as security best practice
Troubleshooting
Notifications not arriving
Checklist:
google-services.jsonpresent inandroid/app/andpackage_namematchesapplicationId?- Firebase credentials uploaded to Moontraze (green status in Notification Setup)?
- FCM enabled in Moontraze Notifications tab?
- Token registered successfully? (check logs for
[FCM] Register response: 200) - Test on real device (emulator limitations)
- Check Firebase Console → Cloud Messaging → Reports for delivery stats
No matching client found for package name
Cause: google-services.json me package_name aur app ka applicationId match nahi kar raha.
Fix:
- Firebase Console → Add correct Android app with your package name
- Download new
google-services.json - Replace in
android/app/
FIREBASE_NOT_CONFIGURED from API
Cause: Project me service account JSON upload nahi hua.
Fix: Dashboard → Notification Setup → Upload JSON.
Notification received on Android but not iOS
Cause: iOS requires APNs certificate/key configured in Firebase Console.
Fix:
- Firebase Console → Project Settings → Cloud Messaging
- Upload APNs authentication key (.p8) from Apple Developer
- Enable Push Notifications capability in Xcode
Cost
Firebase Cloud Messaging (FCM) is 100% free and unlimited.
- No per-message charges
- No monthly quota
- No user limits
- No topic limits (except ~2000 subscriptions per device)
This is true for the Spark (free) plan — you do NOT need Blaze or any paid plan for FCM.
Support
- Moontraze Dashboard → Support Chat
- Firebase docs: https://firebase.google.com/docs/cloud-messaging (opens in a new tab)
- FCM HTTP v1 API: https://firebase.google.com/docs/reference/fcm/rest (opens in a new tab)
Last updated: 2026-09-22