Growth & Strategy14 min read

Android Push Notification Practices That Cost You Users

Four documented gates decide whether an Android push arrives: permission, channel, payload and token. What each silently costs, and the practice that fixes it.

Ahmed Akash
Ahmed Akash

Need testers who stay active for all 14 days?

TesterBee matches you with 12 real Android testers in under 24 hours. Refund if Google rejects you over tester engagement.

Get 12 testers
On this page
  1. 01The short answer
  2. 02The four gates between your send call and a user's screen
  3. 03Gate 1: the permission users can switch off permanently
  4. 04Gate 2: the channel settings you cannot change after shipping
  5. 05Gate 3: the payload decides who draws the notification
  6. 06Gate 4: sending to installs that no longer exist
  7. 07What you can and cannot measure
  8. 08The practice list
  9. 09Frequently Asked Questions
  10. 10Sources

The short answer

Most Android push problems are not copywriting problems. They are four mechanical gates: the POST_NOTIFICATIONS permission, the notification channel, the payload type, and a registration token that may point at an app the user deleted months ago. Each gate is documented, each one can drop a message without writing anything useful to your logs, and two of them are decided at release, before anyone writes a single line of notification text.

If you want notifications that keep users rather than push them out, spend the effort on the two gates the user controls - permission and channel. Those are the only two you get one shot at.

The four gates between your send call and a user's screen

A push message does not travel in one step. It passes four checks in order, and a failure at any one of them produces the same result: nothing appears, and your server may still record a successful send. The diagram is the map for the rest of this article.

Push delivery: four gates in order 1. Permission 2. Channel 3. Payload 4. Token user can revoke frozen at creation system or app valid install?
The four checks a push passes in order. Gate 1 and gate 2 are the ones the user owns, and neither can be undone by a server-side change.

Gate 1: the permission users can switch off permanently

Android 13 (API level 33) introduced a runtime permission for posting notifications: POST_NOTIFICATIONS. Google's guidance for the permission is blunt about the default state: "If a user installs your app on a device that runs Android 13 or higher, your app's notifications are off by default." A new install on a modern phone starts with no notification access at all until your app asks and the user says yes.

That is the part most teams handle. The part they miss is what a refusal does, and how much it depends on the target SDK you shipped.

What each answer in the dialog actually does

User action What Android does What your app should do next
Allow All notification channels are allowed, including foreground service notices. Send what the user opted in for. Nothing else.
Don't allow All channels are blocked except a few specific roles. Foreground service notices appear in the Task Manager but not in the notification drawer. Stop asking in-app. If a feature genuinely needs notifications, deep link to the app's notification settings.
Swipe the dialog away The permission state does not change. Ask again later at a real moment of intent, not on the next screen.
Don't allow, on an app targeting 12L or lower The app is not prompted again until it is uninstalled and reinstalled, or updated to target Android 13 or higher. This is the expensive case. Ship targeting 33 or higher so a single tap does not end the conversation.

Timing is a documented decision, not a growth trick

Google's own guidance is to wait: "Before you ask users to grant any permissions, let them familiarize themselves with your app." The examples given are all moments where the user has just done something that a notification would serve - tapping an alert bell, following an account, submitting a food order. Google also offers a slower alternative: wait until the third or fourth launch before asking.

The reason this matters more than the wording of the prompt is the target SDK behaviour. If you target API 32 or lower, the system shows the dialog the first time the app starts an activity after a notification channel exists - in practice, on first launch, before the user has any idea what your app is for. If you target API 33 or higher, "your app has complete control over when the permission dialog is displayed". The prompt you get by default on older targets is the worst possible one: an unexplained request at cold start.

Check before you send, and again after an OS upgrade

Before posting, confirm the state with areNotificationsEnabled(). When it returns false, you have two honest options: skip the send and stop counting it, or route the user to the system settings page for your app with Settings.ACTION_APP_NOTIFICATION_SETTINGS. Silently posting into a blocked permission and reporting it as delivered is how dashboards stop matching reality.

One more detail that explains a lot of confusing analytics: when a device upgrades to Android 13 or higher, the system pre-grants the permission to eligible apps - those with an existing notification channel and notifications not already disabled by the user. If your delivery rate holds steady across an OS update while a competitor's collapses, that difference is often pre-grant eligibility, not a better send strategy.

Gate 2: the channel settings you cannot change after shipping

Since Android 8.0 (API level 26), every notification must belong to a channel. Google is explicit about the failure mode: if you target 8.0 or higher and post a notification without specifying a channel, "the notification doesn't appear and the system logs an error". That error is in Logcat on the device, not in your push provider's dashboard, which is why it can reach production unnoticed.

