RCS Business Messaging — SMS's better-looking sibling.
Rich cards, suggested replies, branded sender, read receipts. Fall back to SMS in one attribute. Same auth, same idempotency, same webhooks as every other Bird channel — because the same engineering team built them all.
import { BirdClient } from "@bird/sdk";
const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });
const { data, error } = await bird.rcs.send({
to: "+15005550006",
card: {
title: "Your order has shipped",
subtitle: "Arriving Friday, May 22",
image: "https://cdn.example.com/orders/2891.jpg",
buttons: [
{ type: "url", label: "Track package", url: "https://example.com/track/2891" },
{ type: "reply", label: "Reschedule", postback: "reschedule_2891" },
],
},
fallback: "sms",
}).safe();
if (error) throw error;
console.log(data.id);
// → "rcs_4mP82wQ9..."5 minutes from npm install to first send
Send a rich message from the language you already use.
SDKs in every major runtime. The first send goes to the sanctioned test number (+15005550006) and falls back to SMS automatically — so the CI check doesn't require an RCS-capable handset.
import { BirdClient } from "@bird/sdk";
const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });
const { data, error } = await bird.rcs.send({
to: "+15005550006",
card: { title: "Hello", subtitle: "From Bird RCS" },
fallback: "sms",
}).safe();Ten primitives between "text" and a real app surface.
RCS is a richer protocol than SMS. We expose the richness as primitives, not as a designer canvas.
- 01
Rich cards
Title, subtitle, hero image, and up to 4 actions per card. Carousel up to 10 cards in one message.
- 02
Suggested replies
Tap-to-reply chips that post a known string back to your webhook. No NLU guessing on your end.
- 03
Branded sender
Verified business identity with logo, color, and short description in the handset chat header.
- 04
Read receipts and typing
Delivery, read, and "is typing" states as webhooks — the same envelope shape as every other channel.
- 05
Fallback to SMS in one attribute
Pass fallback: "sms". If the handset is not RCS-capable, we transparently send the text variant.
- 06
Interactive postbacks
Button taps come back as rcs.replied events with the postback string you set. Wire to a state machine.
- 07
Media attachments
Images, video up to 100MB, audio, PDF, vCard. We host the asset and serve a signed URL with TTL.
- 08
Two-way conversation
Inbound user replies arrive as HMAC-signed webhooks. Same shape as inbound SMS and WhatsApp.
- 09
Carrier and device gating
We check RCS capability before send. If the device is offline or non-RCS, we fall back automatically.
- 10
Same auth, same error envelope
One API key for RCS, SMS, Email, WhatsApp, Voice. One error type registry across all of them.
Why we build RCS
Because in 2026 the cheapest channel and the richest one should be the same send.
RCS handsets get a rich card; everyone else gets the SMS variant we generated from the same payload. We've been running SMS for ten years across 240 direct-to-carrier connections; RCS is the same routing layer with a richer payload type and a fallback flag. Honest about coverage: today RCS lands on US carriers (T-Mobile, Verizon, AT&T) and Brazil; EU and APAC expand through 2026. Outside that footprint, the fallback flag does the work.
import { BirdClient } from "@bird/sdk";
const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });
const { data, error } = await bird.rcs.send({
to: "+15005550006",
card: {
title: "Your order has shipped",
subtitle: "Arriving Friday, May 22",
image: "https://cdn.example.com/orders/2891.jpg",
buttons: [
{ type: "url", label: "Track package", url: "https://example.com/track/2891" },
{ type: "reply", label: "Reschedule", postback: "reschedule_2891" },
],
},
fallback: "sms",
}).safe();
if (error) throw error;
console.log(data.id);
// → "rcs_4mP82wQ9..."Every state change is a webhook.
HMAC-signed payloads, replay-protected, idempotent. The same envelope on every Bird channel — learn one, you've learned them all.
{
"type": "rcs.delivered",
"id": "evt_7nT91x...",
"created_at": "2026-05-19T15:42:01.221Z",
"data": {
"rcs_id": "rcs_4mP82wQ9",
"to": "+15005550006",
"carrier": "T-Mobile USA",
"rich": true,
"latency_ms": 612
}
}Retry schedule: 5s, 30s, 5m, 30m, 2h, 6h, 12h. Dead-letter after the final attempt; every dead-lettered event is replayable from the dashboard or API.
rcs.queuedAccepted by the API and queued for send.rcs.sentHanded off to the carrier RBM gateway.rcs.deliveredHandset confirmed receipt of the rich message.rcs.readRecipient read the message (if read receipts enabled).rcs.repliedUser tapped a suggested-reply chip or sent a free-form reply.rcs.fellbackHandset was not RCS-capable; the SMS variant was sent instead.rcs.failedPermanent failure before send (invalid recipient, carrier reject).
If you've integrated SMS, you've integrated RCS.
Same auth, same idempotency contract, same error envelope, same webhook shape. The difference is the payload — not how you call it.
RCS.
await bird.rcs.send({
to: "+15005550006",
card: { title: "Your code", subtitle: `Code: ${code}` },
fallback: "sms",
});A real card on RCS-capable devices. Outside the footprint, the fallback flag routes the same payload over SMS.
SMS.
await bird.sms.send({
from: "Bird",
to: "+15005550006",
text: `Your code is ${code}.`,
});Address the SMS path directly when you want it explicit. Same auth, same webhooks, same idempotency.