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
https://api.everymessage.com/v1/rcs/messagesSend 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
| Field | Type | Description |
|---|---|---|
to required | string | Recipient in international format, for example 447700900123. Several can be given, separated by commas. Not needed if you use recipients or recipientParams. |
recipients | array | More recipients. Combined with to; duplicates are removed. |
text | string | A plain text message. |
card | object | A rich card. See Card. |
carousel | array | A carousel: a list of cards the user swipes through (2 to 10). |
templateName | string | The name of an RCS template saved in the portal Template Designer. |
params | object | Values for the template’s {{name}} placeholders, for example {"name": "Sam"}. |
recipientParams | array | Personalise 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. |
contentMessage | object | Advanced: a raw Google RBM contentMessage, for layouts the typed fields don’t cover. |
suggestions | array | Buttons shown under a text message. See Suggestion. |
mode | string | only (default): RCS only. fallback: send an SMS to anyone RCS can’t reach. |
fallbackMode | string | With fallback: text (default) sends fallbackText as a plain SMS; web sends an SMS with a link that shows the card in a web browser. |
fallbackText | string | The SMS text used for fallback. Defaults to text, so always set it for cards and carousels. |
originator | string | The sender to use. Defaults to your account’s sender. |
reference | string | Your own reference. It comes back on the delivery receipt. |
replyUrl | string | Where to send delivery receipts by webhook. See delivery receipts. |
Card
| Field | Type | Description |
|---|---|---|
title | string | Bold heading. |
description | string | Body text. |
mediaUrl | string | A public HTTPS link to an image or video. It must be reachable without signing in, because Google fetches it. |
height | string | Media height: SHORT, MEDIUM (default) or TALL. |
suggestions | array | Buttons on the card. See Suggestion. |
Suggestion
| Field | Type | Description |
|---|---|---|
type required | string | reply, openUrl, dial, viewLocation, viewLocationQuery or shareLocation. |
text required | string | The button label (keep it short: 25 characters or fewer). |
postback | string | reply: the value sent back to you when it is tapped. Defaults to the label. |
url | string | openUrl: the web page to open. |
phone | string | dial: the number to call, for example +441234567890. |
lat, lng, label | number, number, string | viewLocation: map position and pin label. |
query | string | viewLocationQuery: 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
}
]
}
| Field | Type | Description |
|---|---|---|
status | string | accepted. For recipientParams requests: accepted, partial or failed. |
mode | string | only or fallback. |
category | string | Charging tier: basic or rich. |
count | integer | Number of recipients accepted. |
charged | number | Total cost. |
transactionId | integer | Our id for the request. |
recipients | array | One entry per recipient: to, status, messageId (used on delivery receipts), rcsMessageId (used to revoke or replace), conversationId and charged. |
Errors
| HTTP status | Meaning |
|---|---|
400 | Missing recipient or content, invalid JSON, or invalid contentMessage / template output. |
401 | Wrong api_key or secret_key. |
402 | Not enough credit on a prepay account. |
403 | RCS is not enabled for your account, no RCS brand is linked to the sender, or the account is blocked. |
404 | No template with that templateName. |
429 | Your daily sending limit has been reached. |
500 | Something 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.
https://api.everymessage.com/v1/rcs/messages/revokeRequest
{
"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.
https://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')}")
