---
sidebar_position: 8
title: SMS Conversations
description: Manage two-way SMS persistent conversation threads, chat history, and send outbound messages via the Firetell Agent API.
---

# SMS Conversations

The **SMS Conversations API** provides call center agents with a unified two-way messaging inbox. Conversations use a **Persistent Thread** model — exactly one thread exists per client phone number and system DID hotline in the workspace, ensuring complete message history across agent shifts and customer touchpoints.

:::info
**Data Scoping & Team Assignment**

Access to conversation threads is strictly scoped based on the agent's role and team assignments:

- **Leader / Supervisor / Admin**: Full access to view and manage all conversations across the workspace.
- **Member (Agent)**: Can only access conversations where the system DID is assigned to one of their teams (`shared_teams_id`), or threads assigned directly to them or their team. Regular members can only send new SMS from numbers assigned to their teams.
  :::

---

## Endpoints

| Method  | Endpoint                                  | Description                                    |
| ------- | ----------------------------------------- | ---------------------------------------------- |
| `GET`   | `/call-center/conversations`              | List conversation threads (SMS Inbox)          |
| `POST`  | `/call-center/conversations`              | Start a new conversation thread (New Message)  |
| `GET`   | `/call-center/conversations/:id`          | Get details of a single conversation thread    |
| `GET`   | `/call-center/conversations/:id/messages` | List message history for a conversation thread |
| `POST`  | `/call-center/conversations/:id/messages` | Send an outbound SMS reply in a thread         |
| `PATCH` | `/call-center/conversations/:id/read`     | Mark conversation messages as read             |
| `PATCH` | `/call-center/conversations/:id`          | Update conversation status or agent assignment |

## Authentication

All endpoints require a `Bearer` JWT with `agent-api` audience.

```http
Authorization: Bearer YOUR_AGENT_JWT
```

---

## The Conversation Object

