This Firebase Cloud Messaging error occurs when a single device receives messages faster than the rate limit allows. Firebase imposes per-device message rate limits to prevent abuse and ensure service stability. To fix this, you need to implement message throttling, batch notifications, or adjust your sending strategy.
The "messaging/device-message-rate-exceeded" error is Firebase Cloud Messaging's way of enforcing rate limits on individual devices. Firebase Cloud Messaging (FCM) has built-in quotas and throttling to prevent abuse, ensure fair usage, and maintain service reliability for all users. When you send too many messages to a single device token in a short period, Firebase will block further messages to that device temporarily. This is a protective measure that prevents: 1. Spamming users with excessive notifications 2. Overloading the FCM infrastructure 3. Draining device batteries with constant wake-ups 4. Violating platform-specific guidelines (iOS/Android push notification limits) The error specifically indicates that a particular device token has exceeded the allowed message rate, not that your overall project quota has been exceeded. Each device has its own rate limit counter.
First, analyze how often you're sending messages to individual devices. Look at your application logs or implement logging to track:
// Example logging for FCM sends
console.log('Sending to device:', deviceToken, 'at:', new Date().toISOString());Check if you have loops or rapid-fire sends that might trigger the rate limit. Firebase's per-device rate limit is typically around a few messages per minute, but the exact limit isn't publicly documented and may vary.
Instead of sending individual messages immediately, batch them and send at controlled intervals:
// Example: Batch messages and send every 30 seconds
const messageQueue = new Map();
function queueMessage(deviceToken, message) {
if (!messageQueue.has(deviceToken)) {
messageQueue.set(deviceToken, []);
}
messageQueue.get(deviceToken).push(message);
}
// Send batched messages periodically
setInterval(async () => {
for (const [deviceToken, messages] of messageQueue.entries()) {
if (messages.length > 0) {
// Send only the most recent message or combine them
const latestMessage = messages[messages.length - 1];
await admin.messaging().send(latestMessage);
messageQueue.set(deviceToken, []);
}
}
}, 30000); // Every 30 secondsThis prevents hitting the rate limit by spacing out messages.
For messages that update the same information (like "3 new messages"), use collapse keys so only the latest message is delivered:
const message = {
token: deviceToken,
data: {
messageCount: '3'
},
android: {
collapseKey: 'new_messages' // Same collapse key for all message count updates
},
apns: {
headers: {
'apns-collapse-id': 'new_messages'
}
}
};When multiple messages with the same collapse key are sent, FCM will only deliver the most recent one, reducing the message count.
If a message fails, don't retry immediately. Use exponential backoff:
async function sendWithRetry(message, maxRetries = 3) {
let retryCount = 0;
let delay = 1000; // Start with 1 second
while (retryCount < maxRetries) {
try {
await admin.messaging().send(message);
return; // Success
} catch (error) {
if (error.code === 'messaging/device-message-rate-exceeded') {
retryCount++;
console.log(`Rate limited, waiting ${delay}ms before retry ${retryCount}`);
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff: 1s, 2s, 4s, etc.
} else {
throw error; // Other errors should not be retried
}
}
}
throw new Error('Max retries exceeded');
}This prevents making the rate limiting worse by immediately retrying.
If you need to send the same message to many users, use FCM topics instead of individual device tokens:
// Subscribe devices to topics
await admin.messaging().subscribeToTopic([deviceToken1, deviceToken2], 'news_updates');
// Send to topic (counts as one message for rate limiting purposes)
const message = {
topic: 'news_updates',
notification: {
title: 'News Update',
body: 'Check out our latest news!'
}
};
await admin.messaging().send(message);Topic messages have different rate limits and can be more efficient for broadcast scenarios.
Implement monitoring to track rate limit errors:
// Track rate limit errors
const stats = {
rateLimitErrors: 0,
successfulSends: 0
};
try {
await admin.messaging().send(message);
stats.successfulSends++;
} catch (error) {
if (error.code === 'messaging/device-message-rate-exceeded') {
stats.rateLimitErrors++;
console.warn('Rate limit hit for device:', message.token);
}
throw error;
}
// Log stats periodically
console.log('FCM Stats:', stats);Regularly review these metrics to ensure your throttling is effective.
Firebase Cloud Messaging rate limits are dynamic and not publicly documented. They can change based on:
1. Platform differences: iOS (APNs) and Android (FCM) have different underlying rate limits
2. Message priority: High-priority messages might have different limits than normal-priority
3. User engagement: Devices that frequently dismiss notifications might get lower limits
4. Time of day: Limits might be tighter during peak hours
Important considerations:
- The rate limit is per device token, not per user (a user with multiple devices has separate limits for each)
- When a device token is rate-limited, it typically resets after a few minutes
- Using sendMulticast() or sendEachForMulticast() doesn't bypass per-device limits
- Consider implementing a "notification summary" feature that batches multiple events into a single notification
Alternative approach: For high-frequency updates (like chat apps), consider using silent data messages instead of visible notifications, as they might have different rate limits.
messaging/UNSPECIFIED_ERROR: No additional information available
How to fix "messaging/UNSPECIFIED_ERROR: No additional information available" in Firebase Cloud Messaging
App Check: reCAPTCHA Score Too Low
App Check reCAPTCHA Score Too Low
storage/invalid-url: Invalid URL format for Cloud Storage reference
How to fix invalid URL format in Firebase Cloud Storage
auth/missing-uid: User ID identifier required
How to fix "auth/missing-uid: User ID identifier required" in Firebase
auth/invalid-argument: Invalid parameter passed to method
How to fix "auth/invalid-argument: Invalid parameter passed to method" in Firebase