X Enterprises

fastify-xrcs

Fastify plugin for building and sending RCS rich cards and carousels via Twilio's Content API, with a fluent builder pattern and optional builder-only mode.

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

NameTypeDefaultRequiredDescription
accountSidstringNoTwilio Account SID (starts with AC). Must be non-empty and provided together with authToken
authTokenstringNoTwilio Auth Token. Must be non-empty and provided together with accountSid
messagingServiceSidstringNoTwilio Messaging Service SID (starts with MG). Required at send time for sendMessage/sendCard/sendCarousel
activebooleantrueNoSet 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)

Builders (no credentials required)

  • card() — Fluent CardBuilder for RCS rich cards with title, body, media, and buttons.
  • carousel() — Fluent CarouselBuilder for multi-card carousels.
  • template(name?) — Fluent ContentTemplateBuilder for Twilio Content API payloads.

Validators

Constants

ExportValues
CONTENT_TYPESTEXT, MEDIA, CARD, CAROUSEL, QUICK_REPLY, CALL_TO_ACTION, LIST_PICKER
ACTION_TYPESQUICK_REPLY, URL, PHONE_NUMBER
MEDIA_HEIGHTSHORT, 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:

ErrorCause
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:

ErrorCause
[xRCS] Twilio credentials required to register templatescreateTemplate (or sendCard/sendCarousel) without credentials
[xRCS] Twilio credentials required to send messagessendMessage without credentials
[xRCS] Twilio credentials requiredRead APIs (getTemplate/listTemplates/deleteTemplate/getMessageStatus) without credentials
[xRCS] messagingServiceSid required to send messagesSending without messagingServiceSid
[xRCS] 'to' must be a non-empty stringMissing or invalid recipient
[xRCS] 'contentSid' must be a non-empty stringMissing template SID
[xRCS] 'messageSid' must be a non-empty stringMissing message SID
[xRCS] 'limit' must be a positive numberInvalid listTemplates limit
[xRCS] Card title must be 200 characters or lessTitle too long
[xRCS] Media URL must be a non-empty stringMissing media URL
[xRCS] Invalid media height '{height}'. Must be one of: short, medium, tallInvalid media height
[xRCS] Button title must be a non-empty stringMissing button label
[xRCS] Button title must be 25 characters or lessButton label too long
[xRCS] Button URL must be a non-empty stringMissing URL on a URL button
[xRCS] Phone number must be a non-empty stringMissing number on a phone button
[xRCS] Cards can have a maximum of 2 buttonsToo many buttons on a card
[xRCS] Carousel must have at least 2 cardsNot enough cards
[xRCS] Carousel can have a maximum of 10 cardsToo many cards
[xRCS] Button types must be in the same order across all carousel cardsInconsistent button order
[xRCS] Content template must have at least one content typeEmpty 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:

VariablePassed asDescription
TWILIO_ACCOUNT_SIDaccountSidTwilio account SID
TWILIO_AUTH_TOKENauthTokenTwilio auth token
TWILIO_MESSAGING_SERVICE_SIDmessagingServiceSidTwilio 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
Copyright © 2026