Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions v7/api-reference/push-notifications/send.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,54 @@ Requires the `push` bundle.
Optional key-value data payload forwarded to the app alongside the notification. Values must be strings or serializable primitives. Use this to pass deep-link targets, IDs, or any context your app needs when the user taps the notification.
</ParamField>

### Rich payload fields

All optional. Each is mapped to whichever platform(s) support it at dispatch time and silently ignored on platforms that have no equivalent. See the [Push Notifications guide](/push-notifications#rich-notification-payloads) for the client-side setup some of these require (Android channels, the iOS Notification Service Extension, your web service worker).

<ParamField body="sound" type="string">
Sound file to play. iOS: APNs `sound`. Android: governed by the channel on 8+ — pair with `channelId`. Web: forwarded to the service worker.
</ParamField>

<ParamField body="badge" type="number">
iOS app-icon badge count (APNs `badge`). No Android equivalent. The caller supplies the number; Sublay tracks no unread state.
</ParamField>

<ParamField body="channelId" type="string">
Android notification channel id (FCM `android.notification.channel_id`). The channel must be created client-side; on Android 8+ it owns sound/importance/vibration.
</ParamField>

<ParamField body="priority" type="string">
`"high"` or `"normal"`. iOS: APNs `apns-priority` (10/5). Android: FCM `android.priority`. `high` wakes the device for time-sensitive pushes.
</ParamField>

<ParamField body="subtitle" type="string">
iOS subtitle line under the title (APNs `alert.subtitle`).
</ParamField>

<ParamField body="imageUrl" type="string">
Rich/big-picture image URL. Android (FCM `image`) and Web work out of the box. iOS additionally requires a Notification Service Extension in the app — the URL is forwarded in the payload and `mutable-content` is set automatically.
</ParamField>

<ParamField body="tag" type="string">
Display-replace key so notifications collapse in the UI (FCM `android.notification.tag`, Web `tag`).
</ParamField>

<ParamField body="collapseId" type="string">
Transport-level collapse identifier (APNs `apns-collapse-id`, FCM `collapse_key`) — supersedes an undelivered push with the same id.
</ParamField>

<ParamField body="threadId" type="string">
iOS notification grouping (APNs `thread-id`).
</ParamField>

<ParamField body="ttl" type="number">
Time-to-live in seconds for offline devices (APNs `apns-expiration` computed from now, FCM `android.ttl`).
</ParamField>

<ParamField body="mutableContent" type="boolean">
iOS `mutable-content` flag, enabling the app's Notification Service Extension to modify the payload (e.g. attach `imageUrl`). Set implicitly when `imageUrl` is provided.
</ParamField>

## Response

Returns `200` with a `results` map keyed by user ID. Every requested `userId` is present in the response — users with no registered devices get an empty array rather than being omitted.
Expand Down
52 changes: 52 additions & 0 deletions v7/node-sdk/push-notifications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const result = await sublay.push.send({
title: "New message",
body: "You have a new message from Alice.",
data: { conversationId: "conv_xyz789" },
sound: "notification.wav",
channelId: "messages",
badge: 3,
tag: "conv_xyz789",
});
```

Expand All @@ -40,6 +44,54 @@ const result = await sublay.push.send({
Optional key-value payload forwarded to the app alongside the notification. Use for deep links, entity IDs, or any context your app needs when the user taps.
</ParamField>

#### Rich payload fields

All optional. Each maps to whichever platform(s) support it and is silently ignored elsewhere (e.g. `subtitle`/`threadId` are iOS-only, `channelId` is Android-only). See [Rich notification payloads](/push-notifications#rich-notification-payloads) for the platform setup each one needs.

<ParamField body="sound" type="string">
Sound file to play. **iOS:** APNs `sound`. **Android:** on 8+ the sound is owned by the notification channel — pair this with `channelId` and create that channel client-side with the sound set. **Web:** forwarded to your service worker.
</ParamField>

<ParamField body="badge" type="number">
iOS app-icon badge count (APNs `badge`). No Android equivalent. Your backend supplies the number — Sublay does not track per-user unread counts.
</ParamField>

<ParamField body="channelId" type="string">
Android notification channel id (FCM). The channel must be created client-side; on Android 8+ it governs sound, importance, and vibration.
</ParamField>

<ParamField body="priority" type='"high" | "normal"'>
Delivery priority. `high` wakes the device for time-sensitive pushes; `normal` is power-considerate. Maps to APNs `apns-priority` and FCM `android.priority`.
</ParamField>

<ParamField body="subtitle" type="string">
iOS subtitle line shown under the title (APNs `alert.subtitle`).
</ParamField>

<ParamField body="imageUrl" type="string">
Rich/big-picture image URL. Works out of the box on **Android** (FCM) and **Web**. On **iOS** it additionally requires a [Notification Service Extension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension) in your app; the URL is forwarded in the payload and `mutable-content` is set automatically.
</ParamField>

<ParamField body="tag" type="string">
Display-replace key so notifications collapse in the UI instead of stacking (FCM `android.notification.tag`, Web `tag`). Use a per-conversation value to keep one entry per thread.
</ParamField>

<ParamField body="collapseId" type="string">
Transport-level collapse identifier (APNs `apns-collapse-id`, FCM `collapse_key`) — a newer push supersedes an undelivered one with the same id.
</ParamField>

<ParamField body="threadId" type="string">
iOS notification grouping (APNs `thread-id`).
</ParamField>

<ParamField body="ttl" type="number">
Time-to-live in **seconds** for offline devices (APNs `apns-expiration`, FCM `android.ttl`). After this window the provider stops retrying.
</ParamField>

<ParamField body="mutableContent" type="boolean">
iOS `mutable-content` flag, letting your Notification Service Extension modify the payload (e.g. attach `imageUrl`). Set implicitly when `imageUrl` is provided.
</ParamField>

**Returns** — `Promise<SendPushResult>`

```typescript
Expand Down
60 changes: 60 additions & 0 deletions v7/push-notifications.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,66 @@ A single call fans the notification out to all platforms. Capped at **100 user I
A common pattern is to bridge Sublay's in-app notifications to push: subscribe to the `notification.created` webhook and call `push.send()` when it fires. See [Webhooks → Push Notification Bridge](/webhooks#push-notification-bridge).
</Tip>

## Rich Notification Payloads

Beyond `title`, `body`, and `data`, `push.send()` accepts optional fields for sound, badges, images, grouping, priority, and lifetime. Each maps to the native APNs / FCM / Web Push capability and is **silently ignored on platforms that don't support it** — so you can set `subtitle` (iOS-only) and `channelId` (Android-only) in the same call without branching.

```typescript
await sublay.push.send({
userIds: ["usr_abc123"],
title: "New message",
body: "Alice sent you a message",
data: { conversationId: "conv_xyz789" },
sound: "notification.wav", // custom sound
channelId: "messages", // Android channel (see below)
badge: 3, // iOS app-icon badge
tag: "conv_xyz789", // collapse a conversation into one notification
priority: "high",
});
```

| Field | Platforms | Notes |
|-------|-----------|-------|
| `sound` | iOS, Android, Web | Filename of a bundled sound. On Android 8+ the **channel** owns the sound — see below. |
| `badge` | iOS | App-icon badge count. Your backend supplies the number; Sublay tracks no unread state. |
| `channelId` | Android | Notification channel id. Created client-side. |
| `priority` | iOS, Android | `"high"` (default) wakes the device; `"normal"` is power-considerate. |
| `subtitle` | iOS | Line under the title. |
| `imageUrl` | iOS, Android, Web | Big-picture image. iOS needs a Notification Service Extension — see below. |
| `tag` | Android, Web | Display-replace key so notifications collapse instead of stacking. |
| `collapseId` | iOS, Android | Transport-level collapse — a newer push supersedes an undelivered one. |
| `threadId` | iOS | Notification grouping. |
| `ttl` | iOS, Android | Time-to-live in **seconds** for offline devices. |
| `mutableContent` | iOS | Enables your Notification Service Extension. Set automatically when `imageUrl` is present. |

### Client-side requirements

Three of these need setup in **your app** — Sublay forwards the field but cannot do this part for you:

<AccordionGroup>
<Accordion title="Android sound → notification channels">
On Android 8+ the **notification channel** owns the sound, importance, and vibration — the payload can't override it. Create the channel once in your app (with the bundled sound) and pass its id as `channelId`:

```ts
import * as Notifications from "expo-notifications";

await Notifications.setNotificationChannelAsync("messages", {
name: "Messages",
importance: Notifications.AndroidImportance.HIGH,
sound: "notification.wav", // bundled in the app
});
```

Then `push.send({ ..., channelId: "messages", sound: "notification.wav" })`. The `sound` field alone is only a pre-Android-8 fallback. Sublay does **not** create channels for you.
</Accordion>
<Accordion title="iOS images → Notification Service Extension">
iOS does not download remote images from the payload on its own. To show `imageUrl` on iOS, add a [Notification Service Extension](https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension) to your app that reads the URL from the notification payload, downloads it, and attaches it. Sublay sets `mutable-content` automatically whenever `imageUrl` is present so the extension is allowed to run. Android and Web render `imageUrl` with no extra work.
</Accordion>
<Accordion title="Web rendering → your service worker">
Sublay ships no service worker — your app's own SW renders web notifications. The server forwards the web-renderable fields (`sound`, `image`, `tag`) in the push JSON; your `push` event handler decides how to display them via `registration.showNotification(...)`.
</Accordion>
</AccordionGroup>

## Device Lifecycle

- **Re-registration:** registering the same physical device again (same token or endpoint) updates the existing record instead of duplicating it.
Expand Down