fastify-xrcs
fastify-xrcs
Fastify plugin for building and sending RCS rich cards and carousels via the Twilio Content API. Decorates the server with fastify.xrcs providing fluent builders for cards, carousels, and content templates, plus API methods to create, send, list, and delete templates. Works in builder-only mode (no Twilio credentials) or API mode (with credentials).
Installation
npm install @xenterprises/fastify-xrcs fastify@5
Quick Start
import Fastify from "fastify";
import xRCS from "@xenterprises/fastify-xrcs";
const fastify = Fastify();
await fastify.register(xRCS, {
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
});
fastify.post("/notify/:phone", async (request) => {
const card = fastify.xrcs.card()
.title("Order Confirmed")
.body("Your order #1234 is on its way!")
.urlButton("Track Order", "https://example.com/track/1234")
.build();
return fastify.xrcs.sendCard(`+1${request.params.phone}`, card);
});
await fastify.listen({ port: 3000 });
Options
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
accountSid | string | — | No | Twilio Account SID (starts with AC). Must be non-empty and provided together with authToken |
authToken | string | — | No | Twilio Auth Token. Must be non-empty and provided together with accountSid |
messagingServiceSid | string | — | No | Twilio Messaging Service SID (starts with MG). Required at send time for sendMessage/sendCard/sendCarousel |
active | boolean | true | No | Set to false to disable the plugin entirely |
accountSid and authToken must always be provided together. When both are omitted the plugin registers in builder-only mode — builders and validators work but API calls throw. Passing an empty or whitespace-only string for either option is now rejected at registration (see Error Reference); to use builder-only mode, omit both options entirely.
Methods
API methods (require Twilio credentials)
- createTemplate(builder, register?) — Build a payload and optionally register it with the Twilio Content API.
- sendMessage(to, contentSid, vars?) — Send a message using an existing Content API template SID.
- sendCard(to, card, friendlyName?) — Create a card template and send it in one step.
- sendCarousel(to, carousel, friendlyName?) — Create a carousel template and send it in one step.
- getTemplate(contentSid) — Fetch a content template by SID.
- listTemplates(limit?) — List all content templates.
- deleteTemplate(contentSid) — Delete a content template.
- getMessageStatus(messageSid) — Fetch delivery status for a sent message.
Builders (no credentials required)
- card() — Fluent
CardBuilderfor RCS rich cards with title, body, media, and buttons. - carousel() — Fluent
CarouselBuilderfor multi-card carousels. - template(name?) — Fluent
ContentTemplateBuilderfor Twilio Content API payloads.
Validators
- validate.card(card) — Validate a card object without throwing.
- validate.carousel(carousel) — Validate a carousel object without throwing.
Constants
| Export | Values |
|---|---|
CONTENT_TYPES | TEXT, MEDIA, CARD, CAROUSEL, QUICK_REPLY, CALL_TO_ACTION, LIST_PICKER |
ACTION_TYPES | QUICK_REPLY, URL, PHONE_NUMBER |
MEDIA_HEIGHT | SHORT, MEDIUM, TALL |
Also exported as named exports: CONTENT_TYPES, ACTION_TYPES, MEDIA_HEIGHT, CardBuilder, CarouselBuilder, ContentTemplateBuilder, and buildRcsApi(twilioClient, messagingServiceSid) (the API factory, exported so tests can inject a fake client). The package's default export is the Fastify plugin.
Error Reference
Registration fails fast with suite-standard messages that name the plugin, the option, and a usage example:
| Error | Cause |
|---|---|
xrcs: option `active` must be a boolean, e.g. `app.register(xRCS, { active: false })` | Non-boolean active option |
xrcs: option `accountSid` must be a string, e.g. `app.register(xRCS, { accountSid: 'AC...', authToken: 'your-auth-token' })` | Non-string accountSid option |
xrcs: option `accountSid` must be a non-empty string, e.g. `app.register(xRCS, { ... })` | Empty or whitespace-only accountSid (previously silently landed in builder-only mode) |
xrcs: option `authToken` must be a string, e.g. `app.register(xRCS, { accountSid: 'AC...', authToken: 'your-auth-token' })` | Non-string authToken option |
xrcs: option `authToken` must be a non-empty string, e.g. `app.register(xRCS, { ... })` | Empty or whitespace-only authToken |
xrcs: option `messagingServiceSid` must be a string, e.g. `app.register(xRCS, { ..., messagingServiceSid: 'MG...' })` | Non-string messagingServiceSid option |
xrcs: options `accountSid` and `authToken` must be provided together, e.g. `app.register(xRCS, { ... })` | Only one of the pair provided |
Runtime and builder errors keep the [xRCS] prefix:
| Error | Cause |
|---|---|
[xRCS] Twilio credentials required to register templates | createTemplate (or sendCard/sendCarousel) without credentials |
[xRCS] Twilio credentials required to send messages | sendMessage without credentials |
[xRCS] Twilio credentials required | Read APIs (getTemplate/listTemplates/deleteTemplate/getMessageStatus) without credentials |
[xRCS] messagingServiceSid required to send messages | Sending without messagingServiceSid |
[xRCS] 'to' must be a non-empty string | Missing or invalid recipient |
[xRCS] 'contentSid' must be a non-empty string | Missing template SID |
[xRCS] 'messageSid' must be a non-empty string | Missing message SID |
[xRCS] 'limit' must be a positive number | Invalid listTemplates limit |
[xRCS] Card title must be 200 characters or less | Title too long |
[xRCS] Media URL must be a non-empty string | Missing media URL |
[xRCS] Invalid media height '{height}'. Must be one of: short, medium, tall | Invalid media height |
[xRCS] Button title must be a non-empty string | Missing button label |
[xRCS] Button title must be 25 characters or less | Button label too long |
[xRCS] Button URL must be a non-empty string | Missing URL on a URL button |
[xRCS] Phone number must be a non-empty string | Missing number on a phone button |
[xRCS] Cards can have a maximum of 2 buttons | Too many buttons on a card |
[xRCS] Carousel must have at least 2 cards | Not enough cards |
[xRCS] Carousel can have a maximum of 10 cards | Too many cards |
[xRCS] Button types must be in the same order across all carousel cards | Inconsistent button order |
[xRCS] Content template must have at least one content type | Empty template |
[xRCS] Failed to create template: {message} | Twilio API error on create |
[xRCS] Failed to send message: {message} | Twilio API error on send |
[xRCS] Failed to get template '{contentSid}': {message} | Twilio API error on fetch |
[xRCS] Failed to list templates: {message} | Twilio API error on list |
[xRCS] Failed to delete template '{contentSid}': {message} | Twilio API error on delete |
[xRCS] Failed to get message status '{messageSid}': {message} | Twilio API error on status check |
Twilio API errors are wrapped in a new Error carrying only the original err.message — raw SDK error objects (which may contain request details) are never rethrown or logged.
Environment Variables
The plugin never reads process.env. The consumer sets these variables and passes the values into app.register(xRCS, { ... }) — this table documents that consumer convention, not plugin inputs:
| Variable | Passed as | Description |
|---|---|---|
TWILIO_ACCOUNT_SID | accountSid | Twilio account SID |
TWILIO_AUTH_TOKEN | authToken | Twilio auth token |
TWILIO_MESSAGING_SERVICE_SID | messagingServiceSid | Twilio messaging service SID |
How It Works
On registration the plugin validates options — failing fast on non-boolean active, non-string or empty credentials, or a half-provided credential pair — and initializes one Twilio client (reused for all calls; never per-request) when accountSid and authToken are present. fastify.xrcs is decorated with builder factories, constants, validation helpers, and API functions. Builder methods return this for chaining; .build() produces a plain object. API methods call the Twilio Content API to create/list/delete templates and the Messages API to send. sendCard and sendCarousel are convenience wrappers that call createTemplate then sendMessage in sequence.
AI Context
package: "@xenterprises/fastify-xrcs"
type: fastify-plugin
use-when: RCS rich cards and carousels via Twilio Content API — fluent builder pattern with optional builder-only mode (no credentials)
decorator: fastify.xrcs (card, carousel, template builders; createTemplate, sendMessage, sendCard, sendCarousel, getTemplate, listTemplates, deleteTemplate, getMessageStatus, validate)
env: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_MESSAGING_SERVICE_SID — consumer convention; the app reads these and passes values into register options (the plugin never reads process.env)
builder-only: omit accountSid/authToken to use builders/validators without API calls; empty-string credentials throw at registration
