Short answer
You sent faster than one of FCM’s limits allows: the project’s per-minute quota, the rate to a single device, or the rate to a topic. Retry the rejected messages with exponential backoff starting at one minute, spread large sends over time, and clean dead tokens out of your list, because failed sends count toward the quota too. If you are regularly near the limit, you can ask for more.
Rather not fix it yourself? App Fix Ticket starts with a fixed-price diagnosis, then a fixed price for the fix before any fix work.
Which limit you hit, and the fix
01
A burst over the project’s per-minute quota
“The default quota of 600k messages per minute covers over 99% of FCM developers while protecting the stability of the system and minimizing the impact of spiky projects.” Firebase, Throttling and quotas
Sending to your whole user list in one loop can push past the project quota in a minute. Queue the sends and release them at a steady rate. When a 429 comes back, wait and retry; the quota refills in the next minute.
02
Retrying right away
“Use exponential backoff with a minimum initial delay of 1 minute to retry rejected messages.” Firebase, FCM ErrorCode (QUOTA_EXCEEDED)
Retrying a rejected message at once lands in the same full minute and fails again. Firebase asks for backoff that starts at a minute and grows each time. Note the quota minutes are not aligned to the clock, so do not wait for :00.
import { getMessaging } from 'firebase-admin/messaging'; async function sendWithBackoff(message, attempt = 0) { try { return await getMessaging().send(message); } catch (err) { const rateCodes = ['messaging/message-rate-exceeded', 'messaging/device-message-rate-exceeded', 'messaging/topics-message-rate-exceeded']; if (!rateCodes.includes(err.code) || attempt >= 5) throw err; const delay = 60_000 * 2 ** attempt + Math.random() * 5_000; await new Promise((r) => setTimeout(r, delay)); return sendWithBackoff(message, attempt + 1); } }03
Dead tokens using up the quota
“Client errors (HTTP status code 400-499) are counted (excluding 429s).” Firebase, Throttling and quotas
Sends to uninstalled apps fail with 404 UNREGISTERED, and those failures still count. A list full of old tokens wastes part of every minute’s quota and raises your error ratio, which also blocks a quota increase. Delete tokens that come back UNREGISTERED.
04
Too many messages to one device
“For Android, you can send up to 240 messages per minute and 5,000 messages per hour to a single device.” Firebase, Throttling and quotas
A bug that sends in a loop, or a burst of chat messages, can hit the per-device limit. On iOS the limit is Apple’s, and Firebase returns an error when you exceed it. Send fewer, more useful notifications and collapse updates into one.
Checked against the vendor’s docs on : Firebase docs: Throttling and quotas; Firebase docs: FCM HTTP v1 ErrorCode; Firebase docs: Best practices for FCM registration token management.
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
“Very responsive, attentive to fixing bugs, and completed the project as needed! Thank you!”
Related: FCM registration token not registered · Firestore quota exceeded · Firebase auth/quota-exceeded · FCM SENDER_ID_MISMATCH