> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bbrands.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Channels & Targeting

> Per-channel OneSignal semantics, notification categories and how users are targeted by external_id and phone.

## Channels

| Channel         | Handler                          | OneSignal semantics                                                        |
| --------------- | -------------------------------- | -------------------------------------------------------------------------- |
| `push`          | `handleNotificationPush`         | `POST /notifications` with `contents`/`headings` + `target_channel: push`. |
| `in_app`        | `handleNotificationInApp`        | Data-only push (`content_available`, no `contents`).                       |
| `sms`           | `handleNotificationSms`          | `POST /notifications` with `target_channel: sms` + E.164 phone.            |
| `live_activity` | `handleNotificationLiveActivity` | Live Activity `start` / `update` based on `data.action`.                   |

## Categories and suppression

| Category        | Opt-out?           |
| --------------- | ------------------ |
| `marketing`     | Yes (suppressible) |
| `transactional` | No                 |
| `payment`       | No                 |
| `system`        | No                 |

`NON_SUPPRESSIBLE_CATEGORIES = ["payment", "system", "transactional"]`. Users
can never mute these categories; gating only applies to suppressible ones
(currently `marketing`).

## Targeting

<CardGroup cols={2}>
  <Card title="Push / in-app / live activity" icon="id-badge">
    Targeted by `include_aliases.external_id = [auth.users.id]`. The mobile SDK
    sets it on login.
  </Card>

  <Card title="SMS" icon="phone">
    Targeted by `include_phone_numbers` with the E.164 phone resolved from
    `user_metadata.phone` / `user_phone`.
  </Card>
</CardGroup>

<Warning>
  `horizon-api` holds no device tokens. The mobile SDK must call
  `OneSignal.login(auth.users.id)` so OneSignal can resolve the `external_id`
  the backend targets.
</Warning>

## Result classification

| Situation                                    | Result                                  |
| -------------------------------------------- | --------------------------------------- |
| OneSignal 2xx with `id`                      | Processed (`context.oneSignalId`).      |
| 200 with `errors` like "no subscribed users" | Logged skip (no-op delivery).           |
| No / invalid phone (SMS)                     | `NonRetryableNotificationError` → skip. |
| 5xx / network error                          | Error → retry with backoff → DLQ.       |