The harder constraint is what happens after the channel exists. In Google's words: "After you create a notification channel, you can't change the notification behaviors. The user has complete control at that point. However, you can still change a channel's name and description." Importance is included in that freeze: "Once you submit the channel to the NotificationManager, you can't change the importance level."

Why your redesigned notifications still behave like the first release

Channel settings are stored by channel ID, per install, and they persist across app updates. If version 4.0 of your app creates the same channel ID with a new importance level, nothing changes on any device that already has that channel: the user's stored setting wins, and your new constant is ignored. Recreating a channel with its original values is a no-op, so the startup code that creates channels is safe to keep - it just cannot amend them.

There is a mechanical way out: a new channel ID starts with the settings you declare. That is a legitimate move when you are genuinely splitting one overloaded channel into purposes. It is a bad move when the goal is to escape a mute the user chose, because the user can see the new category in settings and mute it again - this time with a reason to distrust the app.

Choosing importance once, on purpose

User-visible level Constant What it does
Urgent IMPORTANCE_HIGH Sound plus a heads-up banner over whatever is on screen.
High IMPORTANCE_DEFAULT Makes a sound, no heads-up banner.
Medium IMPORTANCE_LOW No sound, still visible in the shade.
Low IMPORTANCE_MIN No sound, no status bar icon.
None IMPORTANCE_NONE No sound, no status bar, no shade entry - effectively off.

The practical rule follows from the freeze: decide the level for the lifetime of the channel, because that is roughly how long it will live. Order updates and security alerts belong at high; a re-engagement campaign does not, whatever the growth dashboard says this quarter. One overloaded "general" channel at urgent importance is a reliable way to teach users to mute everything an app sends.

Gate 3: the payload decides who draws the notification

Firebase Cloud Messaging accepts two kinds of message, and the difference is not cosmetic - it decides whether your code runs at all. The behaviour table in Google's receive-messages guide is the one to memorise:

App state Notification message Data message Both payloads
Foreground onMessageReceived onMessageReceived onMessageReceived
Background System tray, drawn by Android onMessageReceived Tray for the notification, data in the intent extras

The consequence: if you build a custom notification - your own layout, your own channel choice, your own grouping - and send it as a notification message, your code never sees it when the app is in the background. Android draws a stock notification instead, using whatever channel the payload names. Teams that test only in the foreground see it working, then ship a payload that renders differently for anyone who is not inside the app - which is most of the audience.

Two practical corollaries. First, send data messages when you need control, and post the notification yourself. Second, onMessageReceived runs with "a short execution window" - long work there gets cut off, so anything that takes more than a few seconds belongs in WorkManager, not in the callback.

Gate 4: sending to installs that no longer exist

The last gate is the least visible, and it is the one an app chasing retention is most likely to be failing. FCM's registration guidance describes stale registrations as "associated with inactive devices that have not connected to FCM for over a month", and for Android, "when a registration has been inactive for 270 days, FCM considers it expired and garbage collects it. Once a registration is expired, FCM marks it as invalid and rejects sends to it."

The reporting consequence is specific and easy to misread. Sending to stale registrations shows up in delivery data as "a dramatic (but not actually valid) drop in delivery rates". If you have never pruned your token table, your delivery rate is partly a measurement of how many of your old users left - which is exactly the number you are trying to move.

Tokens also change on their own. The onNewToken callback fires when a token is first generated and whenever it is replaced - Google lists the triggers as the app being restored to a new device, the user uninstalling and reinstalling, and the user clearing app data. A user who uninstalls and comes back arrives with a new token, so the reinstall you won is only reachable if the app uploads the new token and your server replaces the old one.

What you can and cannot measure

Neither Play Console nor Firebase will tell you that a notification caused an uninstall. Play reports installs, uninstalls and retention cohorts; Firebase's messaging reports record messages sent and opened, plus "impressions" (notifications seen by users) for Android. Attribution from a notification to a later uninstall is not in either dataset, which is why specific claims like "push cut uninstalls by 30%" need a controlled cohort comparison rather than a dashboard read.

If uninstall rate is the number you are trying to move, the measurement side - which Play Console reports to read and how to split retention cohorts - is covered in our guide to reducing app uninstalls and 30-day retention. This page is about the notification mechanics that sit upstream of it.

What you can measure honestly: the share of installs that grant the permission, the share that later revoke it, opens and impressions per send, and the retention curve of users split by opt-in status. If you cannot show that gap, you do not have evidence that notifications are helping.

One disclosure, since the rest of this site reports its own campaign numbers: TesterBee runs closed-testing campaigns, not post-launch retention programmes, so none of the guidance here is drawn from our campaign data. Every mechanism above is sourced to the Android and Firebase documentation in the list at the end.

