diff --git a/v7/api-reference/push-notifications/send.mdx b/v7/api-reference/push-notifications/send.mdx
index e2ee017..16d6b05 100644
--- a/v7/api-reference/push-notifications/send.mdx
+++ b/v7/api-reference/push-notifications/send.mdx
@@ -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.
+### 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).
+
+
+ Sound file to play. iOS: APNs `sound`. Android: governed by the channel on 8+ — pair with `channelId`. Web: forwarded to the service worker.
+
+
+
+ iOS app-icon badge count (APNs `badge`). No Android equivalent. The caller supplies the number; Sublay tracks no unread state.
+
+
+
+ Android notification channel id (FCM `android.notification.channel_id`). The channel must be created client-side; on Android 8+ it owns sound/importance/vibration.
+
+
+
+ `"high"` or `"normal"`. iOS: APNs `apns-priority` (10/5). Android: FCM `android.priority`. `high` wakes the device for time-sensitive pushes.
+
+
+
+ iOS subtitle line under the title (APNs `alert.subtitle`).
+
+
+
+ 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.
+
+
+
+ Display-replace key so notifications collapse in the UI (FCM `android.notification.tag`, Web `tag`).
+
+
+
+ Transport-level collapse identifier (APNs `apns-collapse-id`, FCM `collapse_key`) — supersedes an undelivered push with the same id.
+
+
+
+ iOS notification grouping (APNs `thread-id`).
+
+
+
+ Time-to-live in seconds for offline devices (APNs `apns-expiration` computed from now, FCM `android.ttl`).
+
+
+
+ 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.
+
+
## 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.
diff --git a/v7/node-sdk/push-notifications.mdx b/v7/node-sdk/push-notifications.mdx
index b772722..f6be0f8 100644
--- a/v7/node-sdk/push-notifications.mdx
+++ b/v7/node-sdk/push-notifications.mdx
@@ -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",
});
```
@@ -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.
+#### 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.
+
+
+ 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.
+
+
+
+ iOS app-icon badge count (APNs `badge`). No Android equivalent. Your backend supplies the number — Sublay does not track per-user unread counts.
+
+
+
+ Android notification channel id (FCM). The channel must be created client-side; on Android 8+ it governs sound, importance, and vibration.
+
+
+
+ Delivery priority. `high` wakes the device for time-sensitive pushes; `normal` is power-considerate. Maps to APNs `apns-priority` and FCM `android.priority`.
+
+
+
+ iOS subtitle line shown under the title (APNs `alert.subtitle`).
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ Transport-level collapse identifier (APNs `apns-collapse-id`, FCM `collapse_key`) — a newer push supersedes an undelivered one with the same id.
+
+
+
+ iOS notification grouping (APNs `thread-id`).
+
+
+
+ Time-to-live in **seconds** for offline devices (APNs `apns-expiration`, FCM `android.ttl`). After this window the provider stops retrying.
+
+
+
+ iOS `mutable-content` flag, letting your Notification Service Extension modify the payload (e.g. attach `imageUrl`). Set implicitly when `imageUrl` is provided.
+
+
**Returns** — `Promise`
```typescript
diff --git a/v7/push-notifications.mdx b/v7/push-notifications.mdx
index 1e8bbd2..e43be25 100644
--- a/v7/push-notifications.mdx
+++ b/v7/push-notifications.mdx
@@ -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).
+## 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:
+
+
+
+ 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.
+
+
+ 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.
+
+
+ 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(...)`.
+
+
+
## Device Lifecycle
- **Re-registration:** registering the same physical device again (same token or endpoint) updates the existing record instead of duplicating it.