Send RCS

Send branded RCS messages, with rich cards, carousels and buttons, to Android and iPhone users. Anyone who can’t receive RCS automatically gets an SMS instead.

POST the recipient and the content to https://api.everymessage.com/v1/rcs/messages. Content can be plain text, a rich card, a carousel, or a template you saved in the portal. Each recipient gets an rcsMessageId you can use to revoke or replace the message before it is delivered.

Before you start: RCS must be switched on for your account, and your sender must be linked to a verified RCS brand (agent). Read about RCS or contact us to set it up.

Method

POSThttps://api.everymessage.com/v1/rcs/messages

Send Content-Type: application/json and authenticate with HTTP Basic (details).

The RCS API uses HTTP status codes. Unlike /v1/submit, it does not use the response envelope: 202 means accepted, and errors return a 4xx status with {"error": "…"}.

Request

Rich card

{
  "to": "447700900123",
  "mode": "fallback",
  "fallbackText": "Your parcel arrives today, 10:00-12:00. Track it: https://rcsview.uk/A1042",
  "reference": "parcel-A1042",
  "card": {
    "title": "Your parcel arrives today",
    "description": "Delivery window 10:00-12:00",
    "mediaUrl": "https://example.com/images/delivery-van.png",
    "suggestions": [
      {
        "type": "openUrl",
        "text": "Track parcel",
        "url": "https://example.com/t/A1042"
      },
      {
        "type": "reply",
        "text": "Leave with a neighbour"
      },
      {
        "type": "dial",
        "text": "Call us",
        "phone": "+441234567890"
      }
    ]
  }
}

Text and buttons

{
  "to": "447700900123",
  "text": "Your engineer is on the way and will arrive by 11:00.",
  "suggestions": [
    {
      "type": "viewLocationQuery",
      "text": "Show address",
      "query": "10 Downing Street, London"
    },
    {
      "type": "reply",
      "text": "Reschedule"
    }
  ]
}

Carousel

{
  "recipients": [
    "447700900123",
    "447700900456"
  ],
  "mode": "fallback",
  "fallbackText": "This week's offers: https://example.com/offers",
  "carousel": [
    {
      "title": "Winter tyres",
      "description": "20% off this week",
      "mediaUrl": "https://example.com/img/tyres.png",
      "suggestions": [
        {
          "type": "openUrl",
          "text": "Book a fitting",
          "url": "https://example.com/tyres"
        }
      ]
    },
    {
      "title": "Free health check",
      "description": "While you wait",
      "mediaUrl": "https://example.com/img/check.png",
      "suggestions": [
        {
          "type": "reply",
          "text": "Book me in",
          "postback": "BOOK_CHECK"
        }
      ]
    }
  ]
}

Template

{
  "to": "447700900123",
  "mode": "fallback",
  "templateName": "appointment_reminder",
  "params": {
    "name": "Sam",
    "time": "10:30"
  }
}

Personalised

{
  "mode": "fallback",
  "templateName": "appointment_reminder",
  "recipientParams": [
    {
      "to": "447700900123",
      "params": {
        "name": "Sam",
        "time": "10:30"
      }
    },
    {
      "to": "447700900456",
      "params": {
        "name": "Priya",
        "time": "11:15"
      }
    }
  ]
}

Give the content in exactly one of these ways: text, card, carousel, templateName or contentMessage. A text message (with or without buttons) is charged at the basic rate; a card or carousel at the rich rate. The response tells you which in category.

Request body

FieldTypeDescription
to requiredstringRecipient in international format, for example 447700900123. Several can be given, separated by commas. Not needed if you use recipients or recipientParams.
recipientsarrayMore recipients. Combined with to; duplicates are removed.
textstringA plain text message.
cardobjectA rich card. See Card.
carouselarrayA carousel: a list of cards the user swipes through (2 to 10).
templateNamestringThe name of an RCS template saved in the portal Template Designer.
paramsobjectValues for the template’s {{name}} placeholders, for example {"name": "Sam"}.
recipientParamsarrayPersonalise a template per person: a list of {"to": "…", "params": {…}}. Replaces to and recipients. Shared values can go in params; a recipient’s own values win.
contentMessageobjectAdvanced: a raw Google RBM contentMessage, for layouts the typed fields don’t cover.
suggestionsarrayButtons shown under a text message. See Suggestion.
modestringonly (default): RCS only. fallback: send an SMS to anyone RCS can’t reach.
fallbackModestringWith fallback: text (default) sends fallbackText as a plain SMS; web sends an SMS with a link that shows the card in a web browser.
fallbackTextstringThe SMS text used for fallback. Defaults to text, so always set it for cards and carousels.
originatorstringThe sender to use. Defaults to your account’s sender.
referencestringYour own reference. It comes back on the delivery receipt.
replyUrlstringWhere to send delivery receipts by webhook. See delivery receipts.

