First, the app needs to know where to send the message
Before a notification can be sent, the app needs an identifier for a specific app installation on a device.
With Firebase Cloud Messaging, this involves a Firebase Installation ID (FID) and the registration token system. The backend stores this identifier so it can target a specific app installation later. However, the identifier does not necessarily stay the same forever. It can change, for example, after an app is reinstalled or when the user switches devices. The app therefore needs to handle updates to this information.
In simple terms:
User installs the app → the app registers → the backend stores the identifier → the system can later determine where to send the message.
The user also plays an important role here. Being technically able to send a push notification does not mean the user will actually see it.
On iOS, the app needs permission to display notifications, sounds, or badges. The user can deny this permission or change it later in the system settings. On Android, runtime permission for regular notifications has been required since Android 13 through POST_NOTIFICATIONS.
So if the user has not allowed notifications, everything else can work correctly – and the banner still will not appear.
What happens after clicking “Send”?
Imagine a marketing team prepares a message and clicks Send.
The marketing tool or admin interface sends a request to the backend. The backend creates the message with its content, target device or audience segment, and other settings.
With Firebase Cloud Messaging, the application can send messages through its own server to the FCM backend, which then routes them towards the device.
There is an important distinction here:
“Message accepted” ≠ “message delivered”.
If FCM accepts the request, it means the message has been accepted for delivery. It does not mean that the message has already reached the phone.
The message then travels through the appropriate push service
The next part of the journey depends on the platform.
On Android, Firebase Cloud Messaging (FCM) is used. On Apple devices, the key service is Apple Push Notification service (APNs). When communicating with Apple devices, FCM can use APNs as the platform transport layer.
In simplified form, the journey might look like this:
Marketing tool → backend → FCM → Android device
or:
Marketing tool → backend → FCM → APNs → iPhone
A push notification is therefore not just an app feature. It is communication between several different systems.
What if the phone is offline?
The phone may not be available when the message is sent. The user might have no signal, no data connection, or the device might simply be turned off.
The push system can therefore hold the message for a certain period and try to deliver it later. With FCM, it is also possible to define a message's time-to-live – how long the system should keep trying to deliver it. Once that period expires, the message can expire.
This makes the distinction between time-sensitive notifications and messages that can tolerate delays particularly important.

Even an online phone does not necessarily mean an immediate notification
Delivery can also be affected by message priority and the state of the device.
For example, FCM distinguishes between normal and high priority messages. Normal-priority messages may be delivered later in certain situations, while high priority is intended for more time-sensitive, user-visible content.
The operating system also applies its own rules related to battery usage, background applications, and other restrictions.
A push notification is therefore not a guaranteed teleport from the backend to the screen.
Once the message arrives, the operating system also gets involved
When the message reaches the device, what happens next depends on the type of message that was sent and the state of the application.
FCM, for example, distinguishes between notification messages and data messages. A notification message can be displayed to the user by the system, while a data message allows the application to process the data using its own logic.
On iOS, the system also respects the user's notification permissions and settings – such as alerts, sounds, and badges.
That means there are still several decisions between:
“The server sent the message”
and
“The user saw the notification.”
Push notifications are designed to work even when the app is not currently open. That does not mean the app can perform any arbitrary operation in the background, though. Operating systems have their own rules for background processing and handling received messages.
Developers therefore need to define exactly what should happen when a particular type of push notification is received.
It could simply be:
“Display this message.”
Or it could involve a more complex flow:
“Receive data → update state → prepare the screen → display the notification → open a specific part of the app when the user taps it.”
The journey does not end when the user taps the notification
Eventually, the user taps the notification and the app opens.
The push can contain information telling the app where to take the user – for example, to a specific product, order, chat, or article.
That is why deep linking and post-click behavior are also important parts of push notification implementation.
A good push is therefore not simply:
“We have a new update!”
It is the entire chain:
relevant message → user tap → correct screen in the app.
What if the device is no longer valid?
In production, applications gradually accumulate outdated registrations.
A user may uninstall the app, switch to a new device, or clear the app's data. The registration identifier can therefore change or become invalid.
The backend should regularly remove invalid and long-inactive registrations. Otherwise, it will keep sending requests to non-existent targets and can distort delivery data.
The full push notification journey
When we put the whole process together, it looks roughly like this:
User installs the app
↓
The app registers with the push service
↓
The backend stores the device/installation identifier
↓
The user grants notification permission
↓
The marketing tool or backend creates the message
↓
The backend sends it to FCM or APNs
↓
The push service routes it towards the device
↓
The operating system processes it according to its own rules
↓
The user sees the notification
↓
After the user taps it, the app processes the action and can open a specific screen
And every one of these steps can be a potential point of failure.
A push notification is not just one button
When a user says:
“I didn't get the notification,”
a developer should therefore not only check the message itself.
They need to look at things such as:
- whether the app was registered correctly,
- whether it has a current identifier,
- whether the user allowed notifications,
- whether the backend created the message correctly,
- whether the push service accepted it,
- whether the device was available,
- what priority and time-to-live were configured,
- what the operating system did with the message,
- and what happened after the message was received or tapped.
FCM also provides tools for monitoring delivery and diagnosing issues, including reports in the console and more detailed data available through the Data API and BigQuery.
That is why it makes sense to think of push notifications as a distributed system, rather than simply a “Send” button.
For the user, it is one banner on a screen.
For the application, it is a journey through servers, identifiers, APIs, network connections, and operating system rules.
And once you understand that journey, it becomes much easier to understand why a push notification sometimes arrives immediately, sometimes arrives later – and why “sent” does not necessarily mean “delivered.”