| Field               | Type             | Description                                                                        |
| ------------------- | ---------------- | ---------------------------------------------------------------------------------- |
| `id`                | `string`         | Unique conversation identifier prefixed with `conv_` (e.g. `conv_a1b2c3d4e5f6`)    |
| `workspace_id`      | `string`         | Workspace identifier                                                               |
| `phone_number_id`   | `string`         | ID of the workspace DID number used for the thread (`pn_...`)                      |
| `system_number`     | `string`         | E.164 phone number of the workspace DID (e.g. `+13074295456`)                      |
| `client_number`     | `string`         | E.164 phone number of the client (e.g. `+18647123123`)                             |
| `client_name`       | `string \| null` | Contact display name if matched in address books or manually set                   |
| `contact_id`        | `string \| null` | Associated contact ID if resolved in contacts (`con_...`)                          |
| `assigned_agent_id` | `string \| null` | Agent ID currently assigned to handle this thread                                  |
| `assigned_team_id`  | `string \| null` | Team ID assigned to handle this thread                                             |
| `status`            | `string`         | Thread lifecycle status: [`open` \| `closed`](#conversation-status)               |
| `unread_count`      | `number`         | Number of unread inbound messages from client                                      |
| `last_message`      | `object \| null` | Snippet object of the latest message in the thread (see below)                     |
| `last_message_at`   | `string \| null` | ISO 8601 timestamp of the latest message                                           |
| `last_read_at`      | `string \| null` | ISO 8601 timestamp when an agent last marked the thread as read                    |
| `created_at`        | `string`         | ISO 8601 creation timestamp                                                        |
| `updated_at`        | `string`         | ISO 8601 last update timestamp                                                     |

### The `last_message` Snippet Object

Embedded inside `conversation.last_message`:

| Field         | Type             | Description                                                                                     |
| ------------- | ---------------- | ----------------------------------------------------------------------------------------------- |
| `id`          | `string`         | Message identifier prefixed with `msg_`                                                         |
| `body`        | `string`         | Content text of the latest message                                                              |
| `direction`   | `string`         | Message transmission direction: [`inbound` \| `outbound`](#direction)                           |
| `sender_type` | `string`         | Originator classification: [`client` \| `agent` \| `voice_agent` \| `system`](#sender_type)    |
| `sender_id`   | `string \| null` | Agent ID who sent the message (or `null` if sent by client, AI agent, or automated system)     |
| `created_at`  | `string`         | ISO 8601 creation timestamp                                                                     |

---

## The Message Object

Represents an individual SMS or MMS message within a conversation thread.

| Field             | Type             | Description                                                                                  |
| ----------------- | ---------------- | -------------------------------------------------------------------------------------------- |
| `id`              | `string`         | Unique message identifier prefixed with `msg_` (e.g. `msg_682d82eb0ba1ffeace018f`)          |
| `conversation_id` | `string`         | Associated conversation thread ID (`conv_...`)                                               |
| `workspace_id`    | `string`         | Workspace identifier                                                                         |
| `phone_number_id` | `string`         | ID of the workspace DID phone number used (`pn_...`)                                         |
| `from`            | `string`         | Sender phone number in E.164 format (e.g. `+13074295456`)                                    |
| `to`              | `string`         | Recipient phone number in E.164 format (e.g. `+18647123123`)                                 |
| `client_number`   | `string`         | Client phone number in E.164 format                                                          |
| `body`            | `string`         | Text content of the SMS message                                                              |
| `direction`       | `string`         | Transmission direction: [`inbound` \| `outbound`](#direction)                                 |
| `status`          | `string`         | Delivery / receipt status: [`queued` \| `sending` \| `sent` \| `delivered` \| `received` \| `failed` \| `undelivered`](#message-status) |
| `sender_type`     | `string`         | Originator entity: [`client` \| `agent` \| `voice_agent` \| `system`](#sender_type)          |
| `sender_id`       | `string \| null` | ID of the agent who sent the message (`null` for client or system messages)                  |
| `media_urls`      | `string[]`       | Optional array of public HTTPS URLs for MMS attachments                                      |
| `cost`            | `number \| null` | Cost incurred in workspace credits / USD for sending the SMS                                 |
| `error_code`      | `string \| null` | Telecom carrier or internal error code if transmission failed                                |
| `error_message`   | `string \| null` | Explanatory error message if transmission failed                                             |
| `created_at`      | `string`         | ISO 8601 creation timestamp                                                                  |
| `updated_at`      | `string`         | ISO 8601 last update timestamp                                                               |

---

## Enumerations & Value References

Comprehensive reference for enumerated fields used across conversations, messages, real-time events, and webhooks.

### `direction`

Specifies the communication direction relative to the workspace Call Center.

| Enum Value | Flow | Description |
| ---------- | ---- | ----------- |
| `inbound`  | Customer ➔ Workspace DID | **Incoming message**: Sent by an external customer/client to a workspace DID hotline. Increments `unread_count` on the thread and triggers incoming alert notifications. |
| `outbound` | Workspace DID ➔ Customer | **Outgoing message**: Sent from a workspace DID to a customer's phone number by an agent, AI voice agent, or automated workflow. |

---

### `status`

The `status` attribute is used in two contexts: **Conversation Status** (thread resolution lifecycle) and **Message Status** (carrier delivery state).

#### Conversation Status

Applies to `conversation.status` and query filters on `/call-center/conversations`.

| Enum Value | State | Description |
| ---------- | ----- | ----------- |
| `open`     | Active | The conversation thread is active, awaiting agent response, or undergoing live interaction. Newly created conversations or threads receiving a new customer inbound SMS are automatically set to `open`. |
| `closed`   | Resolved / Archived | The conversation thread has been marked as resolved or closed by an agent or supervisor. If the customer sends another inbound SMS to the same DID in the future, the thread will automatically reopen (`open`) and bump to the top of the inbox. |

#### Message Status

Applies to `message.status` and real-time carrier status delivery updates.

| Enum Value    | Direction | Category | Description |
| ------------- | --------- | -------- | ----------- |
| `queued`      | Outbound  | Pending  | Message has been accepted by the API and enqueued for carrier dispatch. |
| `sending`     | Outbound  | In-flight| Message is actively being transmitted to the telecom carrier network. |
| `sent`        | Outbound  | Dispatched | Telecom carrier accepted the message and dispatched it into the cellular network. |
| `delivered`   | Outbound  | Success  | Carrier received a confirmed Handset Delivery Receipt (DLR) indicating the SMS was successfully delivered to the customer's phone. |
| `received`    | Inbound   | Success  | Inbound SMS was successfully received from the carrier and appended to the conversation thread. |
| `failed`      | Outbound  | Terminal Failure | Message delivery permanently failed before or during carrier dispatch (e.g. invalid destination number, blocked prefix, or unroutable route). Check `error_code` and `error_message`. |
| `undelivered` | Outbound  | Terminal Failure | Carrier dispatched the SMS but the destination mobile network reported delivery failure (e.g. handset powered off, out of coverage, or unreachable). |

```mermaid
stateDiagram-v2
    direction LR
    [*] --> queued: Send Message
    queued --> sending: Carrier Dispatch
    sending --> sent: Carrier Accepted
    sent --> delivered: Carrier DLR Success
    sent --> undelivered: Carrier DLR Failure
    sending --> failed: Carrier Rejected
    queued --> failed: Invalid Route/Number

    [*] --> received: Inbound Message
```

---

### `sender_type`

Identifies the originator or authoring entity that created the message.

| Enum Value    | Typical Direction | Description |
| ------------- | ----------------- | ----------- |
| `client`      | `inbound`         | **Customer / Client**: The external mobile phone subscriber sending an SMS to the workspace DID number. |
| `agent`       | `outbound`        | **Call Center Agent**: A human agent sending a reply from the Firetell Console inbox or Client SDK. `sender_id` will contain the agent's user ID. |
| `voice_agent` | `outbound`        | **AI Voice Agent**: An automated conversational Voice AI agent sending an SMS during or after a phone call (e.g. sending a scheduling link, OTP verification code, address, or summary). |
| `system`      | `outbound`        | **System / Workflow Automation**: Automatically generated by backend platform workflows, API triggers, automated reminders, or webhooks. |

---

## List Conversations (Inbox)

```http
GET /call-center/conversations
```

Retrieve a paginated list of conversation threads accessible to the authenticated agent, ordered by most recent message activity.

### Query Parameters

| Parameter           | Type      | Default | Description                                                                                 |
| ------------------- | --------- | ------- | ------------------------------------------------------------------------------------------- |
| `page`              | `number`  | `1`     | Page number                                                                                 |
| `limit`             | `number`  | `20`    | Number of items per page (max: 100)                                                         |
| `status`            | `string`  | —       | Filter by status: `open` or `closed`                                                        |
| `phone_number_id`   | `string`  | —       | Filter by specific hotline phone number ID (`pn_...`)                                       |
| `assigned_agent_id` | `string`  | —       | Filter by assigned agent: `'me'`, `'unassigned'`, or a specific agent ID                    |
| `assigned_to`       | `string`  | —       | Alias for `assigned_agent_id`                                                               |
| `assigned_team_id`  | `string`  | —       | Filter by assigned team ID                                                                  |
| `unread_only`       | `boolean` | `false` | When set to `true`, only returns conversation threads that contain unread incoming messages |
| `search`            | `string`  | —       | Search across `client_number`, `client_name`, or `system_number`                            |

### Request

```bash
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations?status=open&page=1&limit=20" \
  -H "Authorization: Bearer YOUR_AGENT_JWT"
```

### Response `200 OK`

```json
{
  "data": [
    {
      "id": "conv_a1b2c3d4e5f6",
      "workspace_id": "yourcompany",
      "phone_number_id": "pn_99bf875464f1c096f46aff",
      "system_number": "+13074295456",
      "client_number": "+18647123123",
      "client_name": "John Doe",
      "contact_id": "con_650000000000000000000130",
      "assigned_agent_id": "agent_001",
      "assigned_team_id": "team_support_tier1",
      "status": "open",
      "unread_count": 2,
      "last_message": {
        "id": "msg_682d82eb0ba1ffeace018f",
        "body": "Can I reschedule my appointment?",
        "direction": "inbound",
        "sender_type": "client",
        "sender_id": null,
        "created_at": "2026-09-27T10:00:00.000Z"
      },
      "last_message_at": "2026-09-27T10:00:00.000Z",
      "last_read_at": "2026-09-27T09:30:00.000Z",
      "created_at": "2026-09-25T08:00:00.000Z",
      "updated_at": "2026-09-27T10:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1,
    "page": 1,
    "limit": 20,
    "total_pages": 1
  }
}
```

---

## Start New Conversation (New Message)

```http
POST /call-center/conversations
```

Initiate a new conversation thread or send a message to a client directly from the agent UI (e.g., clicking **"New Message"**). If an active thread already exists between the selected DID and the client, the message is appended to the existing thread. The thread is automatically assigned to the initiating agent if currently unassigned.

:::note
**DID Authorization Check**

For agents with the `member` role, `from` must be a DID phone number assigned to at least one of their teams. If unauthorized, a `403 Forbidden` error is returned.
:::

### Request Body

| Field           | Type       | Required | Description                                                                 |
| --------------- | ---------- | -------- | --------------------------------------------------------------------------- |
| `from`          | `string`   | **Yes**  | Workspace DID phone number to send from (E.164 format, e.g. `+13074295456`) |
| `client_number` | `string`   | **Yes**  | Client recipient phone number (E.164 format, e.g. `+18647123123`)           |
| `body`          | `string`   | **Yes**  | Text body of the SMS message (up to 1,600 characters)                       |
| `media_urls`    | `string[]` | No       | Optional array of media URLs for MMS messages                               |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/conversations" \
  -H "Authorization: Bearer YOUR_AGENT_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+13074295456",
    "client_number": "+18647123123",
    "body": "Hello John, thank you for reaching out to Firetell support."
  }'
```

### Response `201 Created`

```json
{
  "conversation": {
    "id": "conv_a1b2c3d4e5f6",
    "workspace_id": "yourcompany",
    "phone_number_id": "pn_99bf875464f1c096f46aff",
    "system_number": "+13074295456",
    "client_number": "+18647123123",
    "client_name": "John Doe",
    "contact_id": "con_650000000000000000000130",
    "assigned_agent_id": "agent_001",
    "assigned_team_id": null,
    "status": "open",
    "unread_count": 0,
    "last_message": {
      "id": "msg_682d82eb0ba1ffeace018f",
      "body": "Hello John, thank you for reaching out to Firetell support.",
      "direction": "outbound",
      "sender_type": "agent",
      "sender_id": "agent_001",
      "created_at": "2026-09-27T10:05:00.000Z"
    },
    "last_message_at": "2026-09-27T10:05:00.000Z",
    "last_read_at": "2026-09-27T10:05:00.000Z",
    "created_at": "2026-09-27T10:05:00.000Z",
    "updated_at": "2026-09-27T10:05:00.000Z"
  },
  "message": {
    "id": "msg_682d82eb0ba1ffeace018f",
    "conversation_id": "conv_a1b2c3d4e5f6",
    "workspace_id": "yourcompany",
    "phone_number_id": "pn_99bf875464f1c096f46aff",
    "from": "+13074295456",
    "to": "+18647123123",
    "client_number": "+18647123123",
    "body": "Hello John, thank you for reaching out to Firetell support.",
    "direction": "outbound",
    "status": "queued",
    "sender_type": "agent",
    "sender_id": "agent_001",
    "media_urls": [],
    "created_at": "2026-09-27T10:05:00.000Z",
    "updated_at": "2026-09-27T10:05:00.000Z"
  }
}
```

---

## Get Conversation Details

```http
GET /call-center/conversations/:id
```

Retrieve full details of an accessible conversation thread by its ID.

### Path Parameters

| Parameter | Type     | Description                           |
| --------- | -------- | ------------------------------------- |
| `id`      | `string` | Conversation ID prefixed with `conv_` |

### Request

```bash
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6" \
  -H "Authorization: Bearer YOUR_AGENT_JWT"
```

### Response `200 OK`

```json
{
  "id": "conv_a1b2c3d4e5f6",
  "workspace_id": "yourcompany",
  "phone_number_id": "pn_99bf875464f1c096f46aff",
  "system_number": "+13074295456",
  "client_number": "+18647123123",
  "client_name": "John Doe",
  "contact_id": "con_650000000000000000000130",
  "assigned_agent_id": "agent_001",
  "assigned_team_id": "team_support_tier1",
  "status": "open",
  "unread_count": 2,
  "last_message": {
    "id": "msg_682d82eb0ba1ffeace018f",
    "body": "Can I reschedule my appointment?",
    "direction": "inbound",
    "sender_type": "client",
    "sender_id": null,
    "created_at": "2026-09-27T10:00:00.000Z"
  },
  "last_message_at": "2026-09-27T10:00:00.000Z",
  "last_read_at": "2026-09-27T09:45:00.000Z",
  "created_at": "2026-09-25T08:00:00.000Z",
  "updated_at": "2026-09-27T10:00:00.000Z"
}
```

---

## List Messages in Conversation

```http
GET /call-center/conversations/:id/messages
```

Retrieve paginated messages for a conversation thread. Results are sorted in **chronological order** (oldest first to newest last) to facilitate rendering chat feeds in agent interfaces.

### Query Parameters

| Parameter | Type     | Default | Description                                                |
| --------- | -------- | ------- | ---------------------------------------------------------- |
| `page`    | `number` | `1`     | Page number                                                |
| `limit`   | `number` | `50`    | Number of messages to return per page (max: 100)           |
| `before`  | `string` | —       | ISO 8601 timestamp cursor to paginate backwards in history |
| `after`   | `string` | —       | ISO 8601 timestamp cursor to paginate forwards in history  |

### Request

```bash
curl -X GET "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/messages?page=1&limit=50" \
  -H "Authorization: Bearer YOUR_AGENT_JWT"
```

### Response `200 OK`

```json
{
  "data": [
    {
      "id": "msg_682d82eb0ba1ffeace018e",
      "conversation_id": "conv_a1b2c3d4e5f6",
      "workspace_id": "yourcompany",
      "phone_number_id": "pn_99bf875464f1c096f46aff",
      "from": "+18647123123",
      "to": "+13074295456",
      "client_number": "+18647123123",
      "body": "Hi, I need assistance with my service.",
      "direction": "inbound",
      "status": "received",
      "sender_type": "client",
      "sender_id": null,
      "media_urls": [],
      "created_at": "2026-09-27T09:55:00.000Z",
      "updated_at": "2026-09-27T09:55:01.000Z"
    },
    {
      "id": "msg_682d82eb0ba1ffeace018f",
      "conversation_id": "conv_a1b2c3d4e5f6",
      "workspace_id": "yourcompany",
      "phone_number_id": "pn_99bf875464f1c096f46aff",
      "from": "+13074295456",
      "to": "+18647123123",
      "client_number": "+18647123123",
      "body": "Can I reschedule my appointment?",
      "direction": "inbound",
      "status": "received",
      "sender_type": "client",
      "sender_id": null,
      "media_urls": [],
      "created_at": "2026-09-27T10:00:00.000Z",
      "updated_at": "2026-09-27T10:00:01.000Z"
    }
  ],
  "meta": {
    "total": 2,
    "page": 1,
    "limit": 50,
    "total_pages": 1
  }
}
```

---

## Send Message (Reply)

```http
POST /call-center/conversations/:id/messages
```

Send an outbound SMS/MMS reply to the client in an existing conversation thread. The message automatically dispatches from the thread's configured `system_number` to the client's `client_number`.

### Request Body

| Field        | Type       | Required | Description                                             |
| ------------ | ---------- | -------- | ------------------------------------------------------- |
| `body`       | `string`   | **Yes**  | Text body of the SMS reply (up to 1,600 characters)     |
| `media_urls` | `string[]` | No       | Optional array of public HTTPS URLs for MMS attachments |

### Request

```bash
curl -X POST "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/messages" \
  -H "Authorization: Bearer YOUR_AGENT_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Sure thing, John! We have slots available tomorrow at 2:00 PM or 4:00 PM."
  }'
```

### Response `201 Created`

```json
{
  "id": "msg_682d82eb0ba1ffeace0190",
  "conversation_id": "conv_a1b2c3d4e5f6",
  "workspace_id": "yourcompany",
  "phone_number_id": "pn_99bf875464f1c096f46aff",
  "from": "+13074295456",
  "to": "+18647123123",
  "client_number": "+18647123123",
  "body": "Sure thing, John! We have slots available tomorrow at 2:00 PM or 4:00 PM.",
  "direction": "outbound",
  "status": "queued",
  "sender_type": "agent",
  "sender_id": "agent_001",
  "media_urls": [],
  "created_at": "2026-09-27T10:05:00.000Z",
  "updated_at": "2026-09-27T10:05:00.000Z"
}
```

---

## Mark Conversation as Read

```http
PATCH /call-center/conversations/:id/read
```

Mark all unread incoming messages in a conversation thread as read. This resets `unread_count` to `0` and updates `last_read_at` to the current timestamp.

### Request

```bash
curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6/read" \
  -H "Authorization: Bearer YOUR_AGENT_JWT"
```

### Response `200 OK`

```json
{
  "ok": true,
  "conversation_id": "conv_a1b2c3d4e5f6"
}
```

---

## Update Conversation

```http
PATCH /call-center/conversations/:id
```

Update conversation metadata, such as assigning the thread to a specific agent or team, or closing/re-opening the thread status.

### Request Body

| Field               | Type             | Description                                          |
| ------------------- | ---------------- | ---------------------------------------------------- |
| `status`            | `string`         | Set thread status: `open` or `closed`                |
| `assigned_agent_id` | `string \| null` | Assign thread to an agent ID (or `null` to unassign) |
| `assigned_team_id`  | `string \| null` | Assign thread to a team ID (or `null` to unassign)   |
| `client_name`       | `string`         | Custom display name for the customer                 |

### Request

```bash
curl -X PATCH "https://{workspace_id}.firetell.app/api/v1/call-center/conversations/conv_a1b2c3d4e5f6" \
  -H "Authorization: Bearer YOUR_AGENT_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "closed",
    "assigned_agent_id": "agent_002",
    "assigned_team_id": "team_support_tier2"
  }'
```

### Response `200 OK`

```json
{
  "id": "conv_a1b2c3d4e5f6",
  "workspace_id": "yourcompany",
  "phone_number_id": "pn_99bf875464f1c096f46aff",
  "system_number": "+13074295456",
  "client_number": "+18647123123",
  "client_name": "John Doe",
  "contact_id": "con_650000000000000000000130",
  "assigned_agent_id": "agent_002",
  "assigned_team_id": "team_support_tier2",
  "status": "closed",
  "unread_count": 0,
  "last_message": {
    "id": "msg_682d82eb0ba1ffeace018f",
    "body": "Can I reschedule my appointment?",
    "direction": "inbound",
    "sender_type": "client",
    "sender_id": null,
    "created_at": "2026-09-27T10:00:00.000Z"
  },
  "last_message_at": "2026-09-27T10:00:00.000Z",
  "last_read_at": "2026-09-27T10:05:00.000Z",
  "created_at": "2026-09-25T08:00:00.000Z",
  "updated_at": "2026-09-27T10:10:00.000Z"
}
```

---

## Realtime Notifications & Synchronization

Firetell keeps agent devices and browser call center consoles synchronized across all inbound and outbound messages in real time.

### 1. Mobile Push Notifications (Inbound SMS)

When a customer sends an SMS/MMS to a workspace number, Firetell delivers a high-priority alert notification to the assigned agents' mobile apps (iOS & Android) via **APNs (Alert)** and **FCM**.

#### Agent Targeting & Routing

Push notifications are routed automatically based on the conversation assignment:

1. **Assigned Agent**: If the conversation has an `assigned_agent_id`, only that agent's devices receive the push.
2. **Assigned Team**: If the conversation has an `assigned_team_id`, all active agents belonging to that team receive the push.
3. **DID Shared Teams (Fallback)**: If the conversation has no assigned agent or team, all active agents in the teams mapped to the phone number (`shared_teams_id`) receive the push notification.

:::info
**Outbound SMS Policy**
Outbound messages sent by agents do **not** trigger mobile push notifications. This prevents notification spam and alert fatigue across teammates while ensuring agents only get mobile alerts when a customer needs attention.
:::

#### Device Token Registration

Mobile clients must register their regular notification token with the Agent API using [`POST /me/devices/notification-push-token`](/docs/rest-api/agent-api/account#register-notification-push-token):

- **iOS**: Register the standard APNs alert token (`UNUserNotificationCenter`).
- **Android**: Register the FCM registration token (`FirebaseMessaging`).

#### Push Payload Specification

##### iOS (APNs Alert Payload)

Delivered with `apns-push-type: alert` and `apns-priority: 10`:

```json
{
  "aps": {
    "alert": {
      "title": "Alice Smith (+18647123123)",
      "body": "Hello, I need assistance with my reservation."
    },
    "sound": "default",
    "badge": 3,
    "thread-id": "conv_a1b2c3d4e5f6"
  },
  "event": "message.received",
  "workspace_id": "ws_123456789",
  "message_id": "msg_be52701819fa34a72ae414",
  "conversation_id": "conv_a1b2c3d4e5f6",
  "from_number": "+18647123123",
  "to_number": "+13074295456",
  "client_number": "+18647123123",
  "client_name": "Alice Smith",
  "body": "Hello, I need assistance with my reservation.",
  "unread_count": 3,
  "created_at": "2026-09-27T10:00:00.000Z"
}
```

##### Android/iOS (FCM Notification + Data Payload)

Sent with priority `high`:

```json
{
  "notification": {
    "title": "Alice Smith",
    "body": "Hello, I need assistance with my reservation."
  },
  "android": {
    "notification": {
      "channel_id": "firetell_messages",
      "tag": "conv_a1b2c3d4e5f6"
    }
  },
  "apns": {
    "payload": {
      "aps": {
        "thread-id": "conv_a1b2c3d4e5f6"
      }
    }
  },
  "data": {
    "event": "message.received",
    "workspace_id": "ws_123456789",
    "message_id": "msg_be52701819fa34a72ae414",
    "conversation_id": "conv_a1b2c3d4e5f6",
    "from_number": "+18647123123",
    "to_number": "+13074295456",
    "client_number": "+18647123123",
    "client_name": "Alice Smith",
    "body": "Hello, I need assistance with my reservation.",
    "unread_count": "3",
    "created_at": "2026-09-27T10:00:00.000Z"
  }
}
```

:::tip
**Conversation Grouping (Threading)**

- **iOS**: Uses `aps.thread-id` set to `conversation_id`. iOS natively groups incoming notifications into an expandable conversation stack in the Notification Center.
- **Android**: Uses `android.notification.tag` set to `conversation_id`. If building custom notifications via `FirebaseMessagingService`, pass `data.conversation_id` into `NotificationCompat.Builder.setGroup(conversationId)` for native Android notification grouping.
  :::

---

### 2. Mobile Silent / Data-Only Push Notifications (`conversation.updated`)

When a conversation is modified — such as an agent marking messages as read, reassigning to another agent/team, or updating thread status (`open` / `closed`) — Firetell dispatches a **silent, data-only push notification** (`conversation.updated`) to target mobile devices.

#### Purpose & Architecture

Unlike incoming SMS alerts, silent pushes contain **no alert banner, no sound, and no popup**. They wake the mobile operating system in the background to execute code:

- **Local Storage Synchronization**: Update local databases (e.g. SQLite, WatermelonDB, Core Data, Realm) with the new `unread_count`, `status`, and `assigned_agent_id`.
- **Badge Count Synchronization**: Update or clear the application icon badge without requiring user interaction.
- **Notification Dismissal**: Dismiss existing notification center banners for this conversation when the thread has been read or resolved on another device or web portal.

#### Agent Targeting & Routing

Delivered automatically to:

1. **Assigned Agent**: If the conversation is assigned, the assigned agent's devices receive the silent push.
2. **Assigned Team**: If assigned to a team, all active agents in that team receive the push.
3. **DID Shared Teams (Fallback)**: If unassigned, active agents in the teams assigned to the phone number receive the push.

#### Push Payload Specification

##### iOS (APNs Background Silent Push)

Delivered with `apns-push-type: background` and `apns-priority: 5`:

```json
{
  "aps": {
    "content-available": 1
  },
  "event": "conversation.updated",
  "workspace_id": "ws_123456789",
  "id": "conv_a1b2c3d4e5f6",
  "conversation_id": "conv_a1b2c3d4e5f6",
  "phone_number_id": "pn_99bf875464f1c096f46aff",
  "system_number": "+13074295456",
  "client_number": "+18647123123",
  "client_name": "Alice Smith",
  "assigned_agent_id": "agent_001",
  "assigned_team_id": null,
  "status": "open",
  "unread_count": 0,
  "last_message": {
    "id": "msg_05f18045cba1452204774f",
    "body": "Hello Alice, I have updated your reservation for 7:00 PM.",
    "direction": "outbound",
    "sender_type": "agent",
    "sender_id": "agent_001",
    "created_at": "2026-09-28T03:39:00.000Z"
  },
  "last_message_at": "2026-09-28T03:39:00.000Z",
  "last_read_at": "2026-09-28T03:40:00.000Z",
  "created_at": "2026-09-27T10:00:00.000Z",
  "updated_at": "2026-09-28T03:40:00.000Z",
  "timestamp": "2026-09-28T03:40:00.000Z"
}
```

##### Android / iOS (FCM Data-Only Payload)

Sent with `priority: high` and **no `notification` block**:

```json
{
  "android": {
    "priority": "high",
    "ttl": "60s"
  },
  "apns": {
    "headers": {
      "apns-priority": "5",
      "apns-push-type": "background"
    },
    "payload": {
      "aps": {
        "content-available": 1
      }
    }
  },
  "data": {
    "event": "conversation.updated",
    "workspace_id": "ws_123456789",
    "id": "conv_a1b2c3d4e5f6",
    "conversation_id": "conv_a1b2c3d4e5f6",
    "phone_number_id": "pn_99bf875464f1c096f46aff",
    "system_number": "+13074295456",
    "client_number": "+18647123123",
    "client_name": "Alice Smith",
    "assigned_agent_id": "agent_001",
    "assigned_team_id": "",
    "status": "open",
    "unread_count": "0",
    "last_message": "{\"id\":\"msg_05f18045cba1452204774f\",\"body\":\"Hello Alice, I have updated your reservation for 7:00 PM.\",\"direction\":\"outbound\",\"sender_type\":\"agent\",\"sender_id\":\"agent_001\",\"created_at\":\"2026-09-28T03:39:00.000Z\"}",
    "last_message_at": "2026-09-28T03:39:00.000Z",
    "last_read_at": "2026-09-28T03:40:00.000Z",
    "created_at": "2026-09-27T10:00:00.000Z",
    "updated_at": "2026-09-28T03:40:00.000Z",
    "timestamp": "2026-09-28T03:40:00.000Z"
  }
}
```

:::tip
**Handling Silent Pushes in Mobile Code**

- **iOS APNs (`AppDelegate` / `UNUserNotificationCenter`)**: Handle via `application(_: didReceiveRemoteNotification:fetchCompletionHandler:)`. Inspect `payload["event"] == "conversation.updated"`, sync local database, update `UIApplication.shared.applicationIconBadgeNumber`, and call `completionHandler(.newData)`.
- **Android/iOS FCM (`FirebaseMessagingService`)**: In `onMessageReceived(remoteMessage: RemoteMessage)`, check `remoteMessage.data["event"] == "conversation.updated"`. Because there is no `notification` block, the Android/iOS OS will not show any system notification. Update your local database (Room / SQLite / WatermelonDB) in the background.
  :::

---

### 3. Browser Realtime Synchronization (SSE)

Browser-based Call Center Portals subscribe to the [Realtime Events (SSE)](/docs/rest-api/agent-api/realtime-events) stream:

```http
GET /stream?token=YOUR_AGENT_JWT
```

Message events are published to the agent's scoped unicast channels

#### Supported Message Events

| Event                  | Direction | Description                                                                                                                                  |
| ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `message.received`     | Inbound   | Emitted when a new message is received from a customer. Updates the chat thread and increases the unread count.                              |
| `message.sent`         | Outbound  | Emitted when an agent sends an outbound message. Instantly updates the conversation for all teammates in the same team to prevent collision. |
| `message.updated`      | Status    | Emitted when delivery status updates from the carrier (e.g., transitions from `queued` to `sent`, `delivered`, or `failed`).                 |
| `conversation.updated` | State     | Emitted when a conversation thread is updated (assigned/reassigned agent, status open/closed, or unread count reset upon reading).           |

#### Example JavaScript Listener

```javascript
const eventSource = new EventSource(
  `https://{workspace_id}.firetell.app/stream?token=${encodeURIComponent(agentJwt)}`,
);

// 1. Inbound customer message
eventSource.addEventListener("message.received", (e) => {
  const { data } = JSON.parse(e.data);
  console.log("New inbound SMS:", data.body, "from:", data.from_number);
  // Prepend or append to conversation chat view, increment unread badge
  updateConversationInbox(data.conversation_id, data);
});

// 2. Outbound message from colleague (Collision Avoidance)
eventSource.addEventListener("message.sent", (e) => {
  const { data } = JSON.parse(e.data);
  console.log("Teammate sent SMS:", data.body, "by agent:", data.sender_id);
  // Reflect teammate's reply immediately without reloading
  appendOutboundMessageToThread(data.conversation_id, data);
});

// 3. Delivery status update (Carrier acknowledgment)
eventSource.addEventListener("message.updated", (e) => {
  const { data } = JSON.parse(e.data);
  console.log("Message status updated:", data.id, "to:", data.status);
  // Update tick icon to delivered / failed
  updateMessageStatusIndicator(data.id, data.status, data.error_message);
});

// 4. Conversation state updated (Assignment / Status change / Read receipt)
eventSource.addEventListener("conversation.updated", (e) => {
  const { data } = JSON.parse(e.data);
  console.log(
    "Conversation updated:",
    data.id,
    "assigned to:",
    data.assigned_agent_id,
    "status:",
    data.status,
  );
  // Update conversation inbox thread item in real time
  syncConversationState(data.id, data);
});
```