Card

FieldTypeDescription
titlestringBold heading.
descriptionstringBody text.
mediaUrlstringA public HTTPS link to an image or video. It must be reachable without signing in, because Google fetches it.
heightstringMedia height: SHORT, MEDIUM (default) or TALL.
suggestionsarrayButtons on the card. See Suggestion.

Suggestion

FieldTypeDescription
type requiredstringreply, openUrl, dial, viewLocation, viewLocationQuery or shareLocation.
text requiredstringThe button label (keep it short: 25 characters or fewer).
postbackstringreply: the value sent back to you when it is tapped. Defaults to the label.
urlstringopenUrl: the web page to open.
phonestringdial: the number to call, for example +441234567890.
lat, lng, labelnumber, number, stringviewLocation: map position and pin label.
querystringviewLocationQuery: an address or place to show on a map.

shareLocation asks the user to send you their location and needs only type and text.

SMS fallback

RCS works on most modern Android phones and on iPhones running iOS 18 or later, where the network supports it. With "mode": "fallback" we try RCS first and automatically send an SMS to anyone it can’t reach, all from one request. Use "mode": "only" when you would rather not send at all than send an SMS.

  • Text fallback sends fallbackText. Put the key information and a link in it, because buttons and images are lost.
  • Web fallback ("fallbackMode": "web") sends an SMS containing a short link that shows the same card, images and buttons in the phone’s browser.

SMS fallback is charged at your SMS rate.

Response

HTTP 202 Accepted:

{
  "status": "accepted",
  "channel": "RCS",
  "mode": "fallback",
  "category": "rich",
  "count": 1,
  "charged": 0.04,
  "transactionId": 4821,
  "recipients": [
    {
      "to": "447700900123",
      "status": "Success",
      "messageId": 48233,
      "rcsMessageId": "261011_4821_1_1_1_0_1-447700900123",
      "conversationId": "K7Q2M4XW9PLD3T6B",
      "charged": 0.04
    }
  ]
}
FieldTypeDescription
statusstringaccepted. For recipientParams requests: accepted, partial or failed.
modestringonly or fallback.
categorystringCharging tier: basic or rich.
countintegerNumber of recipients accepted.
chargednumberTotal cost.
transactionIdintegerOur id for the request.
recipientsarrayOne entry per recipient: to, status, messageId (used on delivery receipts), rcsMessageId (used to revoke or replace), conversationId and charged.

Errors

HTTP statusMeaning
400Missing recipient or content, invalid JSON, or invalid contentMessage / template output.
401Wrong api_key or secret_key.
402Not enough credit on a prepay account.
403RCS is not enabled for your account, no RCS brand is linked to the sender, or the account is blocked.
404No template with that templateName.
429Your daily sending limit has been reached.
500Something went wrong on our side. Safe to retry after a short wait.

Revoke and replace

You can pull back an RCS message that has not yet been delivered, for example when a phone is switched off. Once a message has been delivered it can’t be recalled or edited.

POSThttps://api.everymessage.com/v1/rcs/messages/revoke

Request

{
  "to": "447700900123",
  "messageId": "261011_4821_1_1_1_0_1-447700900123"
}

Response

{
  "to": "447700900123",
  "messageId": "261011_4821_1_1_1_0_1-447700900123",
  "revoked": true,
  "status": "REVOKED"
}

Responses: 200 revoked; 404 unknown or expired messageId; 409 too late, usually because it was already delivered.

POSThttps://api.everymessage.com/v1/rcs/messages/replace
{
  "to": "447700900123",
  "messageId": "261011_4821_1_1_1_0_1-447700900123",
  "mode": "fallback",
  "text": "Update: your engineer will now arrive by 13:00."
}

Replace takes every send field plus the messageId to replace. It revokes the original if it is still pending, then sends the new content as a new, charged message. The new message is sent even if the original couldn’t be revoked; the response’s revoked object says what happened, and rcsMessageId is the new id.

Delivery receipts

RCS delivery receipts arrive in the same format as SMS receipts: by webhook to replyUrl (or your account’s default receipt URL), or by collecting them. They refer to the messageId in the send response. See delivery receipts. Replies and button taps from RCS users are delivered to your configured inbound destination.

Code examples

Send a rich card with SMS fallback

Both examples read the keys from EM_API_KEY and EM_SECRET_KEY. C# needs .NET 8 or later; Python needs 3.10 or later and pip install httpx.

C#

// Send an RCS rich card, falling back to SMS, with the everymessage v1 API (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;

using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));

var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

