Your app has 50,000 installs. Active daily users? Fewer than 4,000. You've built something genuinely useful, but most of your users open it once after download and never return. This is the retention cliff nearly every mobile product faces — and in most cases, push notifications and deep links are the two levers that can pull users back, if they're implemented with any real care. The problem is that most teams bolt these features on as afterthoughts, wonder why click-through rates hover around 1–2%, and conclude that "users just don't respond to push." That's rarely the real diagnosis.
What Push Notifications Actually Are (and Are Not)
Push notifications are server-initiated messages delivered to a device even when the app isn't open. On Android, they travel through Firebase Cloud Messaging (FCM). On iOS, they go through Apple Push Notification service (APNs). Both are free to use, but the implementation details differ enough that teams building cross-platform apps often stumble at the edges.
FCM handles both Android and, via a compatibility layer, can reach iOS if you configure an APNs key in your Firebase project. But for iOS, APNs is the actual delivery channel underneath — Firebase is just the broker. This matters because APNs has stricter delivery policies, a different authentication mechanism (p8 key vs. the older p12 certificate), and distinct payload structures for foreground vs. background delivery.
What push notifications are not is a marketing broadcast tool you can spray indiscriminately. Apple's permission prompt is a one-time ask. Roughly half of iOS users decline it when prompted at first launch. If you prompt at the wrong moment — say, before the user has experienced any value from your app — that permission is gone permanently unless they manually re-enable it in Settings.
Setting Up FCM and APNs: The Right Way
The technical setup is well-documented but the ordering matters. Here's the sequence that avoids the most common errors:
- Create a Firebase project and add your Android and iOS apps. For Android, download
google-services.jsonand place it in your app module. For iOS, downloadGoogleService-Info.plist. - For iOS, generate an APNs Authentication Key (p8 format) in the Apple Developer portal under Certificates, Identifiers & Profiles. Upload this to Firebase under Project Settings → Cloud Messaging. This key does not expire, unlike the older p12 certificates.
- In your iOS app, request notification permission using
UNUserNotificationCenter— but only after the user has seen value. A common pattern is to show a soft pre-prompt screen explaining why you need permission, letting the user opt in before the system dialog fires. - Register for remote notifications in
AppDelegate, capture the APNs token, and pass it to the Firebase SDK so FCM can map its registration token to the APNs token. - On Android 13+, notification permission is no longer automatic. Your app must explicitly request
POST_NOTIFICATIONSpermission, same as you would for camera or location.
Once a device token is captured, store it against the user's account on your backend — not just locally. Tokens change when a user reinstalls the app or restores a device backup. Your backend needs to handle token updates gracefully, and you should purge stale tokens when FCM returns a registration-token-not-registered error.
Deep Links: Routing Users to the Right Place
A deep link is a URL that opens a specific screen inside your app rather than dropping the user at the home screen. When paired with a push notification, a deep link is the difference between a 2% click-through rate and a 12% one — because the user lands exactly where the notification promised them they would.
There are three varieties worth understanding:
- Custom URI schemes (
myapp://product/123): Simple, but they silently fail if the app isn't installed. The browser does nothing, the user sees nothing. Avoid for any public-facing link. - Universal Links (iOS) / App Links (Android): These are standard HTTPS URLs that the OS intercepts and routes to your app if it's installed. If not installed, the browser opens the page normally. This requires hosting a verification file (
apple-app-site-associationorassetlinks.json) at the root of your domain over HTTPS. - Firebase Dynamic Links: A hosted service that wraps your deep link, handles install attribution, and survives the App Store install flow — meaning a user who taps a link, installs the app, and opens it for the first time still lands on the right screen. Firebase Dynamic Links was deprecated in 2025, so teams should evaluate alternatives like Branch.io or implement deferred deep linking themselves using the referrer API on Android.
For most production apps, Universal Links and App Links are the right default. The HTTPS verification file must be served without redirects and with the correct Content-Type: application/json header — this trips up many teams whose CDNs add redirects.
Routing Logic Inside the App
Receiving a deep link URL is one problem. Navigating to the right screen from any app state is another. Consider these scenarios your router needs to handle:
- App is in the foreground — handle the link immediately without disrupting the current screen stack
- App is backgrounded — resume and navigate without losing the user's previous context
- App is cold-started from a notification — initialize the app, authenticate if needed, then navigate
- App is not installed — deferred deep link picks up after install
In React Native, libraries like React Navigation handle this via a linking config that maps URL patterns to screen names. In Flutter, GoRouter does the same. Native iOS apps typically implement scene(_:openURLContexts:) and application(_:continue:restorationHandler:) for Universal Links. The cold-start case is the one most often broken in QA because it requires testing with the app fully killed — not just backgrounded.
Engagement Best Practices That Move the Needle
The engineering is only half the problem. Here's what separates teams with 8–12% click-through rates from teams stuck at 1–2%:
| Practice | Why It Works | Common Mistake |
|---|---|---|
| Segment by behavior, not just demographics | Users who viewed a product 3x but didn't buy respond to different copy than first-timers | Sending the same message to all users |
| Time notifications to local timezone | Delivery at 2 AM generates unsubscribes, not opens | Scheduling in UTC and forgetting timezone offsets |
| Limit frequency | More than 2–3 per week per user typically increases unsubscribe rates | Treating push like email newsletters |
| Personalize the payload | "{name}, your order shipped" vs. "Your order shipped" | Static titles and bodies with no personalization |
| Always deep-link to the relevant screen | Reduces friction — users don't have to search for what the notification referenced | Linking to the app home screen |
One pattern worth borrowing from high-retention consumer apps is the triggered notification — sent when a user completes or fails to complete a specific action, not on a calendar schedule. A user who adds items to a cart but doesn't check out within 4 hours is a much better target for a notification than a user who's been inactive for 14 days. Event-driven push requires more backend work but consistently outperforms scheduled campaigns.
Measurement: What to Track and What to Ignore
Delivery rate and open rate are the obvious metrics but they can mislead. A 20% open rate on a badly segmented send is worse for your app than a 6% open rate on a highly targeted one, because the 20% case may be driving unsubscribes or permission revocations faster. Track these instead:
- Notification-attributed conversion: Did the user complete the intended action after opening from a notification?
- Permission retention rate: What percentage of users still have push enabled 30/60/90 days after install?
- Unsubscribe velocity: Are permission revocations spiking after a specific campaign?
- Deep link success rate: What percentage of notification opens successfully land on the target screen vs. the home screen (indicating a routing failure)?
Frequently Asked Questions
Do push notifications work the same on iOS and Android?
The delivery mechanisms differ significantly. Android uses FCM directly and, on Android 13+, requires explicit runtime permission. iOS routes through APNs (even when using Firebase), requires a one-time user permission prompt, and has stricter background delivery rules. Testing must cover both platforms independently — behavior that works on Android often doesn't translate to iOS without adjustments.
What is the difference between a deep link and a universal link?
A deep link is the general concept — a URL that opens a specific screen in an app. A universal link is Apple's specific implementation using verified HTTPS URLs, which fall back gracefully to a web page if the app isn't installed. App Links is Android's equivalent. Custom URI schemes (like myapp://) are an older deep-link approach that doesn't fall back gracefully and should generally be replaced with universal/app links in new projects.
How do I handle deep links when the app isn't installed yet?
This is called deferred deep linking. The original link stores the intended destination, the user goes through the App Store or Play Store install flow, and on first launch the app retrieves the intended destination and navigates there. Firebase Dynamic Links (now deprecated) handled this automatically. Current options include Branch.io, Adjust, or a custom implementation using Android's install referrer API and a server-side token lookup on iOS.
Why are my push notification open rates so low?
Low open rates almost always trace back to one or more of: poor timing (wrong time of day or timezone), irrelevant content (no segmentation or personalization), notification fatigue (too many messages), or a mismatch between the notification copy and what the user finds when they open it. Start by auditing your notification frequency and segmentation before assuming it's a technical problem.
When you're ready to build this, Mexilet can help — explore our mobile app development and software development team.
If you're building or rebuilding the notification and deep-link layer for a mobile app — or if your current implementation is losing users at the permission prompt — the team at Mexilet Technologies works with product teams and software companies worldwide to get this architecture right from the start. Get in touch to talk through your specific setup and find out where the engagement drop-off is really happening.
