@xenterprises/fastify-xrcs
v1.3.0
Published
Fastify plugin for building and sending RCS rich cards and carousels via Twilio
Downloads
56
Readme
@xenterprises/fastify-xrcs
Fastify plugin for building and sending RCS rich cards and carousels via Twilio's Content API. It provides fluent builders that enforce Twilio's structural constraints (character limits, button counts, carousel rules) at build time, plus a thin API layer for registering content templates and sending messages through a Twilio Messaging Service. For teams sending rich RCS/WhatsApp content from a Fastify service.
Installation
npm install @xenterprises/fastify-xrcs fastify@5Minimal Example
import Fastify from "fastify";
import xRCS from "@xenterprises/fastify-xrcs";
const fastify = Fastify();
await fastify.register(xRCS, {
accountSid: "AC...", // your Twilio Account SID
authToken: "your-auth-token", // your Twilio Auth Token
messagingServiceSid: "MG...", // your Twilio Messaging Service SID
});
// Build and send a product card
const card = fastify.xrcs
.card()
.title("iPhone 15 Pro")
.body("Starting at $999")
.media("https://example.com/iphone.jpg")
.quickReply("Buy Now", "buy_iphone")
.urlButton("Learn More", "https://example.com/iphone");
await fastify.xrcs.sendCard("+15551234567", card);Configuration arrives only via register options — the plugin never reads process.env. Pass values from your own env handling (e.g. accountSid: process.env.TWILIO_ACCOUNT_SID).
Options
| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| active | boolean | true | No | Enable/disable the plugin. When false, registration is a no-op |
| 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 |
When accountSid and authToken are omitted the plugin runs in builder-only mode — you can construct and validate card/carousel payloads locally but the Twilio API methods throw. A typical consumer wires env vars itself:
await fastify.register(xRCS, {
accountSid: process.env.TWILIO_ACCOUNT_SID,
authToken: process.env.TWILIO_AUTH_TOKEN,
messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
});Decorators
All methods and builders are exposed on the single fastify.xrcs namespace.
Builder Factory Methods
| Method | Returns | Description |
|--------|---------|-------------|
| fastify.xrcs.card() | CardBuilder | Create a new card builder |
| fastify.xrcs.carousel() | CarouselBuilder | Create a new carousel builder |
| fastify.xrcs.template(name) | ContentTemplateBuilder | Create a new content template builder |
API Methods (require credentials)
| Method | Returns | Description |
|--------|---------|-------------|
| createTemplate(builder, register?) | Promise<object> | Create and optionally register a content template. Pass register=false to get the payload without calling Twilio |
| sendMessage(to, contentSid, vars?) | Promise<object> | Send a message using a registered template SID |
| sendCard(to, card, name?) | Promise<object> | Create a template from a card and send it in one step |
| sendCarousel(to, carousel, name?) | Promise<object> | Create a template from a carousel and send it in one step |
| getTemplate(contentSid) | Promise<object> | Fetch a template by SID |
| listTemplates(limit?) | Promise<object[]> | List all content templates (default limit: 50) |
| deleteTemplate(contentSid) | Promise<boolean> | Delete a content template |
| getMessageStatus(messageSid) | Promise<object> | Get delivery status for a sent message |
Validation Helpers
| Method | Returns | Description |
|--------|---------|-------------|
| validate.card(card) | { valid, errors } | Validate a card object against RCS constraints |
| validate.carousel(carousel) | { valid, errors } | Validate a carousel object against RCS constraints |
Configuration
| Property | Type | Description |
|----------|------|-------------|
| config.hasCredentials | boolean | Whether Twilio credentials are configured |
| config.accountSid | string\|null | Masked account SID (first 8 chars + ...) — never the full SID |
| config.messagingServiceSid | string\|null | Messaging Service SID |
Classes & Constants (namespace properties and named exports)
| Export | Description |
|--------|-------------|
| CardBuilder | Class for building RCS card structures |
| CarouselBuilder | Class for building RCS carousel structures |
| ContentTemplateBuilder | Class for building Twilio Content API payloads |
| CONTENT_TYPES | Enum of supported content types (TEXT, MEDIA, CARD, CAROUSEL, QUICK_REPLY, CALL_TO_ACTION, LIST_PICKER) |
| ACTION_TYPES | Enum of button action types (QUICK_REPLY, URL, PHONE_NUMBER) |
| MEDIA_HEIGHT | Enum of media height options (SHORT, MEDIUM, TALL) |
The package's default export is the Fastify plugin. 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).
Card Builder
const card = fastify.xrcs
.card()
.title("Product Name") // Max 200 chars
.body("Product description")
.media("https://example.com/image.jpg", "tall") // short | medium | tall
.quickReply("Buy", "action_buy") // Quick reply button (max 25 chars)
.urlButton("Details", "https://example.com/product") // URL button
.phoneButton("Call", "+15551234567"); // Phone button
const cardData = card.build(); // Max 2 buttons per cardCarousel Builder
const carousel = fastify.xrcs
.carousel()
.body("Choose a product") // RCS drops body text; WhatsApp shows it
.addCard(fastify.xrcs.card().title("Laptop").body("$999").quickReply("Buy", "buy_laptop"))
.addCard(fastify.xrcs.card().title("Tablet").body("$499").quickReply("Buy", "buy_tablet"));
await fastify.xrcs.sendCarousel("+15551234567", carousel);Constraints: 2–10 cards, button types must be in the same order across all cards.
Content Template Builder
Build complete Twilio Content API payloads for any content type:
// Text with variables
const template = fastify.xrcs
.template("Welcome")
.language("en")
.variables({ "1": "John" })
.text("Hello {{1}}, welcome!")
.build();
// Register with Twilio
const registered = await fastify.xrcs.createTemplate(template);
// Or get payload without registering
const payload = await fastify.xrcs.createTemplate(
fastify.xrcs.template("Preview").text("Hello"),
false
);Supported Content Types
| Type | Constant | RCS | WhatsApp |
|------|----------|-----|----------|
| Text | CONTENT_TYPES.TEXT | Yes | Yes |
| Media | CONTENT_TYPES.MEDIA | Yes | Yes |
| Card | CONTENT_TYPES.CARD | Yes | Yes |
| Carousel | CONTENT_TYPES.CAROUSEL | Yes | Yes |
| Quick Reply | CONTENT_TYPES.QUICK_REPLY | Yes | Yes |
| Call to Action | CONTENT_TYPES.CALL_TO_ACTION | No | Yes |
| List Picker | CONTENT_TYPES.LIST_PICKER | No | Yes |
Routes
None. This plugin adds no routes and no hooks — only the fastify.xrcs decorator.
Error Behavior
Registration fails fast with actionable messages that name the plugin, the option, and an example:
| Error | When |
|-------|------|
| 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. ... | Non-string accountSid option |
| xrcs: option `accountSid` must be a non-empty string, e.g. ... | Empty or whitespace-only accountSid option |
| xrcs: option `authToken` must be a string, e.g. ... | Non-string authToken option |
| xrcs: option `authToken` must be a non-empty string, e.g. ... | Empty or whitespace-only authToken option |
| xrcs: option `messagingServiceSid` must be a string, e.g. ... | Non-string messagingServiceSid option |
| xrcs: options `accountSid` and `authToken` must be provided together, e.g. ... | Only one of the pair is provided |
Runtime and builder errors are prefixed with [xRCS]:
| Error | When |
|-------|------|
| [xRCS] Twilio credentials required to register templates | Calling createTemplate without credentials |
| [xRCS] Twilio credentials required to send messages | Calling sendMessage without credentials |
| [xRCS] Twilio credentials required | Calling any read API without credentials |
| [xRCS] messagingServiceSid required to send messages | Sending without messagingServiceSid |
| [xRCS] 'to' must be a non-empty string | Empty/missing recipient |
| [xRCS] 'contentSid' must be a non-empty string | Empty/missing template SID |
| [xRCS] 'messageSid' must be a non-empty string | Empty/missing message SID |
| [xRCS] 'limit' must be a positive number | Invalid limit for listTemplates |
| [xRCS] Card title must be 200 characters or less | Title exceeds 200 chars |
| [xRCS] Button title must be 25 characters or less | Button label exceeds 25 chars |
| [xRCS] Cards can have a maximum of 2 buttons | More than 2 actions on a card |
| [xRCS] Carousel must have at least 2 cards | Fewer than 2 cards |
| [xRCS] Carousel can have a maximum of 10 cards | More than 10 cards |
| [xRCS] Button types must be in the same order across all carousel cards | Inconsistent action types |
| [xRCS] Content template must have at least one content type | Empty template |
| [xRCS] Failed to create template: ... | Twilio API error on create |
| [xRCS] Failed to send message: ... | Twilio API error on send |
| [xRCS] Failed to get template '...': ... | Twilio API error on fetch |
| [xRCS] Failed to list templates: ... | Twilio API error on list |
| [xRCS] Failed to delete template '...': ... | Twilio API error on delete |
| [xRCS] Failed to get message status '...': ... | 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.
How It Works
The plugin wraps Twilio's Content API with a fluent builder pattern and a thin Fastify integration layer:
- Registration — the plugin validates options, initialises one Twilio client (reused for all calls; never per-request), and decorates the Fastify instance with
fastify.xrcs. - Builders —
CardBuilder,CarouselBuilder, andContentTemplateBuilderare plain classes with no network dependencies. They enforce Twilio's structural constraints (character limits, button counts, carousel card counts, button-order consistency) at build time so you get immediate validation errors. - Template creation —
createTemplate()accepts a builder or a raw payload object. Withregister=true(default) it callstwilioClient.content.v1.contents.create()to register the template with Twilio and returns the SID. - Sending —
sendMessage()sends a message viatwilioClient.messages.create()using a registered template SID and the configured Messaging Service.sendCard()andsendCarousel()are convenience wrappers that create a template and send it in one call. - Builder-only mode — when credentials are omitted the plugin still registers all builders, constants, and validation helpers. Only the API methods throw.
Requirements
- Node.js >= 20
- Fastify >= 5 (peer dependency)
- Twilio account with RCS-capable Messaging Service (for API mode)
License
Proprietary — All Rights Reserved X Enterprises. See LICENSE.
