The React Native side of push for the platform's mobile clients: token registration and refresh, the Android 13 permission, foreground banners, and deep-link routing that works whether the app is open, in the background or not running.
3app states routed
RN 0.79React 19, TypeScript
FCM+ Notifee
1automatic retry on 401
Try it
Send a push to an invented app
Pick an app state and a deep link, press Send, then tap the notification. Turn on a failure drill to see the 401 retry.
push simulator · synthetic dataInteractive demo
The interactive simulator needs JavaScript. It shows a campaign composer, the delivery pipeline and a phone that receives the notification and opens the linked screen.
A simulation of the client behaviour. No message leaves your browser.
Problem
Brands with mobile apps wanted to send campaigns that open the right screen, not just the app.
A tap has to land on the product, category or cart in three different situations, each handled by a different API. Tokens change, sessions expire, and Android 13 added a runtime permission that silently drops notifications if nobody asks for it.
My role
I built the React Native modules and the reference app that the integration is tested with. The backend that queues and sends messages is an existing Java/Spring service with RabbitMQ and an FCM worker; I integrated with it and traced its queue bindings when debugging delivery.
FCM token registration through the profile endpoint, and a listener that picks up refreshed tokens
JWT login with one automatic re-login and retry on 401
Android 13+ permission check and a notification channel
Payload normalisation and tap routing for foreground, background and cold start
A debug panel and scripts to send by token or topic
Architecture
From campaign to screen
PanelA marketer creates the campaign.
Push backendExisting Spring service queues it on RabbitMQ.
FCM workerExisting worker sends through Firebase Cloud Messaging.
DeviceNormalise payload, show the banner, handle the tap.
RouteOpen Home, Category, Product or Cart.
Violet tint: parts I wrote.
Engineering decisions
What was hard and what I did
One tap handler per app state
Foreground messages arrive in onMessage and are shown with Notifee. Background taps come through onNotificationOpenedApp, cold starts through getInitialNotification. All three feed one router, so a link behaves the same everywhere.
One payload shape
Campaigns arrive with fields in different places. A normaliser maps them to title, body, image, route and data before anything renders, so screens never parse raw messages.
Expired sessions without user impact
On a 401 the client logs in again once and retries the call. If the retry fails it stops, instead of looping.
Debugging a system I didn't write
When delivery looked broken I traced the queue bindings in the existing backend to find where messages stopped, and kept the investigation separate from the client fix.
Have a problem worth solving?Let's make it reliable