Rich Notifications
Send notifications with modern Expo message fields from the native-notify SDK — subtitle, badge, ttl, interruption level, iOS category, Android channel, collapse id, sound, and more.
Native Notify's send helpers pass the modern Expo message fields through to every send path — mass, single subscriber, group, and followers. The same fields are accepted directly by the REST endpoints, and the dashboard and MCP server send them too.
These helpers ship in the next native-notify release — install native-notify@latest to get them (see Upgrading to v5).
Sends are live:
Every helper sends through your Native Notify account the moment it is called — there is no test mode, and a send cannot be recalled. Unset rich fields keep the server's defaults.
Send helpers
| Helper | Sends to |
|---|---|
sendMassNotification(title, body, options) | Every registered device of the app. |
sendIndieNotification(subID, title, message, options) | One individual-push subscriber. |
sendIndieGroupNotification(subIDs, title, message, options) | A list of subscriber ids you already hold. |
sendNotificationToFollowers(masterSubID, title, message, options) | Every follower of one follow-master subID. |
All four take the same options object, resolve with the server's response, and throw when the server rejects the send. sendIndieGroupNotification also throws when subIDs is not a non-empty array — an empty audience would otherwise be a silent no-op.
Quick start
import { sendMassNotification, sendIndieNotification } from 'native-notify';
// Every registered device (mass):
await sendMassNotification('Service update', 'We are back online.', {
pushData: { url: '/status' },
subtitle: 'All systems normal',
channelId: 'alerts', // Android channel — create it first (below)
interruptionLevel: 'active', // iOS
sound: 'chime.wav',
});
// One subscriber (indie):
await sendIndieNotification(userId, 'Your order', 'It shipped!', {
pushData: { url: '/orders/' + orderId },
badge: 1,
categoryId: 'ORDER_UPDATE',
});
// A custom audience, or the followers of a follow-master:
await sendIndieGroupNotification(subIds, 'Group news', 'Hello all', { channelId: 'news' });
await sendNotificationToFollowers(masterSubId, 'New post', 'Read it', { mutableContent: true });
Options
Every helper accepts the same options object:
| Option | Type | What it does |
|---|---|---|
pushData | object | The data payload your app reads on tap (deep links, ids). |
bigPictureURL | string | Android big-picture image / iOS attachment source. |
subtitle | string (max 200 chars) | iOS subtitle shown under the title. |
badge | number | iOS app-icon badge count — 0 clears the badge. |
ttl | number | Seconds the push may be delivered for. |
interruptionLevel | 'passive' | 'active' | 'timeSensitive' | 'critical' | iOS interruption level — use timeSensitive / critical sparingly. |
categoryId | string | iOS notification category (your action buttons). |
channelId | string | Android channel id — create the channel first. |
collapseId | string | APNs collapse id — a newer push with the same id replaces the older one. |
contentAvailable | boolean | iOS background delivery (silent data update). |
mutableContent | boolean | iOS notification service extension may modify the payload (e.g. attach an image). |
sound | string | false | Custom sound file name — or false for a silent notification. |
appId / appToken | number | string | Optional overrides; normally set once with NativeNotify.init() / <NativeNotifyProvider>. |
Worth knowing:
- Only the options you pass are sent. Unset fields keep the server's defaults, and
sound: false,badge: 0andcontentAvailable: falseare real values, not omissions. - The server validates them up front. An invalid value (an unknown
interruptionLevel, a non-booleanmutableContent, …) fails the request with a400instead of being rejected per recipient by Expo. - Raw REST calls accept one more field:
expiration(a unix timestamp) as an alternative tottl. The SDK helpers exposettl. - Open analytics work with these sends. Mass and single-indie sends carry the hidden notification id that Sessions & Push Opens reports on — enable it with
analytics: { opens: true }.
Android notification channels: setAndroidNotificationChannel
A channelId in your send has to match a channel the app has created on the device. setAndroidNotificationChannel(channelId, options) is a thin wrapper over expo-notifications' setNotificationChannelAsync():
import * as Notifications from 'expo-notifications';
import { setAndroidNotificationChannel } from 'native-notify';
await setAndroidNotificationChannel('alerts', {
name: 'Alerts',
importance: Notifications.AndroidImportance.HIGH,
sound: 'chime.wav',
vibrationPattern: [0, 250, 250, 250],
});
// Then send to it:
await sendMassNotification('Heads up', 'The service is back', { channelId: 'alerts' });
Create the channel before sending a push with a matching channelId, so the notification routes to it. The channel's name, sound, importance, and vibration are user-visible in Android's per-app notification settings.
Options — only the keys you set are used:
| Option | Type | Notes |
|---|---|---|
name | string | Human-readable name shown in Android settings (defaults to the channel id). |
description | string | Long description shown in Android settings. |
importance | Notifications.AndroidImportance | MAX / HIGH / DEFAULT / LOW / MIN. |
sound | string | null | Custom sound file name (omit for the platform default). |
vibrationPattern | number[] | Vibration pattern in ms, e.g. [0, 250, 250, 250]. |
lightColor | string | Notification LED color, e.g. '#FF231F7C'. |
enableLights | boolean | Enable the notification LED. |
enableVibrate | boolean | Enable vibration for this channel. |
showBadge | boolean | Show a badge dot on the app icon for this channel's notifications. |
setAndroidNotificationChannel() resolves true on Android; it resolves false on iOS/web (Android channels don't exist there) or when the call fails — it never throws, so callers keep their default channel.