The practice list

  1. Ship targeting API 33 or higher. It is the difference between one refusal and a permanent one, and it puts the dialog under your control.
  2. Ask after the user has seen the value. Google's examples are actions: an alert bell, a follow, a completed order. Cold start is the worst moment and the most common one.
  3. One channel per purpose, named for the purpose. Channel IDs are effectively permanent API. Do not name one after a campaign.
  4. Set importance as if you can never change it, because you cannot. Marketing messages should not be urgent.
  5. Check areNotificationsEnabled() before sending and route blocked users to the app's notification settings instead of posting into the void.
  6. Use data messages when you draw the notification yourself, and test in the background, not just in the foreground.
  7. Store the token with a timestamp, refresh it in onNewToken, and prune stale entries. Uninstalls are the population you are trying to reduce, not a data-cleaning chore.
  8. Report opens and impressions, not sends. A send is a claim about your server; an impression is a claim about a person.

Frequently Asked Questions

Do push notifications actually reduce uninstalls?

They can support retention, but the mechanism is narrower than most articles suggest: a notification only helps if the permission is granted, the channel is not muted, and the message is relevant to something the user did. Google does not publish a figure linking notification behaviour to uninstall rates, so treat any specific percentage you see quoted as that author's own data or an estimate.

Why do my notifications not appear on Android 13 and later?

Check four things in order: whether the permission was granted at all (new installs start with notifications off), whether the channel is blocked or muted in system settings, whether you posted without a channel on a target of API 26 or higher, and whether the token is still valid. The first three are silent failures by design.

Can I change a notification channel's importance after release?

No. Once the channel exists, your app can change only its name and description; the user can change importance, sound and vibration at any time. A new importance level requires a new channel ID, and users who had the old channel muted will see the new one as a new category.

A user denied the notification permission once. Can I ask again?

It depends on your target SDK. If you target API 32 or lower, one tap on Don't allow means no further prompt until the app is reinstalled or updated to target API 33 or higher. If you target 33 or higher you control the dialog, but Google's documentation does not promise unlimited re-prompts, so the reliable route is a deep link to the app's notification settings.

Do uninstalled users still receive my notifications?

No. Sends to invalid registrations are rejected, and registrations that stay inactive on Android are expired after 270 days and garbage collected. If the same person reinstalls, the app registers again and reports a new token through onNewToken.

Are notification channels the same as "categories" in Android settings?

Yes. Google's channel documentation notes that "the user interface refers to notification channels as 'categories'", so a user reporting categories is describing your channels.

Do I need POST_NOTIFICATIONS to run a foreground service?

No - apps do not need the permission to launch a foreground service, and they must still post a notification when they do. But if the notification permission is denied, that foreground service notice appears in the Task Manager and not in the notification drawer.

Should I send notification messages or data messages?

Notification messages when you want Android to draw a standard notification and you have nothing custom to do. Data messages when you need your own layout, your own channel selection, or any logic at receipt time - because a notification message never reaches your code while the app is in the background.

Sources

  • Android Developers, Notification runtime permission (page updated 28 October 2026; read 3 November 2026) - default off state, dialog outcomes, the target-SDK caution, pre-grant eligibility, areNotificationsEnabled(), request timing guidance.
  • Android Developers, Create and manage notification channels (read 3 November 2026) - mandatory channels from Android 8.0, the no-channel error, immutability of behaviours and importance, importance levels, "categories" wording.
  • Firebase Cloud Messaging, Receive messages in Android apps (read 3 November 2026) - foreground and background behaviour per payload type, the short execution window for onMessageReceived, messaging report metrics.
  • Firebase Cloud Messaging, Best practices for FCM registration management (read 3 November 2026) - stale registrations, the 270-day Android expiry, the delivery-rate reporting artefact, storing timestamps.
  • Firebase Cloud Messaging, Set up a Firebase Cloud Messaging client app on Android (read 3 November 2026) - when onNewToken fires: new device restore, uninstall and reinstall, cleared app data.
  • Android Developers reference, Settings.ACTION_APP_NOTIFICATION_SETTINGS (read 3 November 2026) - the intent that opens a single app's notification settings.
Ahmed Akash
Ahmed Akash·Senior Full Stack Developer & Founder of TesterBee

Senior full stack developer specializing in Laravel, React, and Firebase. Helped 1,300+ Android developers pass Google Play Closed Testing and publish apps. Expert in Play Console, production access, and Android app publishing.

November 3, 2026 · 14 min readLinkedInGitHub

Get 12 testers into your closed test in 24 hours

Matched Android testers who open your app every day of the 14-day window, so your production access review has real engagement to look at.

Start your closed test