# SMS and MMS API

Send and receive texts and media from your own phone numbers or a brand name, with US carrier registration, opt-outs and replies by webhook.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

The SMS and MMS API sends texts and media from phone numbers you own on Zernio, or from a brand name. Replies arrive by webhook and land in the same inbox as your other channels.

## What you can build

- **Order and appointment notifications.** Send a text with `POST /v1/sms/messages`, or schedule it with `sendAt`.
- **Two-way support over text.** Get replies as [`message.received`](/webhooks/inbox/message-received) webhooks and answer from the same number.
- **Receipts and photos by MMS.** Attach up to 10 public media URLs with `mediaUrls`.
- **Campaigns to a contact list.** Send the same text to many contacts with an SMS [broadcast](/broadcasts/create-broadcast).
- **Branded one-way alerts abroad.** Send from a name like `ACME` with a sender ID made by `POST /v1/sms/sender-ids`.

## Platforms

<ProductPlatforms product="sms" />

A text goes out from one of two kinds of sender:

| Sender | Replies | Where it sends | Set up |
|---|---|---|---|
| A phone number you own | Yes | Its own country, or abroad when the number allows it | [Buy a number](/products/phone-numbers) and turn on SMS. US numbers need a [carrier registration](/platforms/sms/setup) |
| An alphanumeric [sender ID](/platforms/sms/sender-ids) | No | Many countries, never the US, Canada or Puerto Rico | `POST /v1/sms/sender-ids`. Text only, no MMS |

## How it works

`from` is a number you own with SMS turned on, or one of your sender IDs. Zernio finds the sender by that value, so you never pass an `accountId`.

Every text threads into an inbox conversation with the recipient. Delivery arrives later as `message.delivered` or `message.failed`, and replies as `message.received` with `platform: "sms"`.

Texts bill per segment by destination, from your [prepaid balance](/pricing/balance) (older accounts: the monthly invoice). Rates are on [phone pricing](/pricing/phone).

## Your first request

You need an API key and a number with SMS turned on. This sends one text.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data } = await zernio.sms.sendSms({
  body: {
    from: '+14155550100',
    to: '+13105551234',
    text: 'Acme: your order #1234 has shipped. Reply STOP to opt out.',
  },
});

console.log(data.id, data.status);
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

sent = client.sms.send_sms(
    from_="+14155550100",
    to="+13105551234",
    text="Acme: your order #1234 has shipped. Reply STOP to opt out.",
)

print(sent["id"], sent["status"])
```
</Tab>
<Tab value="curl">
```bash
curl -X POST "https://zernio.com/api/v1/sms/messages" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "+14155550100", "to": "+13105551234", "text": "Acme: your order #1234 has shipped. Reply STOP to opt out."}'
```
</Tab>
</Tabs>

Response (`200`):

```json
{
  "id": "67c9d0e1f2a3b4c5d6e7f8a9",
  "conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
  "status": "sent"
}
```

`status: "sent"` means the carrier accepted it. Every field is on [Send an SMS/MMS](/sms/send-sms).

## Examples

### Send an MMS

Add `mediaUrls` with public URLs, up to 10. Pass `text` for a caption, or leave it out to send media only.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
const { data } = await zernio.sms.sendSms({
  body: {
    from: '+14155550100',
    to: '+13105551234',
    text: 'Here is your receipt',
    mediaUrls: ['https://acme.example.com/receipts/1234.png'],
  },
});
```
</Tab>
<Tab value="Python">
```python
sent = client.sms.send_sms(
    from_="+14155550100",
    to="+13105551234",
    text="Here is your receipt",
    media_urls=["https://acme.example.com/receipts/1234.png"],
)
```
</Tab>
<Tab value="curl">
```bash
curl -X POST "https://zernio.com/api/v1/sms/messages" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14155550100",
    "to": "+13105551234",
    "text": "Here is your receipt",
    "mediaUrls": ["https://acme.example.com/receipts/1234.png"]
  }'
```
</Tab>
</Tabs>

MMS works only where the number supports it: check `mmsCapable` when you [turn on SMS](/platforms/sms/setup).

### Receive a reply by webhook

Subscribe an endpoint to `message.received` with `POST /v1/webhooks/settings` ([webhooks](/webhooks)). A text to your number then arrives like this, trimmed:

```json
{
  "event": "message.received",
  "message": {
    "id": "67c9d0e1f2a3b4c5d6e7f8b0",
    "conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
    "platform": "sms",
    "direction": "incoming",
    "text": "Can I change the delivery address?",
    "attachments": [],
    "sender": { "id": "+13105551234", "name": "+13105551234" }
  },
  "account": { "platform": "sms", "username": "+14155550100" }
}
```

Answer with `POST /v1/sms/messages`, using your number as `from` and the sender as `to`. The reply threads into the same `conversationId`.

### Schedule a text

`sendAt` queues the text for a future time. `message.delivered` fires when it actually goes out.

```bash
curl -X POST "https://zernio.com/api/v1/sms/messages" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from": "+14155550100", "to": "+13105551234", "text": "Reminder: your appointment is tomorrow at 10:00.", "sendAt": "2026-11-17T09:00:00Z"}'
```

## How it behaves

- **Length.** `text` is at most 10 segments: 1,530 GSM-7 or 670 unicode characters.
- **Opt-outs.** A recipient who replies STOP is opted out, and later sends to them answer `409`. `GET /v1/sms/opt-outs` lists them.
- **US numbers.** They receive from the start, but send only once their carrier registration is approved.
- **Retries.** Send an `Idempotency-Key` header so a retry never sends twice.

## If it fails

| Error | Cause | Fix |
|---|---|---|
| `403` `registration_required` or `registration_pending` | The US number's carrier registration is missing or not approved yet | [Register the number](/platforms/sms/setup) and wait for `approved` |
| `403` `cross_border_not_supported` | The number can only text its own country | Use a number from the recipient's country |
| `403` `mms_not_supported` | The sender cannot send media | Send text only, or use an MMS-capable number |
| `404` | No SMS-enabled number or sender ID of yours matches `from` | Turn on SMS for the number, or fix `from` |
| `409` `recipient_opted_out` | The recipient replied STOP | Drop them from your list |

## Related

- [Sending](/platforms/sms/sending)
- [Carrier registration](/platforms/sms/setup)
- [Sender IDs](/platforms/sms/sender-ids)
- [Phone Numbers API](/products/phone-numbers)
- [Inbox webhooks](/webhooks/inbox)
- [Phone pricing](/pricing/phone)