React Native Push Notifications with Sendrealm: Expo and Bare Setup Guide
Push notification setup in React Native is often described as an SDK installation. The JavaScript package is the easy part. A working production setup also needs an Android app registered in Firebase, an iOS App ID with push enabled, provider credentials, native capabilities, runtime permission, device identity, a trusted backend sender, and a way to diagnose the final result.
Sendrealm brings those pieces into one workflow. The same @sendrealm/react-native package supports bare React Native apps and Expo apps that can include native code. Device registration, identity linking, tags, events, deep-link handling, notification channels, and support diagnostics use a consistent JavaScript API, while Sendrealm communicates with FCM for Android and APNs for iOS.
This guide covers both paths from provider setup to the first backend send. Keep the official pages nearby because they contain the current platform details:
- React Native Expo SDK documentation
- React Native Bare SDK documentation
- Mobile push credentials guide
- Send Push Notification API
- JavaScript SDK documentation
Choose Expo or bare React Native
The choice depends on who owns the native projects.
Use the Expo path when the app uses Expo prebuild, expo run, EAS development builds, or EAS production builds. The Sendrealm config plugin applies the required native configuration during prebuild.
Use the bare path when the repository directly owns and maintains android and ios. Autolinking installs the native module, but the team is responsible for Firebase configuration, Xcode capabilities, and the iOS AppDelegate callbacks described below.
Expo Go is not supported. Mobile push requires native Android and iOS code, and Expo Go does not contain the Sendrealm native module. Use a development build, prebuild, expo run, or EAS instead. This is a build choice, not an SDK failure.
At the time of publication, the bare SDK documentation requires React 18 or newer and React Native 0.76 or newer. Check the current bare requirements when integrating an older application.
Understand the four credentials before touching code
The setup becomes much easier when every identifier has one clear home.
Sendrealm Push App ID
The Push App ID identifies the app inside Sendrealm. It is public and belongs in the mobile SDK configuration. It is not a secret and does not authorize backend sends.
Sendrealm API key
The API key authorizes trusted server-side operations. Store it in the backend’s secret manager or environment. Never ship it in the React Native bundle, app.json, app.config.js, google-services.json, or a mobile environment variable that becomes part of the application.
Firebase files for Android
Android needs two different JSON files with different responsibilities:
google-services.jsonbelongs in the app project. For bare React Native, place it atandroid/app/google-services.json. For Expo, point the config plugin at the file so prebuild copies it into the generated Android app.- The Firebase service account private key JSON belongs in the Sendrealm dashboard. It lets Sendrealm authenticate with FCM HTTP v1 when sending.
These files are not interchangeable. Uploading google-services.json as a provider credential will not give Sendrealm server-side FCM authorization. Committing the service account private key to the mobile repository would expose a production credential.
APNs values for iOS
Sendrealm needs an APNs .p8 private key, Key ID, Apple Team ID, Bundle ID, and APNs environment. Upload those through the Sendrealm provider settings. The private key never belongs in the app.
The Apple App ID and the Xcode or Expo target must use the same Bundle ID. Push Notifications must be enabled for the Apple App ID and the built app target.
The mobile push credentials guide walks through both providers step by step.
Step 1: create the Sendrealm Push App
Open the Sendrealm dashboard, select the correct project, and create or select the Push App for this mobile application. Keep development and production intentionally separated. If development and production use different Android package names or iOS Bundle IDs, configure the exact app identity that initializes the SDK.
Copy the public Sendrealm Push App ID. You will use it during JavaScript initialization.
Next, upload the provider credentials:
- Firebase service account JSON and exact Android package name for Android;
- APNs
.p8, Key ID, Team ID, exact Bundle ID, and correct environment for iOS.
Do not proceed to audience sends yet. First prove that one real development device can register and receive a test.
Step 2A: install Sendrealm in an Expo app
Install the package through Expo so it selects a compatible package version:
npx expo install @sendrealm/react-native
Add the config plugin to app.json or app.config.js:
{
"expo": {
"android": {
"package": "com.example.app"
},
"ios": {
"bundleIdentifier": "com.example.app"
},
"plugins": [
[
"@sendrealm/react-native",
{
"android": {
"googleServicesFile": "./google-services.json",
"notificationIcon": "ic_stat_sendrealm",
"notificationColor": "#111827"
},
"ios": {
"apnsEnvironment": "sandbox",
"enableBackgroundRemoteNotifications": true,
"notificationServiceExtension": true
}
}
]
],
"extra": {
"sendrealmAppId": "YOUR_SENDREALM_APP_ID",
"sendrealmPushEnvironment": "development",
"sendrealmApnsEnvironment": "sandbox"
}
}
}
Only enable background remote notifications if the app actually processes silent or background pushes. Enable the Notification Service Extension when rich image notifications are required. Both settings affect native code and require a new build.
The notificationIcon must refer to an Android drawable resource. Status-bar icons should normally be simple monochrome artwork. If the generated Android project does not contain a matching resource such as res/drawable/ic_stat_sendrealm.xml, Android cannot use the requested icon.
Most importantly, do not put the Sendrealm API key, Firebase service account JSON, or APNs .p8 content inside Expo extra. Expo configuration is built into the client.
Build the Expo native app
For a local native build:
npx expo prebuild --platform android
npx expo run:android
npx expo prebuild --platform ios
npx expo run:ios --device
Use --device for end-to-end APNs testing. A physical iOS device is the reliable test target.
For EAS development builds:
npx eas build --profile development --platform android
npx eas build --profile development --platform ios
Whenever plugin settings change, rerun prebuild or create a fresh EAS build. Reloading JavaScript cannot add an entitlement, service extension, Gradle plugin, or native resource to an already-built binary.
Step 2B: install Sendrealm in a bare React Native app
Install the package and rebuild both applications:
npm install @sendrealm/react-native
npx react-native run-android
npx react-native run-ios
If the iOS project manages CocoaPods directly:
cd ios
pod install
For Android:
- Place
google-services.jsonatandroid/app/google-services.json. - Apply the Google Services Gradle plugin if the application does not already use it.
- Confirm the Firebase Android package matches the Gradle
applicationIdexactly, including case. - Test on a physical device or emulator with Google Play services.
For iOS:
- Enable Push Notifications for the App ID in Apple Developer.
- Add Push Notifications under the Xcode target’s Signing & Capabilities.
- Confirm the Bundle ID matches Sendrealm and the APNs key configuration.
- Add Background Modes with Remote notifications only when background pushes are required.
- Add a Notification Service Extension when rich images are required.
- Forward the native notification callbacks.
Autolinking installs the SDK, but a bare iOS app must forward APNs token registration to Sendrealm. In AppDelegate.swift, configure the module and pass the device token:
import SendrealmReactNative
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
SendrealmModule.configure()
return true
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
SendrealmModule.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
}
Merge these calls into the application’s existing AppDelegate implementation. Do not replace React Native’s startup work or other notification delegates just to copy this shortened example.
If the app receives background remote notifications, forward that callback as described in the bare SDK iOS setup. If the app assigns its own UNUserNotificationCenter.current().delegate after configuring Sendrealm, also forward notification responses so click and action events remain trackable.
Step 3: initialize once from JavaScript
Both Expo and bare apps use the same SDK initialization shape:
import { useEffect } from 'react';
import Sendrealm from '@sendrealm/react-native';
export default function App() {
useEffect(() => {
void Sendrealm.initialize({
appId: 'YOUR_SENDREALM_APP_ID',
environment: 'development',
autoRequestPermission: false,
apnsEnvironment: 'sandbox'
});
}, []);
return null;
}
Initialize once near application startup. Avoid placing initialization in a component that repeatedly mounts during navigation.
Expo apps can read the public configuration from expo-constants:
import Constants from 'expo-constants';
const extra = Constants.expoConfig?.extra ?? {};
await Sendrealm.initialize({
appId: extra.sendrealmAppId,
environment: extra.sendrealmPushEnvironment ?? 'development',
autoRequestPermission: false,
apnsEnvironment: extra.sendrealmApnsEnvironment ?? 'sandbox'
});
There are two environment concepts:
environment: "development"is Sendrealm’s device environment. It separates development registrations from production targets.apnsEnvironment: "sandbox"is Apple’s token environment for development-signed iOS builds.
Use APNs production for TestFlight, App Store, and production-signed builds. A TestFlight token is not a sandbox token. Mixing these values can produce successful local registration followed by provider rejection during send.
Step 4: ask permission at the right moment
Keep autoRequestPermission: false for most products. Initialize first, show an in-app explanation when notifications become valuable, and request permission from a user action:
import Sendrealm from '@sendrealm/react-native';
async function enableNotifications() {
const alreadyAllowed = await Sendrealm.hasNotificationPermission();
if (alreadyAllowed) {
return true;
}
return Sendrealm.requestPermission();
}
iOS gives an application limited opportunities to present the system prompt. Android 13 and newer also require runtime notification permission. Asking immediately on first launch, before the customer understands the benefit, often produces a permanent or difficult-to-reverse refusal.
Connect the prompt to a real use case: order status, important account activity, new messages, reminders, or another feature the person just enabled.
Step 5: link the signed-in customer
The SDK can register a device before sign-in, but identity-based targeting requires a link to the authenticated user:
await Sendrealm.login('user-123', '[email protected]');
Use the stable product user ID as the external ID. Call logout() when the person signs out:
await Sendrealm.logout();
After login, the backend can target external_ids, contacts, emails, or audiences without maintaining a separate mapping from every user to every raw token.
Tags and custom events can enrich targeting and automation:
await Sendrealm.addTags({
plan: 'pro',
onboardingComplete: true
});
await Sendrealm.trackEvent('checkout_started', {
product_id: 'sku_123',
price: 29
});
Use mobile tags for state the app genuinely observes. Send authoritative billing, account, security, compliance, and verified profile fields from a trusted backend. Do not let a modified client declare that an account is paid or approved.
Step 6: handle notification opens and deep links
Listen for notification interaction while the app is running:
const subscription = Sendrealm.addNotificationClickListener(event => {
console.log(event.notificationId, event.launchUrl);
// Route event.launchUrl through the app's deep-link handler.
});
// During cleanup:
subscription.remove();
Read the notification that launched the app from a terminated state:
const initialNotification = await Sendrealm.getInitialNotification();
if (initialNotification?.launchUrl) {
// Route after navigation and authentication are ready.
}
Treat the launch URL as navigation input, not as authorization. The destination screen must still enforce normal authentication and access checks.
Step 7: create Android notification channels
Android channels control user-visible importance, sound, and vibration behavior. Create separate channels only for materially different categories:
await Sendrealm.createNotificationChannel({
id: 'orders',
name: 'Order updates',
importance: 'high',
soundName: 'order_update'
});
Android remembers channel behavior after creation, and users can change it in system settings. If the product needs materially different behavior later, use a new channel ID rather than assuming an update will override the stored channel.
Reserve high importance for notifications that genuinely deserve interruption. A technical ability to wake the device is not a product reason to do it.
Step 8: send from trusted backend code
Install the server-side SDK in a backend, worker, API route, or queue:
npm install @sendrealm/sdk
Create the client with a secret environment variable and send to the external ID linked by the mobile SDK:
import Sendrealm from '@sendrealm/sdk';
const client = new Sendrealm({
apiKey: process.env.SENDREALM_API_KEY,
maxRetries: 2
});
const result = await client.push.notifications.send({
app_id: 'YOUR_SENDREALM_APP_ID',
external_ids: ['user-123'],
environment: 'development',
notification: {
title: 'Your order shipped',
body: 'Tap to follow the delivery.',
launch_url: 'myapp://orders/123'
},
data: {
order_id: '123'
}
});
console.log({
total: result.total,
sent: result.sent,
failed: result.failed,
queued: result.queued
});
Use exactly one targeting style per request: raw tokens, web_subscriptions, Sendrealm device_ids, contact_ids, external_ids, emails, or audiences. Mixing target styles makes the intended scope ambiguous and is rejected.
For production, omit environment or set it to production. Keep development sends explicitly pointed at development registrations.
The Send Push Notification API documentation describes payload fields, platform overrides, buttons, localization, scheduling, and targeting.
Step 9: inspect diagnostics before guessing
When the first push does not appear, check the device state before rewriting backend targeting:
const diagnostics = await Sendrealm.getSupportDiagnostics();
console.log(JSON.stringify(diagnostics, null, 2));
Confirm:
- a Sendrealm device ID exists;
- a native token is present;
- permission has the expected status;
- the subscription is active unless the user opted out;
- the SDK version is visible;
- the environment matches the send;
- iOS uses the expected APNs environment;
- there is no unexpected SDK error.
Then check provider configuration. On Android, verify the Firebase project, package name, google-services.json, service account, Google Play services, and Android 13 permission. On iOS, verify physical device, Bundle ID, push entitlement, Team ID, Key ID, .p8 key, and APNs environment.
Provider acceptance, a queued count, or a sent count does not guarantee visible display. Device connectivity, Android power management, manufacturer restrictions, channel settings, iOS Focus, notification summary, user settings, provider throttling, and stale tokens all affect the final result. Push is best effort, so important state should also be available after the app opens.
A production checklist
Before enabling a real audience:
- The Sendrealm Push App belongs to the correct project.
- Development and production identities are intentionally separated.
- Firebase package name matches the Android
applicationIdor Expo package. -
google-services.jsonis in the app, not uploaded as the server credential. - The Firebase service account JSON is stored in Sendrealm and nowhere in mobile code.
- The iOS Bundle ID matches Apple Developer, Xcode or Expo, and Sendrealm.
- Push Notifications capability is enabled for the App ID and built target.
- APNs Key ID, Team ID,
.p8, Bundle ID, and environment match. - Expo builds use development builds, prebuild, or EAS—not Expo Go.
- Bare iOS forwards token registration and notification responses.
- Initialization occurs once.
- Permission follows a user-facing explanation.
- Login and logout follow the authenticated session.
- Deep links enforce normal authorization.
- Android channels match the importance of the message.
- The backend API key exists only in a trusted environment.
- A controlled Android device and physical iOS device received tests.
- Diagnostics and server send results are available to support.
- The team understands that provider acceptance is not guaranteed display.
A small SDK surface, a complete delivery path
The everyday React Native API stays compact: initialize, request permission, link the customer, add app-observed context, listen for opens, and inspect diagnostics. The complexity that cannot disappear—Firebase authorization, APNs credentials, native capabilities, build environments, and provider behavior—is made explicit rather than hidden.
That is what makes the setup manageable in both Expo and bare applications. Teams use one React Native SDK and one backend API while retaining the native controls Android and iOS require.
Start with the path that matches your project:
- Set up an Expo development or production build
- Set up a bare React Native application
- Configure Firebase and APNs credentials
- Send the first notification from trusted backend code
Once one real device works end to end, identity-based targeting, audiences, campaigns, automations, and cross-channel email-plus-push journeys can use the same Sendrealm project.