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

# Send Messages and List Sender IDs — Zexa Messages API

> Use POST /messages to send on any channel, GET /messages/{id} to check delivery status, and GET /sender-ids to list your registered Sender IDs.

The Messages API lets you send a single message to one recipient on any channel supported by Zexa — SMS, WhatsApp, Telegram, email, or Slack. Each successful request returns a unique message ID and an initial delivery status. Use the message ID to poll for status updates, or set up a [webhook](/api/webhooks) to receive delivery events pushed to your server in real time.

## Send a Message

```text theme={null}
POST https://api.zexa.ao/v1/messages
```

### Request Parameters

<ParamField body="channel" type="string" required>
  The messaging channel to use. Must be one of: `sms`, `whatsapp`, `telegram`, `email`, `slack`.
</ParamField>

<ParamField body="to" type="string" required>
  The recipient identifier. Use an E.164-formatted phone number (e.g. `+244912345678`) for `sms`, `whatsapp`, and `telegram`. Use an email address for `email`. Use a Slack channel ID or user ID for `slack`.
</ParamField>

<ParamField body="from" type="string" required>
  Your registered Sender ID or verified email address. Sender IDs are configured in your Zexa dashboard under **Settings → Senders**. Use `GET /sender-ids` to retrieve your registered Sender IDs programmatically.
</ParamField>

<ParamField body="body" type="string">
  The plain-text message content. Required unless `template` is provided.
</ParamField>

<ParamField body="subject" type="string">
  The email subject line. Applies to the `email` channel only.
</ParamField>

<ParamField body="html" type="string">
  An HTML body for email messages. When provided alongside `body`, the `body` field serves as the plain-text fallback for email clients that do not render HTML.
</ParamField>

<ParamField body="template" type="object">
  A WhatsApp-approved message template. Use this instead of `body` for WhatsApp messages that require a pre-approved template.

  <Expandable title="template fields">
    <ParamField body="template.name" type="string" required>
      The exact name of the approved WhatsApp template.
    </ParamField>

    <ParamField body="template.language" type="string" required>
      The language code for the template (e.g. `pt_BR`, `en_US`).
    </ParamField>

    <ParamField body="template.variables" type="array">
      An ordered array of string values to substitute into the template's variable placeholders (e.g. `["Maria", "150"]`).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="scheduled_at" type="string">
  An ISO 8601 UTC datetime string specifying when to send the message (e.g. `2026-07-01T09:00:00Z`). Omit this field to send the message immediately.
</ParamField>

### Request Examples

<CodeGroup>
  ```bash SMS theme={null}
  curl -X POST https://api.zexa.ao/v1/messages \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "channel": "sms",
      "to": "+244912345678",
      "from": "ZEXA",
      "body": "Your verification code is 482910. It expires in 10 minutes."
    }'
  ```

  ```bash Email theme={null}
  curl -X POST https://api.zexa.ao/v1/messages \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "channel": "email",
      "to": "maria@example.com",
      "from": "noreply@myapp.ao",
      "subject": "Your order has been confirmed",
      "body": "Hi Maria, your order #10294 has been confirmed and is being prepared.",
      "html": "<p>Hi Maria,</p><p>Your order <strong>#10294</strong> has been confirmed and is being prepared.</p>"
    }'
  ```

  ```bash WhatsApp Template theme={null}
  curl -X POST https://api.zexa.ao/v1/messages \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "channel": "whatsapp",
      "to": "+244923456789",
      "from": "+244900000001",
      "template": {
        "name": "order_confirmation",
        "language": "pt_BR",
        "variables": ["Maria", "10294", "2026-06-25"]
      }
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.zexa.ao/v1/messages",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={
          "channel": "sms",
          "to": "+244912345678",
          "from": "ZEXA",
          "body": "Your verification code is 482910. It expires in 10 minutes.",
      },
  )

  print(response.json())
  ```
</CodeGroup>

### Response Fields

<ResponseField name="id" type="string">
  The unique message identifier, prefixed with `msg_` (e.g. `msg_a1b2c3d4`). Use this ID to retrieve the message status later.
</ResponseField>

