Implementing App notifications: The FOSS way

Rachit KhuranaRachit Khurana
8 min read

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

UnifiedPush Flow

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:

  1. Your app registers for notifications directly with FCM endpoints

  2. No Firebase project or configuration required

  3. No Firebase libraries needed

  4. 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:

  1. Generate VAPID key pairs

  2. Use WebPush protocol for sending notifications

  3. 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 modifications

  • Used my own version of the library as the npm package instead

  • Tested various permission configurations and background processing settings

Me debugging for hours

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, or

  • Using hexadecimal IDs (like MongoDB ObjectIds or UUIDs)

The solution: Unified push library requires notification IDs to be numeric only.

Facepalm

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!

10
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 🖤