# Sending

Send RCS text, media, rich cards and carousels with buttons from your agent with POST /v1/rcs/messages, with SMS fallback for phones without RCS.

When you finish this page you have sent every kind of RCS message and know what happens on phones that cannot receive RCS.

## Step 1: Send a text with buttons

You need an agent in `testing` (to reach your accepted test phones) or `live` (to reach anyone). See [registering an agent](/platforms/rcs/connect).

Call `POST /v1/rcs/messages` with `agentId`, `to` and either `text` (a plain message) or `content` (anything richer).

This example sends text with three buttons.

```bash
curl -X POST "https://zernio.com/api/v1/rcs/messages" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821-shipped" \
  -d '{
    "agentId": "6aba8af3e7aba406647e82a1",
    "to": "+13125550188",
    "content": {
      "type": "text",
      "text": "Your order #4821 has shipped. It arrives Thursday between 10:00 and 14:00.",
      "suggestions": [
        { "type": "openUrl", "text": "Track package", "url": "https://northsideshoes.example.com/t/4821" },
        { "type": "calendarEvent", "text": "Add to calendar", "startTime": "2027-01-07T10:00:00Z", "endTime": "2027-01-07T14:00:00Z", "title": "Northside Shoes delivery" },
        { "type": "reply", "text": "Change address", "postbackData": "change_address_4821" }
      ]
    },
    "fallbackText": "Northside Shoes: order #4821 shipped, arriving Thursday. Track it: https://northsideshoes.example.com/t/4821"
  }'
```

Response (`200`):

```json
{
  "id": "4031938e-60e4-4235-a8dd-0b1c55a23e7a",
  "conversationId": "66c3d2ae7b4f6c8d0e1f2a3c",
  "status": "sent"
}
```

The message threads into the agent's inbox conversation with that phone number. Its delivery and read receipts arrive as webhooks (see [receiving](/platforms/rcs/inbox)).

Send an `Idempotency-Key` header so a retry replays the original response instead of sending twice ([idempotency](/guides/idempotency)).

## Step 2: Send rich content

`content.type` picks the layout. Every type accepts up to 11 `suggestions` shown under the message.

**Media**: an image or video on its own.

```json
{ "type": "media", "media": { "url": "https://northsideshoes.example.com/img/trail-runner.jpg" } }
```

**Rich card**: media, a title up to 200 characters, a description up to 2,000 and up to 4 buttons on the card.

- `orientation` is `VERTICAL` (default) or `HORIZONTAL`.
- `media.height` is `SHORT`, `MEDIUM` (default) or `TALL`.

```json
{
  "type": "card",
  "card": {
    "title": "Trail Runner 2",
    "description": "20% off this weekend with code TRAIL20. Free returns for 60 days.",
    "media": { "url": "https://northsideshoes.example.com/img/trail-runner.jpg", "height": "MEDIUM" },
    "suggestions": [
      { "type": "openUrl", "text": "Shop now", "url": "https://northsideshoes.example.com/trail" },
      { "type": "reply", "text": "Not interested", "postbackData": "optout_promo" }
    ]
  }
}
```

**Carousel**: 2 to 10 cards the person swipes through. `cardWidth` is `SMALL` or `MEDIUM` (default).

```json
{
  "type": "carousel",
  "cards": [
    { "title": "City Knit", "description": "$89, 6 colours", "media": { "url": "https://northsideshoes.example.com/img/city-knit.jpg" } },
    { "title": "Trail Runner 2", "description": "$120, waterproof", "media": { "url": "https://northsideshoes.example.com/img/trail.jpg" } }
  ]
}
```

## Buttons

| `type` | What a tap does | Extra fields |
|---|---|---|
| `reply` | Sends the label back as a reply | `postbackData` |
| `openUrl` | Opens a link | `url`, optional `application` (`BROWSER` or `WEBVIEW`) and `webviewViewMode` (`FULL`, `HALF`, `TALL`) |
| `dial` | Starts a call | `phoneNumber` (E.164) |
| `viewLocation` | Opens the map on a pin | `latitude`, `longitude`, optional `label` |
| `shareLocation` | Asks the person to share their location | none |
| `calendarEvent` | Adds an event to their calendar | `startTime`, `endTime`, `title` (100), optional `description` (500) |

Button labels are up to 25 characters.

`postbackData` is up to 2,048 characters of any string, JSON included. It comes back unchanged when the button is tapped, and defaults to the label.

## SMS fallback

If the agent has `smsFallbackFrom` set, phones that cannot receive RCS get the message as plain SMS from that number.

The SMS carries `fallbackText` when you send it. Otherwise it carries the message's text (for cards, the title, description and media link).

Without a fallback number, the message only reaches RCS phones.

Add `ttlSeconds` to let an undelivered message expire after that many seconds.

To check ahead of time which recipients can receive RCS, and which rich features their phones support, call `GET /v1/rcs/capabilities` with up to 100 numbers:

```bash
curl "https://zernio.com/api/v1/rcs/capabilities?agentId=6aba8af3e7aba406647e82a1&numbers=%2B13125550188,%2B13125550177" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
```

Response (`200`):

```json
{
  "capabilities": [
    { "phoneNumber": "+13125550188", "rcsCapable": true, "features": ["RICHCARD_STANDALONE", "RICHCARD_CAROUSEL", "ACTION_OPEN_URL", "ACTION_DIAL"] },
    { "phoneNumber": "+13125550177", "rcsCapable": false }
  ]
}
```

## If it fails

- `403`: the agent is not launched yet and the number is not an accepted test phone (the message says so).
- `409`: the recipient opted out (code `recipient_opted_out`), or the agent does not exist with the carriers yet (`invalid_resource_state`).
- `404`: the agent is not on your team.

Every error uses the envelope in [error handling](/guides/error-handling).

## Related

- [Receiving](/platforms/rcs/inbox): replies, button taps and receipts.
- [Registering an agent](/platforms/rcs/connect): test phones and the SMS fallback number.