<ResponseField name="status" type="string">
  The initial delivery status of the message. See the [Delivery Statuses](#delivery-statuses) table below.
</ResponseField>

<ResponseField name="channel" type="string">
  The channel the message was sent on.
</ResponseField>

<ResponseField name="to" type="string">
  The recipient identifier as provided in the request.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 UTC timestamp of when the message was created.
</ResponseField>

**Example response (`201 Created`):**

```json theme={null}
{
  "id": "msg_a1b2c3d4",
  "status": "queued",
  "channel": "sms",
  "to": "+244912345678",
  "created_at": "2026-06-24T10:00:00Z"
}
```

### Error Scenarios

| Status | Error                  | Description                                                          |
| ------ | ---------------------- | -------------------------------------------------------------------- |
| `400`  | `invalid_request`      | Missing required fields or malformed JSON body                       |
| `401`  | `unauthorized`         | API key is missing or invalid                                        |
| `422`  | `validation_error`     | `to` is not in E.164 format, or `from` is not a registered Sender ID |
| `422`  | `insufficient_credits` | Account does not have enough credits to send the message             |
| `429`  | `rate_limit_exceeded`  | Too many requests — retry after the time indicated in `Retry-After`  |

## Delivery Statuses

| Status          | Description                                                                           |
| --------------- | ------------------------------------------------------------------------------------- |
| `queued`        | Message has been accepted and is waiting to be dispatched to the carrier or channel   |
| `sent`          | Message has been dispatched to the downstream carrier or channel provider             |
| `delivered`     | The carrier or channel confirmed successful delivery to the recipient's device        |
| `failed`        | Delivery failed after all retry attempts; check the `error` field for the reason      |
| `undeliverable` | The destination is permanently unreachable (e.g. invalid number, deactivated account) |

## Get Message Status

Retrieve the current status of a message by its ID. For production workloads, prefer [webhooks](/api/webhooks) over polling to reduce latency and API usage.

```text theme={null}
GET https://api.zexa.ao/v1/messages/{id}
```

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.zexa.ao/v1/messages/msg_a1b2c3d4 \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.zexa.ao/v1/messages/msg_a1b2c3d4",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  )

  print(response.json())
  ```
</CodeGroup>

**Example response:**

```json theme={null}
{
  "id": "msg_a1b2c3d4",
  "status": "delivered",
  "channel": "sms",
  "to": "+244912345678",
  "created_at": "2026-06-24T10:00:00Z",
  "delivered_at": "2026-06-24T10:00:05Z"
}
```

<Note>
  For production applications, use [webhooks](/api/webhooks) to receive delivery status updates in real time rather than polling this endpoint. Polling introduces latency and counts against your rate limit.
</Note>

***

## List Sender IDs

Retrieve all Sender IDs registered on your account. The `from` field in a send request must match one of these values for SMS, WhatsApp, and Telegram messages.

```text theme={null}
GET https://api.zexa.ao/v1/sender-ids
```

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.zexa.ao/v1/sender-ids \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.zexa.ao/v1/sender-ids",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  )

  print(response.json())
  ```
</CodeGroup>

### Response Fields

<ResponseField name="data" type="array">
  An array of Sender ID objects registered to your account.

  <Expandable title="Sender ID object fields">
    <ResponseField name="id" type="string">
      The unique identifier for this Sender ID record (e.g. `sid_abc123`).
    </ResponseField>

    <ResponseField name="name" type="string">
      The Sender ID string used in the `from` field when sending messages (e.g. `MYBRAND`).
    </ResponseField>

    <ResponseField name="channel" type="string">
      The channel this Sender ID is approved for: `sms`, `whatsapp`, or `telegram`.
    </ResponseField>

    <ResponseField name="status" type="string">
      Approval status of the Sender ID: `active`, `pending`, or `rejected`.
    </ResponseField>

    <ResponseField name="created_at" type="string">
      ISO 8601 UTC timestamp of when the Sender ID was registered.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object">
  Pagination metadata.

  <Expandable title="meta fields">
    <ResponseField name="total" type="integer">
      Total number of Sender IDs registered on the account.
    </ResponseField>

    <ResponseField name="page" type="integer">
      Current page number.
    </ResponseField>

    <ResponseField name="per_page" type="integer">
      Number of results per page.
    </ResponseField>
  </Expandable>
</ResponseField>

**Example response:**

```json theme={null}
{
  "data": [
    {
      "id": "sid_abc123",
      "name": "MYBRAND",
      "channel": "sms",
      "status": "active",
      "created_at": "2026-01-15T08:00:00Z"
    },
    {
      "id": "sid_def456",
      "name": "+244900000001",
      "channel": "whatsapp",
      "status": "active",
      "created_at": "2026-02-10T12:30:00Z"
    }
  ],
  "meta": {
    "total": 2,
    "page": 1,
    "per_page": 50
  }
}
```

<Tip>
  To register a new Sender ID or manage existing ones in the dashboard, visit [Sender IDs](/messaging/sender-ids). Sender ID registration may require carrier approval and can take up to 72 hours depending on the channel and country.
</Tip>
