This document describes how to set up push notifications for the Forward Email Tauri mobile apps on iOS (APNs), Android (FCM), and devices without Google Play Services (UnifiedPush).
sequenceDiagram
participant App as Mobile App<br/>(Tauri + WebView)
participant API as Forward Email<br/>API Server
participant Push as APNs / FCM /<br/>UnifiedPush Gateway
App->>App: 1. Request permission & get device token
App->>API: 2. POST /v1/push-tokens (register token)
API->>API: 3. Store device token
Note over App,API: Later, when new mail arrives...
API->>Push: 4. Send push via gateway
Push->>App: 5. Deliver push notification
App->>App: 6. Display notification & navigate
-
App Launch: The Tauri app requests push notification permission from the OS and obtains a device token (APNs token on iOS, FCM registration token on Android).
-
Token Registration: The device token is sent to the Forward Email API server via
POST /v1/push-tokenswith the user's authentication token. -
Server-Side Push: When new mail arrives for the user, the server sends a push notification via APNs (iOS) or FCM (Android) using the stored device token.
-
Notification Display: The OS displays the notification even if the app is backgrounded or closed. Tapping the notification opens the app and navigates to the relevant message.
- Apple Developer Program membership
- Xcode installed on macOS
- A physical iOS device (push notifications do not work in the simulator)
-
Open the Tauri iOS project in Xcode:
cd src-tauri/gen/apple open ForwardEmail.xcodeproj -
Select the project target → Signing & Capabilities.
-
Click + Capability → Add Push Notifications.
-
Also add Background Modes and check Remote notifications.
-
Go to Apple Developer Portal.
-
Click Keys → Create a Key.
-
Enter a name (e.g., "Forward Email Push Key").
-
Check Apple Push Notifications service (APNs).
-
Click Continue → Register.
-
Download the
.p8key file. Save it securely — it can only be downloaded once. -
Note the Key ID and your Team ID (visible in the top-right of the portal).
Set the following environment variables on the Forward Email API server:
# APNs Configuration
APNS_KEY_ID=ABC123DEFG # Your APNs Key ID
APNS_TEAM_ID=TEAM123456 # Your Apple Developer Team ID
APNS_KEY_PATH=/path/to/AuthKey_ABC123DEFG.p8 # Path to the .p8 key file
APNS_BUNDLE_ID=net.forwardemail.mail # Must match the app bundle ID
APNS_PRODUCTION=true # false for sandbox/development
# Alternative: Base64-encoded key (for cloud deployments)
# APNS_KEY_BASE64=LS0tLS1CRUdJTi...The Entitlements.plist should include:
<key>aps-environment</key>
<string>production</string>For development builds, use development instead of production.
The server should use a library like apns2 (Node.js) or a2 (Rust) to send
push notifications:
// Example: Node.js server-side APNs push
const apn = require('apn');
const provider = new apn.Provider({
token: {
key: process.env.APNS_KEY_PATH,
keyId: process.env.APNS_KEY_ID,
teamId: process.env.APNS_TEAM_ID,
},
production: process.env.APNS_PRODUCTION === 'true',
});
async function sendPushNotification(deviceToken, { title, body, data }) {
const notification = new apn.Notification();
notification.pushType = 'alert'; // REQUIRED for iOS 13+ (apns-push-type header)
notification.alert = { title, body };
notification.topic = process.env.APNS_BUNDLE_ID; // e.g. 'net.forwardemail.mail'
notification.badge = data.unreadCount || 1;
notification.sound = 'default';
notification.priority = 10; // Immediate delivery for visible alerts
notification.payload = { type: 'new-message', ...data };
notification.expiry = Math.floor(Date.now() / 1000) + 86400; // 24h TTL
const result = await provider.send(notification, deviceToken);
return result;
}Important: The original
apnpackage (v2.2.0) on npm does not support thepushTypeheader required by iOS 13+. Use@parse/node-apn(maintained fork) orapn2which support the HTTP/2 APNs protocol with all required headers.
- Google account with Firebase access
- Android SDK installed
-
Go to Firebase Console.
-
Click Add project → Enter "Forward Email" → Continue.
-
Disable Google Analytics (optional) → Create project.
-
In the Firebase project, click Add app → Android.
-
Enter the package name:
net.forwardemail.mail -
Enter the app nickname: "Forward Email"
-
Download
google-services.json. -
Place it in
src-tauri/gen/android/app/google-services.json.
-
In Firebase Console → Project Settings → Cloud Messaging tab.
-
If FCM API (V1) is enabled, note the Sender ID.
-
For the server, you'll use a Service Account key:
- Go to Project Settings → Service accounts.
- Click Generate new private key.
- Download the JSON key file.
# FCM Configuration
FCM_PROJECT_ID=forward-email-12345 # Your Firebase project ID
FCM_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
# Alternative: Base64-encoded service account JSON
# FCM_SERVICE_ACCOUNT_BASE64=eyJ0eXBlIjoi...// Example: Node.js server-side FCM push (using firebase-admin)
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.cert(
JSON.parse(fs.readFileSync(process.env.FCM_SERVICE_ACCOUNT_PATH, 'utf8')),
),
});
async function sendPushNotification(deviceToken, { title, body, data }) {
const message = {
token: deviceToken,
notification: { title, body },
data: {
type: 'new-message',
uid: String(data.uid || ''),
mailbox: data.mailbox || 'INBOX',
},
android: {
priority: 'high',
notification: {
channelId: 'new-mail',
sound: 'default',
clickAction: 'OPEN_MAIL',
},
},
};
const response = await admin.messaging().send(message);
return response;
}All push notifications from the Forward Email server should use this payload format:
{
"type": "new-message",
"uid": "12345",
"mailbox": "INBOX",
"from": "sender@example.com",
"subject": "Hello World",
"unreadCount": 5
}| Type | Description | Navigation Target |
|---|---|---|
new-message |
New email received | #INBOX/{uid} |
calendar-event |
Calendar event created/updated | #calendar |
contact-update |
Contact created/updated | #contacts |
The client-side push notification handling is in src/utils/push-notifications.js.
import { initPushNotifications } from './utils/push-notifications.js';
// Call after user signs in
await initPushNotifications({
authToken: userAuthToken,
});import { cleanupPushNotifications } from './utils/push-notifications.js';
// Call on sign-out
await cleanupPushNotifications(userAuthToken);| Variable | Required | Description |
|---|---|---|
APNS_KEY_ID |
iOS | APNs authentication key ID |
APNS_TEAM_ID |
iOS | Apple Developer Team ID |
APNS_KEY_PATH |
iOS | Path to .p8 key file |
APNS_BUNDLE_ID |
iOS | App bundle identifier |
APNS_PRODUCTION |
iOS | true for production, false for sandbox |
FCM_PROJECT_ID |
Android | Firebase project ID |
FCM_SERVICE_ACCOUNT_PATH |
Android | Path to service account JSON |
| Variable | Required | Description |
|---|---|---|
GOOGLE_SERVICES_JSON |
Android | Path to google-services.json (default: scripts/android/google-services.json) |
APPLE_TEAM_ID |
iOS | Apple Developer Team ID (for signing) |
IOS_EXPORT_METHOD |
iOS | app-store, ad-hoc, enterprise, or release-testing |
APNS_PRODUCTION |
iOS | Set to true to force production APNs gateway |
IOS_SIGNING_IDENTITY |
iOS | Code signing identity (default: Apple Distribution) |
IOS_PROFILE_NAME |
iOS | Provisioning profile name |
The push token registration endpoint (/v1/push-tokens) is hardcoded to the
Forward Email API — no client-side URL configuration is needed.
- Build the app for a physical device (not simulator).
- Sign in and grant notification permission.
- Background the app.
- Send an email to the signed-in account.
- Verify the push notification appears.
- Build the app and install on a device or emulator with Google Play Services.
- Sign in and grant notification permission.
- Background the app.
- Send an email to the signed-in account.
- Verify the push notification appears.
Use the Forward Email API to verify token registration:
curl -X GET https://api.forwardemail.net/v1/push-tokens \
-H "Authorization: Bearer YOUR_AUTH_TOKEN"For Android devices without Google Play Services (e.g., GrapheneOS, /e/OS, LineageOS), the app supports UnifiedPush as a fallback push notification provider.
- The user installs a UnifiedPush distributor app (e.g., ntfy, NextPush).
- On app launch, if FCM token acquisition fails, the app checks for a UnifiedPush distributor.
- If found, the app registers with the distributor and receives an endpoint URL.
- The endpoint URL is sent to the Forward Email API server.
- When new mail arrives, the server sends an HTTP POST to the endpoint URL.
- The distributor forwards the message to the Forward Email app.
| Priority | Provider | Condition |
|---|---|---|
| 1 | FCM (Android) / APNs (iOS) | Google Play Services available |
| 2 | UnifiedPush | Distributor installed, no FCM |
| 3 | WebSocket | Always active when app is in foreground |
FCM and UnifiedPush handle background push (app closed or backgrounded). WebSocket provides real-time updates when the app is in the foreground. They complement each other.
The Forward Email API server sends push notifications to UnifiedPush endpoints using standard HTTP POST. No special SDK is required:
// Example: Send push to a UnifiedPush endpoint
async function sendUnifiedPush(endpoint, payload) {
await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
}The payload format is the same as the WebSocket event format:
{
"event": "newMessage",
"mailbox": "INBOX",
"message": { "uid": 42 }
}- Install ntfy from F-Droid.
- Build and install the Forward Email app on a device without Google Play Services.
- Sign in — the app should detect the ntfy distributor and register.
- Background the app.
- Send an email to the signed-in account.
- Verify the push notification appears via ntfy.
- No token received: Ensure Push Notifications capability is enabled in Xcode and the provisioning profile includes push notifications.
- Notifications not delivered: Check that
aps-environmentin entitlements matches the APNs environment (sandbox vs production). - Token invalid: APNs tokens are environment-specific. A sandbox token won't work with the production APNs endpoint.
- No token received: Ensure
google-services.jsonis in the correct location and the Firebase project is properly configured. - Notifications not shown: Check that the notification channel (
new-mail) is created and not disabled by the user in system settings. - FCM quota exceeded: Firebase has daily limits for free-tier projects. Consider upgrading to the Blaze plan for production use.
- WEBSOCKET.md — WebSocket protocol (complements push for foreground delivery)
- ARCHITECTURE.md — Full architecture document
- SECURITY.md — Notification security hardening details
- DEVELOPMENT.md — Building and testing on physical devices