var request = new RcsMessage(
    To: "447700900123",
    Mode: "fallback",
    FallbackText: "Your parcel arrives today, 10:00-12:00. Track it: https://rcsview.uk/A1042",
    Reference: "parcel-A1042",
    Card: new Card(
        Title: "Your parcel arrives today",
        Description: "Delivery window 10:00-12:00",
        MediaUrl: "https://example.com/images/delivery-van.png",
        Suggestions:
        [
            new Suggestion("openUrl", "Track parcel", Url: "https://example.com/t/A1042"),
            new Suggestion("reply", "Leave with a neighbour"),
            new Suggestion("dial", "Call us", Phone: "+441234567890")
        ]));

using var response = await http.PostAsJsonAsync("v1/rcs/messages", request, json);

// Unlike /v1/submit, the RCS API uses HTTP status codes: 202 means accepted.
if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException(
        $"RCS send failed ({(int)response.StatusCode}): {await response.Content.ReadAsStringAsync()}");

var result = await response.Content.ReadFromJsonAsync<RcsResult>(json)
             ?? throw new InvalidOperationException("Empty response from everymessage");

// Keep rcsMessageId if you may want to revoke or replace the message.
foreach (var r in result.Recipients)
    Console.WriteLine($"{r.To}: {r.Status} (rcsMessageId {r.RcsMessageId})");

Console.WriteLine($"{result.Category} message, charged {result.Charged}");

record RcsMessage(string To, Card Card, string Mode = "only",
    string? FallbackText = null, string? Reference = null);
record Card(string Title, string Description, string? MediaUrl = null,
    IReadOnlyList<Suggestion>? Suggestions = null);
record Suggestion(string Type, string Text, string? Url = null, string? Phone = null);

record RcsResult(string Status, string Category, decimal Charged, IReadOnlyList<RecipientResult> Recipients);
record RecipientResult(string To, string Status, long MessageId, string? RcsMessageId);

Python

"""Send an RCS rich card, falling back to SMS (Python 3.10+, pip install httpx)."""
import os

import httpx

payload = {
    "to": "447700900123",
    "mode": "fallback",
    "fallbackText": "Your parcel arrives today, 10:00-12:00. Track it: https://rcsview.uk/A1042",
    "reference": "parcel-A1042",
    "card": {
        "title": "Your parcel arrives today",
        "description": "Delivery window 10:00-12:00",
        "mediaUrl": "https://example.com/images/delivery-van.png",
        "suggestions": [
            {"type": "openUrl", "text": "Track parcel", "url": "https://example.com/t/A1042"},
            {"type": "reply", "text": "Leave with a neighbour"},
            {"type": "dial", "text": "Call us", "phone": "+441234567890"},
        ],
    },
}

with httpx.Client(
    base_url="https://api.everymessage.com",
    auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
    timeout=30,
) as client:
    response = client.post("/v1/rcs/messages", json=payload)

# Unlike /v1/submit, the RCS API uses HTTP status codes: 202 means accepted.
if response.is_error:
    raise RuntimeError(f"RCS send failed ({response.status_code}): {response.text}")

result = response.json()
# Keep rcsMessageId if you may want to revoke or replace the message.
for r in result["recipients"]:
    print(f"{r['to']}: {r['status']} (rcsMessageId {r.get('rcsMessageId')})")
print(f"{result['category']} message, charged {result['charged']}")

Revoke a message

C#

// Revoke an RCS message that has not been delivered yet (.NET 8 or later)
// Usage: dotnet run -- <to> <rcsMessageId>
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;

var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;

using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));

using var response = await http.PostAsJsonAsync("v1/rcs/messages/revoke",
    new { to = args[0], messageId = args[1] });

// 200 = revoked, 404 = unknown or expired id, 409 = too late (usually already delivered).
if (response.StatusCode is not (HttpStatusCode.OK or HttpStatusCode.NotFound or HttpStatusCode.Conflict))
    throw new InvalidOperationException(
        $"Revoke failed ({(int)response.StatusCode}): {await response.Content.ReadAsStringAsync()}");

var result = await response.Content.ReadFromJsonAsync<RevokeResult>()
             ?? throw new InvalidOperationException("Empty response from everymessage");

Console.WriteLine(result.Revoked ? "Revoked before delivery" : $"Not revoked: {result.Status}");

record RevokeResult(string To, string MessageId, bool Revoked, string? Status);

Python

"""Revoke an RCS message that has not been delivered yet (Python 3.10+, pip install httpx).

Usage: python revoke_rcs.py <to> <rcsMessageId>
"""
import os
import sys

import httpx

with httpx.Client(
    base_url="https://api.everymessage.com",
    auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
    timeout=30,
) as client:
    response = client.post("/v1/rcs/messages/revoke",
                           json={"to": sys.argv[1], "messageId": sys.argv[2]})

# 200 = revoked, 404 = unknown or expired id, 409 = too late (usually already delivered).
if response.status_code not in (200, 404, 409):
    raise RuntimeError(f"Revoke failed ({response.status_code}): {response.text}")

result = response.json()
print("Revoked before delivery" if result["revoked"] else f"Not revoked: {result.get('status')}")