How to fix messaging/message-rate-exceeded: Overall sending rate too high in Firebase

FirebaseINTERMEDIATEHIGHUpdated Dec 29, 2025· 221 views

You exceeded Firebase Cloud Messaging's per-project rate limit by sending too many messages within a short time window. FCM enforces a default quota of 600,000 messages per minute across your entire project.

Quick fix

Fixes messaging/message-rate-exceeded: Overall sending rate too high

const MAX_RETRIES = 5;
const BASE_DELAY_MS = 1000;

async function sendMessageWithRetry(message: Message): Promise<string> {
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      return await admin.messaging().send(message);
    } catch (error: any) {
      if (error.code === 'messaging/message-rate-exceeded' && attempt < MAX_RETRIES - 1) {
        // Exponential backoff: 1s, 2s, 4s, 8s, 16s
        const baseDelay = BASE_DELAY_MS * Math.pow(2, attempt);
        // Add jitter: randomize between 0.9x and 1.0x of calculated delay
        const jitter = Math.random() * 0.1;
        const delay = baseDelay * (0.9 + jitter);

        console.log(`Rate limited. Retrying after ${Math.round(delay)}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}
  1. Implement exponential backoff with jittering on 429 errors
  2. Ramp up traffic gradually instead of sending all messages at once
  3. Check your current quota usage in Google Cloud Console
  4. Request a quota increase if your usage is legitimate
firebase — error
const MAX_RETRIES = 5;
const BASE_DELAY_MS = 1000;
async function sendMessageWithRetry(message: Message): Promise<string> {
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
return await admin.messaging().send(message);
} catch (error: any) {
if (error.code === 'messaging/message-rate-exceeded' && attempt < MAX_RETRIES - 1) {
// Exponential backoff: 1s, 2s, 4s, 8s, 16s
const baseDelay = BASE_DELAY_MS * Math.pow(2, attempt);
// Add jitter: randomize between 0.9x and 1.0x of calculated delay
const jitter = Math.random() * 0.1;
const delay = baseDelay * (0.9 + jitter);
console.log(`Rate limited. Retrying after ${Math.round(delay)}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
messaging/message-rate-exceeded: Overall sending rate too high

What this error means

Firebase Cloud Messaging uses a token bucket system to rate-limit message sending. Each project gets a quota of 600,000 messages per minute by default. When your sending rate exceeds this limit, the service returns a 429 RESOURCE_EXHAUSTED error and rejects further messages until the quota resets in the next minute window. This error indicates you're sending messages faster than Firebase allows, either due to legitimate high-volume campaigns or spiky traffic patterns.

Typical symptoms

  • Push notifications fail to send with 'message-rate-exceeded' error
  • Bulk notification campaigns fail partway through
  • Some messages are delivered while others are rejected
  • Error rate spikes when sending to large user bases
  • Performance issues during peak traffic times or special events

Common causes

  1. Sending more than 600,000 messages per minute (default quota)
  2. Sudden spikes in message volume without gradual ramp-up
  3. Poorly implemented retry logic that retries too aggressively
  4. Synchronous sending of messages to thousands of users at once
  5. Per-device limits exceeded (240 msgs/min or 5,000 msgs/hour per device on Android)
  6. Sending collapsible messages beyond the burst limit (20 per app per device)

How to fix "messaging/message-rate-exceeded: Overall sending rate too high"

1Implement exponential backoff with jittering on 429 errors

When you receive a 429 RESOURCE_EXHAUSTED error, don't retry immediately. Instead, use exponential backoff with random jittering to spread retry attempts over time.

Example implementation:

typescript
const MAX_RETRIES = 5;
const BASE_DELAY_MS = 1000;

async function sendMessageWithRetry(message: Message): Promise<string> {
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      return await admin.messaging().send(message);
    } catch (error: any) {
      if (error.code === 'messaging/message-rate-exceeded' && attempt < MAX_RETRIES - 1) {
        // Exponential backoff: 1s, 2s, 4s, 8s, 16s
        const baseDelay = BASE_DELAY_MS * Math.pow(2, attempt);
        // Add jitter: randomize between 0.9x and 1.0x of calculated delay
        const jitter = Math.random() * 0.1;
        const delay = baseDelay * (0.9 + jitter);

        console.log(`Rate limited. Retrying after ${Math.round(delay)}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

Recommended backoff sequence: 1s, 2s, 4s, 8s, 16s, 32s. The jitter prevents thundering herd problems where all clients retry at the same time.

2Ramp up traffic gradually instead of sending all messages at once

Rather than sending to all users instantly, distribute your message sends over time. This is critical for high-volume campaigns.

Example approach:

typescript
const userBatches = chunkArray(userIds, 1000); // Split into batches of 1000

for (const batch of userBatches) {
  // Send to batch concurrently (up to reasonable limit)
  const promises = batch.map(userId =>
    sendMessageWithRetry({
      notification: { title: 'Hello', body: 'Message content' },
      token: getUserToken(userId)
    })
  );

  await Promise.all(promises);

  // Wait 1-2 seconds between batches to avoid rate limits
  await new Promise(resolve => setTimeout(resolve, 1000));
}

For large campaigns, ramp from 0 messages to your max RPS across a 60-second window minimum. Longer ramp windows (2-5 minutes) are safer for extremely high volumes.

3Check your current quota usage in Google Cloud Console

Monitor your actual usage to understand if you're near the default 600K/min limit.

Steps:
1. Go to [Google Cloud Console](https://console.cloud.google.com)
2. Select your Firebase project
3. Navigate to APIs & Services → Firebase Cloud Messaging API
4. Click Quotas & System Limits
5. Look for "Quota" metric and check your current RPM usage

If you're consistently using 80%+ of your quota for 5+ minutes per day with less than 5% client error rate, you may be eligible to request a quota increase of up to +25%.

4Request a quota increase if your usage is legitimate

If your high message volume is expected and your error rate is low, Firebase may grant a quota increase.

Eligibility requirements:
- Usage is regularly ≥80% of current quota for at least 5 consecutive minutes per day
- Client error ratio is <5%, especially during peak traffic
- You're following best practices for sending at scale

Quota increase timeline:
- Standard requests: Submit at least 15 days before you need it
- Large requests (>18M messages/min): Submit 30 days in advance

Submit your request through your [Firebase Console](https://console.firebase.google.com) or contact Firebase Support. Include details about your use case, expected volume, and why you need the increase.

5Implement client-side rate limiting and respect Retry-After headers

Teach your application to voluntarily back off when it detects failures.

typescript
async function sendMessageWithRetryAfter(message: Message): Promise<string> {
  try {
    return await admin.messaging().send(message);
  } catch (error: any) {
    if (error.code === 'messaging/message-rate-exceeded') {
      // Firebase Admin SDK may include retry-after in error details
      const retryAfter = error.retryAfter || 60; // Default to 60 seconds

      console.log(`Rate limited. Server requests retry after ${retryAfter}s`);
      // Queue for retry later
      await queueMessageForRetry(message, retryAfter * 1000);

      return 'queued';
    }
    throw error;
  }
}

Set a minimum 10-second timeout on send requests before giving up, since FCM's internal RPCs use 10-second timeouts.

6Avoid sending during FCM peak times

FCM experiences predictable load spikes at :00, :15, :30, and :45 minute marks. Schedule important sends outside these windows.

Better approach:
- Avoid sending within 2 minutes before/after these quarter-hour marks
- Schedule batch sends for :02, :17, :32, :47 past the hour
- Use scheduling to spread sends throughout the day rather than bunching them

typescript
function getOptimalSendTime(): Date {
  const now = new Date();
  const minutes = now.getMinutes();

  // Calculate next safe window (avoid :00, :15, :30, :45 ±2 minutes)
  const unsafeWindows = [
    { start: 58, end: 2 },   // :58-:02
    { start: 13, end: 17 },  // :13-:17
    { start: 28, end: 32 },  // :28-:32
    { start: 43, end: 47 }   // :43-:47
  ];

  // Find next safe time...
}
7Check per-device message limits

Different platforms have different per-device rate limits:

Android: 240 messages per minute, 5,000 messages per hour per device
iOS: Limited by Apple Push Notification (APNs) rates
Web: Similar to Android limits

If you're sending personalized messages to the same user from multiple sources, check that combined traffic doesn't exceed device limits.

The per-device limit is high by design to allow short bursts (like rapid chat messages), but aggressive retry logic hitting the limit indicates a logic error.

Advanced notes

Collapsible Messages: If using collapsible messages (messages with the same collapse_key), note that FCM limits these to 20 messages per app per device as a burst, with a refill of 1 message every 3 minutes. This prevents battery drain on devices and is separate from the overall rate limit.

Multi-region Considerations: If sending from multiple regions or server instances, coordinate to avoid summing up to exceed quota. A distributed queue system (like Cloud Tasks or Pub/Sub) can help coordinate message sending across multiple processes.

Token Bucket Model: Understanding the token bucket: each project starts each 60-second window with 600,000 tokens. Each message send consumes 1 token. When tokens are exhausted, new requests are rejected with 429 until the bucket refills at the next minute boundary. Short bursts are allowed, but sustained high volume triggers the error.

Debugging: Enable debug logging in Firebase Admin SDK to see exact error details:

typescript
process.env.DEBUG = 'firebase-admin:*';

Check the [FCM Error Codes documentation](https://firebase.google.com/docs/cloud-messaging/error-codes) for a complete list of messaging errors.

Frequently asked questions

What does "messaging/message-rate-exceeded: Overall sending rate too high" mean?
Firebase Cloud Messaging uses a token bucket system to rate-limit message sending. Each project gets a quota of 600,000 messages per minute by default. When your sending rate exceeds this limit, the service returns a 429 RESOURCE_EXHAUSTED error and rejects further messages until the quota resets in the next minute window.
How do I fix "messaging/message-rate-exceeded: Overall sending rate too high"?
You exceeded Firebase Cloud Messaging's per-project rate limit by sending too many messages within a short time window. FCM enforces a default quota of 600,000 messages per minute across your entire project.
What causes "messaging/message-rate-exceeded: Overall sending rate too high"?
Sending more than 600,000 messages per minute (default quota) Sudden spikes in message volume without gradual ramp-up Poorly implemented retry logic that retries too aggressively
Is the "messaging/message-rate-exceeded: Overall sending rate too high" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading