Implementing App notifications: The FOSS way

Table of contents
- The Beginning: Building My First "Real" App
- The Notification Wall
- The F-Droid Reality Check
- Down the Rabbit Hole: Discovering UnifiedPush
- Understanding the Foundation: WebPush and VAPID
- How UnifiedPush Works
- The Elegant Solution: Embedded Distributors
- The Implementation Reality
- The Results: Better Than Expected
- The iOS Reality: Apple's Walled Garden
- What's Next?
- For Fellow Developers
- Resources

The Beginning: Building My First "Real" App
Recently, I embarked on building a Splitwise-like expense-splitting app using React Native and Expo. This wasn't just another side project—it was my first proper dive into this tech stack, with React Native for mobile development and Golang powering the backend.
From the start, I had one clear goal: avoid third-party dependencies wherever possible. No BaaS solutions like Supabase, Appwrite, or Firebase. I wanted to build everything myself to truly understand the underlying technologies.
This philosophy led me down some interesting rabbit holes. I even implemented native Google Login without relying on any auth provider service—quite challenging since Expo's official Documentation's free solution was being deprecated, and the new approach was locked behind a SaaS paywall. But I persevered and found a way to make it work using a package that used the native credential manager APIs and worked great.
The Notification Wall
Then came the next feature: push notifications.
A quick Google search seemed to confirm what every developer "knows"—Firebase Cloud Messaging (FCM) is the solution for mobile notifications. FCM appeared to be the only viable option.
Reluctantly, I integrated FCM into both my app and backend. It worked.
The F-Droid Reality Check
Everything changed when I decided to release my app on F-Droid—the open-source Android app marketplace.
That's when I discovered F-Droid's strict policy: apps must be completely open source and cannot depend on any proprietary services. My FCM integration meant I will not be able to release my app on F-Droid.
But this sparked a crucial question: If other apps on F-Droid support notifications, there must be alternatives to FCM, right?
Down the Rabbit Hole: Discovering UnifiedPush
This question sent me deep into research mode. And that's when I stumbled upon something revolutionary: UnifiedPush.
UnifiedPush is a decentralized push notification system that lets you choose the service you want to use. It's designed to be privacy-friendly, flexible, and open—making it perfect if you want control over your push notifications.
Instead of forcing all notifications through a single company's servers (like FCM), UnifiedPush lets users choose their notification provider. This was a paradigm shift I hadn't considered.
Understanding the Foundation: WebPush and VAPID
To truly grasp UnifiedPush, I needed to understand how notifications actually work. This led me to discover the open standards that power web notifications:
WebPush Protocol
This is a W3C standard that defines how push notifications work on the web. It's the foundation that browsers use for notifications.
WebPush is defined by 3 standards: for the transport (RFC8030), for the authorization (VAPID, RFC8292), for the encryption (RFC8291)
VAPID (Voluntary Application Server Identification)
Think of this as a way for your server to prove its identity when sending notifications. Instead of using proprietary API keys tied to specific services, you generate your own cryptographic key pair:
Public key: Identifies your app
Private key: Signs your messages
What's beautiful about this approach is that it's service-agnostic. The same VAPID keys and WebPush messages work whether the user chooses Google's servers, a self-hosted solution, or anything in between.
Privacy by Design: Here's a crucial security feature that sets webpush apart—encryption. When your server sends a notification, the message is encrypted using the user webpush keys. This means:
✅ Only your app can decrypt the notification content
✅ Distributors (ntfy, NextPush, etc.) cannot read your messages
✅ Even if using FCM as a distributor, Google cannot see your data
✅ True privacy regardless of which distributor the user chooses
This is fundamentally different from traditional FCM, where Google's servers can potentially access all notification content. With UnifiedPush's VAPID encryption, the distributor is just a secure messenger—they can deliver your encrypted package, but they can't open it.
The Mobile Connection
Here's the key insight: mobile devices can receive WebPush notifications too. The same standards that power web notifications work on mobile platforms.
How UnifiedPush Works
UnifiedPush elegantly bridges the gap between open standards and mobile notifications:
The Flow
Step 1: App Sends Notification
Your server sends a WebPush message to the user's chosen distributor
Step 2: Distributor Delivers Notification
The distributor receives the message and forwards it to your app
Step 3: Device Receives Notification
Your app wakes up and processes the data, showing a notification to the user
What Are Distributors?
Distributors (or Notification Providers) are apps that run in the background and handle notification delivery. Users can choose from various options:
ntfy: Self-hosted, maximum privacy
Sunup: Uses Mozilla's Push Servers
gCompat-UP: Uses Google's infrastructure as a fallback
Custom solutions: Roll your own
The Elegant Solution: Embedded Distributors
Now, you might be thinking: "This sounds great, but requiring users to install another app seems like a UX nightmare. Users will have to install another app"
Here's where UnifiedPush gets really clever: embedded distributor modules.
How It Works
UnifiedPush provides an embedded distributor that your app can use internally. When using this embedded module:
Your app registers for notifications directly with FCM endpoints
No Firebase project or configuration required
No Firebase libraries needed
FCM natively supports WebPush and VAPID standards
This means you can send notifications to your app using direct FCM endpoints without any Firebase setup—just pure WebPush with VAPID authentication.
The Best of Both Worlds
My implementation strategy became clear:
1. Check if user has any external distributors installed
2. If yes → Give them the option to choose their preferred distributor
3. If no → Use the embedded distributor seamlessly
This approach provides:
Zero UX friction for mainstream users
Full choice and privacy for power users
F-Droid compatibility
The Implementation Reality
Server-Side Changes
The backend migration was surprisingly straightforward. I simply replaced the Firebase library with a standard web-push library. Instead of dealing with Firebase service accounts and complex authentication, I just needed:
Generate VAPID key pairs
Use WebPush protocol for sending notifications
Handle subscription management
Mobile App Integration
On the React Native side, replacing FCM with UnifiedPush was not exactly smooth for me. The expo-unified-push
library provided a cleaner API than Firebase's implementation, with better debugging capabilities and more predictable behavior.
The Debugging Nightmare (And Its Embarrassingly Simple Solution)
However, I hit a frustrating wall.
The Problem: Notifications worked perfectly when my app was in the foreground, but completely failed when the app was closed or in the background. This is obviously a deal-breaker for any notification system.
My Desperate Debugging Journey:
Spent countless hours debugging the UnifiedPush library source code
Tried different distributors, thinking it was distributor-specific
Even forked the
expo-unified-push
repository and made custom modificationsUsed my own version of the library as the npm package instead
Tested various permission configurations and background processing settings
Me debugging for hours
The False Victory: My forked version seemed to work initially, which made me think I'd solved some complex architectural issue. I was ready to write a detailed post about the "fixes" needed for background notifications.
The Humbling Reality: The next day, after seeing the developer's comments, I discovered the real culprit. It wasn't the library, the distributors, or any complex background processing limitation.
The actual issue: The notification id
field format.
I had been either:
Omitting the
id
field entirely, orUsing hexadecimal IDs (like MongoDB ObjectIds or UUIDs)
The solution: Unified push library requires notification IDs to be numeric only.
Me realizing the bug was just using wrong ID format
The Lesson: Sometimes the most frustrating bugs have the simplest solutions. What felt like a fundamental architectural problem was actually a basic data format requirement that I'd overlooked.
The Community That Made All the Difference
Throughout this debugging nightmare, I wasn't struggling alone. The UnifiedPush community on Matrix was incredibly supportive and patient with my questions.
Even when I was going down the wrong path with my forked library approach, the community helped me think through the problem systematically. They suggested checking payload formats, testing with different distributors, and reviewing the notification ID requirements—which ultimately led me to discover the numeric ID issue.
The human side of open source: This experience reminded me why I love open source communities. Instead of being stuck with corporate support tickets or waiting for Stack Overflow responses, I had direct access to the people building and maintaining the tools I was using. They were invested in my success because my success meant UnifiedPush was working for more developers.
The Results: Better Than Expected
User Experience Wins
Mainstream users: Zero change in experience—notifications "just work"
Privacy-conscious users: Full control over notification routing
Degoogled device users: Finally have access to full app functionality
The iOS Reality: Apple's Walled Garden
Now, let's address the elephant in the room: iOS.
Apple's ecosystem presents unique challenges for alternative notification systems. Unlike Android, where users can install alternative app stores and modify system behavior, iOS maintains strict control over push notifications through APNs (Apple Push Notification service).
The Current iOS Situation:
All iOS notifications must go through Apple's APNs servers
No alternative notification distributors allowed
Users cannot choose their notification provider
Apps distributed outside the App Store (sideloaded) still require APNs for notifications
Apple being apple, they have won't allow any third-party notification systems to replace APNs. They won't wanna give up control over notifications, which is a shame because it limits user choice and privacy.
What's Next?
I will soon be releasing my app on F-Droid (Hopefully)
But more importantly, I've discovered an entire ecosystem of developers building user-centric, privacy-respecting technology. The UnifiedPush community is growing rapidly, with major apps like Element, Tusky, and FluffyChat all adopting this approach.
For Fellow Developers
If you're building mobile apps and care about user privacy, open standards, or F-Droid compatibility, I highly recommend exploring UnifiedPush. The implementation is often simpler than traditional solutions, and your users will appreciate having real choice in how their data flows.
The future of mobile notifications doesn't have to be controlled by Big Tech. We can build something better—something that puts users first.
Resources
https://unifiedpush.org/news/20250131_push_for_decentralized/
https://unifiedpush.org/
https://developer.chrome.com/blog/web-push-interop-wins
Want to follow my journey building privacy-first mobile apps? Connect with me or check out my other projects!
Subscribe to my newsletter
Read articles from Rachit Khurana directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Rachit Khurana
Rachit Khurana
Full Stack Developer | Pythonista | Student | Programmer | Tech Enthusiast | Learner | Linux 🖤