The Problem With Most Push Notification Systems
Most apps treat push notifications as an afterthought. The flow is: marketing team writes a message, clicks "send to all," and hopes for the best. The result: 4-5% open rate, increasing unsubscribes, and users turning off notifications entirely. We've built push notification systems for six apps, and the ones that perform well all share the same engineering principles.
Our best performer — a fintech app — achieves a 31% average open rate. The worst — a content app that initially used the "blast everyone" approach — started at 3.8% before we rebuilt the system. Let's walk through the full stack.
Delivery Infrastructure: FCM Is Not Enough
Firebase Cloud Messaging is the transport layer, not the entire solution. FCM handles delivery to the device, but it doesn't handle: segmentation, scheduling, throttling, delivery tracking, or fallback channels. You need an orchestration layer on top of FCM/APNs.
We use a custom Node.js service backed by a job queue (BullMQ on Redis) for notification orchestration. Each notification job contains: the recipient, the message template, personalization variables, delivery window preferences, and channel preferences (push, SMS, WhatsApp as fallbacks). The job processor handles: checking user preferences, applying quiet hours, personalizing the message, sending via the appropriate channel, and recording delivery and open events.
For clients who don't need a fully custom solution, we've had good results with OneSignal and Firebase's built-in messaging features. But for anything beyond basic broadcasts, you'll outgrow them quickly.
Timing: Send When Users Open the App
This sounds paradoxical, but the single biggest factor in notification open rates is timing. We track each user's app usage patterns — when they typically open the app, how frequently, and for how long. Then we schedule notifications during their active windows. The fintech app sends transaction reminders 15 minutes before the user's typical morning app check (learned from their usage data). Open rate for timed notifications: 38%. Open rate for same notifications sent at a fixed time: 12%.
We calculate per-user optimal send times using a simple heuristic: look at the user's last 30 days of app opens, find the three most common time windows, and prefer the earliest one. No ML needed — a SQL query and a cron job. The implementation is simpler than it sounds, but the impact is dramatic.
Personalization: Beyond "Hey {firstName}"
Personalized notifications perform 2-3x better than generic ones. But personalization means more than inserting the user's name. For the fintech app, instead of "Check your investment portfolio," we send "Your NIFTY 50 SIP gained ₹2,340 this month." That specific, contextual information is what makes someone tap. The "personalization" is really just pulling the right data at send time and inserting it into the template.
We store notification templates with variable placeholders in a CMS-like interface that the marketing team can edit. The orchestration service resolves variables at send time by querying the relevant data sources. This separation means the marketing team controls the message and the engineering team controls the data — each team works in their domain.
Deep Linking: The Forgotten Critical Path
A notification that opens the app's home screen is a wasted notification. Every notification should deep link to the relevant content. For the content app, notifications link directly to the article. For the fintech app, notifications link to the specific transaction or portfolio view.
Deep linking in React Native is more complex than it should be. We use React Navigation's linking configuration to handle both cold starts (app not running) and warm starts (app in background). The gotcha that bit us: on iOS, if the app is terminated and the user taps a notification, the initial URL is only available in the first render cycle. If your navigation isn't set up by then, you miss it. We solve this by queuing the initial deep link URL and processing it after navigation is fully initialized.
Measuring What Matters
We track: delivery rate (FCM confirmation), display rate (notification actually shown, not blocked by OS), open rate (user tapped), action rate (user performed the intended action after opening), and unsubscribe rate. The ratio between open rate and action rate tells you if your deep linking is working. If people open but don't act, the landing experience is broken. The ratio between delivery and display rate tells you if users are blocking your notifications at the OS level — a leading indicator that you're sending too many.