Frequently Asked Questions
Use the /send
endpoint of the Fastify API, specifying the campaign ID. The API integrates with Plivo's SMS platform to dispatch messages to opted-in subscribers. This setup handles subscriber management, consent, and reliable message delivery using Fastify's efficient framework and Plivo's communication platform.
Fastify is a high-performance Node.js web framework used to build the backend API service for the SMS marketing application. It was chosen for its speed, extensibility, and developer-friendly features such as built-in validation and logging, which enhance the performance and maintainability of the application.
Plivo is a cloud communications platform that offers robust SMS API capabilities. It's integrated into the project for its reliable message delivery, easy-to-use developer tools, and scalability to handle potentially large volumes of messages.
Pino-pretty is best used during development. It formats logs for improved readability in the console. For production environments, standard JSON logging is preferred for structured logging, which is easier to parse by monitoring systems.
While the tutorial uses PostgreSQL, you could potentially adapt the project to use a different database. You'd need to adjust the Prisma schema and connection string in the .env
file and might need to modify database interactions in the service layer if there are specific syntax differences.
First, create a Plivo account and obtain your Auth ID, Auth Token, and a Plivo phone number. Then, configure these credentials in the project's .env
file. The Plivo SDK is used to send SMS messages via their API.
Prisma acts as an Object-Relational Mapper (ORM), simplifying interactions with the PostgreSQL database. It allows developers to work with database records as JavaScript objects, abstracting away complex SQL queries, improving code readability, and type safety.
Docker is used for containerizing the Fastify application, ensuring consistent deployment across different environments. This containerization approach makes the deployment process more predictable and manageable, whether deploying to a local server, a cloud platform, or other containerized environments.
The tutorial recommends using the latest Long-Term Support (LTS) version of Node.js. LTS versions offer stability, security, and ongoing support, which are essential for production applications. Use node -v
to check your Node.js version.
The Fastify API exposes endpoints for subscriber management. POST /api/v1/subscribers
creates a new subscriber, GET /api/v1/subscribers
retrieves all subscribers, GET /api/v1/subscribers/:id
retrieves a specific subscriber by ID, PUT /api/v1/subscribers/:id/optin
updates the opt-in status, and DELETE /api/v1/subscribers/:id
deletes a subscriber.
The application follows a client-server architecture where a client (e.g., a web interface) interacts with the Fastify API, which in turn communicates with the Plivo API for sending SMS messages. A PostgreSQL database stores subscriber and campaign data, providing persistence for the application.
The project uses the libphonenumber-js
library for phone number validation and formatting. It ensures phone numbers are in the correct format (preferably E.164) and helps prevent issues with SMS delivery due to incorrect formatting.
Tracking consent is a legal requirement for SMS marketing in many jurisdictions (e.g., TCPA, GDPR). It allows you to send messages only to users who have explicitly agreed to receive them, promoting ethical and legal compliance.
Fly.io is suggested as a platform for deploying the full-stack application and database. While deployment details are not included in the provided article section, it is mentioned as a deployment option. It simplifies the deployment process.
Building Production-Ready SMS Marketing Campaigns with Fastify, Plivo & Node.js: Complete Developer Guide
This comprehensive guide walks you through building a production-ready SMS marketing campaign application using Fastify (70,000+ req/s performance), Plivo's messaging APIs, and modern Node.js best practices. You'll master everything from initial project setup to TCPA-compliant deployment, monitoring, and scaling your SMS infrastructure.
By the end of this tutorial, you'll have a functional backend service capable of managing subscriber lists with Mobile Number Portability (MNP) support, creating TCPA-compliant SMS marketing campaigns, and sending bulk messages via Plivo with production-grade security, error handling, and horizontal scalability.
Project Overview and Goals
What We're Building:
We are creating a backend API service using Fastify. This service will manage:
Problem Solved:
This application provides the core infrastructure needed for businesses to leverage SMS marketing effectively. It centralizes subscriber management, ensures consent is handled, and integrates with a reliable communication platform (Plivo) for message delivery.
Technologies Used:
System Architecture:
Prerequisites:
Final Outcome:
A containerized Fastify application exposing RESTful API endpoints for managing subscribers and campaigns, capable of sending SMS messages via Plivo. The application will include logging, basic error handling, security measures, and deployment configurations.
Important Legal Compliance Note:
SMS marketing in the United States is governed by the Telephone Consumer Protection Act (TCPA). As of 2024, key requirements include 4:
Additionally, 15 U.S. states have specific SMS marketing laws requiring express consent. This application implements opt-in/opt-out functionality, but you must ensure proper consent collection processes and compliance with all applicable regulations.
1. Setting up the Project
Let's initialize our Fastify project and set up the basic structure.
Create Project Directory:
Initialize Node.js Project:
Install Core Dependencies:
fastify
: The core framework.fastify-env
: For managing environment variables.fastify-sensible
: Adds useful decorators likehttpErrors
.fastify-cors
: Enables Cross-Origin Resource Sharing.pino-pretty
: Developer-friendly logger formatting (for development).@prisma/client
: Prisma's database client.plivo
: The official Plivo Node.js SDK (v4.74.0+ recommended).async-retry
: For adding retry logic to Plivo calls.libphonenumber-js
: For robust phone number validation/formatting.fastify-rate-limit
: For API rate limiting.@fastify/helmet
: For security headers.@fastify/sentry
,@sentry/node
,@sentry/tracing
: For error tracking with Sentry.bullmq
,ioredis
: (Optional) BullMQ is production-proven for background jobs, used by Microsoft, Vendure, and Datawrapper 5. Recommended for high-volume SMS campaigns requiring asynchronous processing with Redis-backed job queues.Install Development Dependencies:
prisma
: The Prisma CLI for migrations and generation.nodemon
: Automatically restarts the server during development.tap
: Fastify's recommended testing framework.Configure
package.json
Scripts: Update thescripts
section in yourpackage.json
:test
: Runs tests usingtap
.start
: Runs the application in production mode.dev
: Runs the application in development mode withnodemon
for auto-reloads,pino-pretty
for readable logs, and the Node inspector attached to127.0.0.1
(localhost only) for security.db:seed
: Runs the database seeding script.prisma:*
: Helper scripts for Prisma operations.Initialize Prisma:
This creates a
prisma
directory with aschema.prisma
file and a.env
file for your database connection string.Configure Environment Variables (
.env
): Update the generated.env
file. Add placeholders for Plivo credentials and other settings..env
file with real secrets to version control. Add.env
to your.gitignore
file.Project Structure: Create the following directory structure:
Basic Fastify App Setup (
src/app.js
):fastify-env
? It validates required environment variables on startup, preventing runtime errors due to missing configuration.fastify-sensible
? Provides standard HTTP error objects (fastify.httpErrors.notFound()
) and other utilities.fastify-autoload
? Simplifies loading plugins and routes from separate directories, keepingapp.js
clean.helmet
andrateLimit
added early.Server Entry Point (
src/server.js
):Add
.gitignore
: Create a.gitignore
file in the root directory:prisma/migrations/*.sql
: This line prevents generated SQL migration files from being committed. This is standard practice as theschema.prisma
file is the source of truth, and migrations are generated from it. If your team workflow requires committing the SQL files for review, you can remove this line.You now have a basic Fastify project structure ready for adding features. Run
npm run dev
to start the development server. You should see log output indicating the server is listening.2. Implementing Core Functionality
We'll start by defining our database schema and creating services for managing subscribers and campaigns.
Define Database Schema (
prisma/schema.prisma
):@unique
onphoneNumber
? Prevents duplicate subscriber entries.isOptedIn
? Crucial for tracking consent, a legal requirement under TCPA (USA) and GDPR (EU). As of 2024, TCPA violations carry penalties of $500-$1,500 per message with no maximum limit 4.@@index([isOptedIn])
? Added an index to potentially speed up queries filtering by opt-in status, which is common.Apply Database Migrations: Create the initial migration and apply it to your database.
Prisma will create the
Subscriber
andCampaign
tables in your PostgreSQL database.Create Prisma Plugin (
src/plugins/prisma.js
): This plugin initializes the Prisma client and makes it available throughout the Fastify application.fastify-plugin
? Ensures the plugin is loaded correctly and decorators (fastify.prisma
) are available globally.onClose
hook? Ensures the database connection is closed cleanly when the server shuts down.Create Subscriber Service (
src/services/subscriberService.js
): This service encapsulates the logic for interacting with theSubscriber
model, now usinglibphonenumber-js
.libphonenumber-js
for robust validation and formatting to E.164 standard.Create Campaign Service (
src/services/campaignService.js
): (No changes from original, logic remains the same)These services provide a clean abstraction layer over the database operations. We'll use them in our API routes next.
3. Building a Complete API Layer
Now, let's expose our services through Fastify routes with proper request validation.
Subscriber Routes (
src/routes/subscribers.js
):phoneNumber
at the API layer, relying on the service layer for E.164 validation and formatting. Addedformat
hints for CUID and E.164.Campaign Routes (
src/routes/campaigns.js
): (Schema and basic CRUD routes remain similar, focus on the/send
endpoint)References
Frequently Asked Questions
What is Fastify and why should I use it for SMS marketing campaigns?
Fastify is a high-performance Node.js web framework that delivers 70,000-80,000 requests per second in benchmarks, significantly outperforming Express (20,000-30,000 req/s). For SMS marketing applications requiring bulk message processing, webhook handling, and real-time subscriber management, Fastify's low overhead, built-in validation, and production reliability make it ideal for handling concurrent API requests efficiently.
How do I ensure TCPA compliance for SMS marketing in the United States?
TCPA compliance requires Prior Express Written Consent (PEWC) before sending marketing SMS, processing opt-out requests within 10 business days (updated from 30 days in 2024), sending messages only between 8 AM and 9 PM recipient's local time, and never texting numbers on the National Do Not Call (DNC) Registry. Violations carry penalties of $500-$1,500 per message with no maximum limit. This tutorial implements opt-in/opt-out functionality, but you must establish proper consent collection processes and comply with the 15 U.S. states with additional specific SMS marketing laws.
What is the difference between GSM-7 and UCS-2 SMS encoding?
GSM-7 encoding supports 160 characters per SMS message (153 characters per segment when concatenated), while UCS-2 encoding supports only 70 characters (67 per segment). Non-GSM characters like emojis, curly quotes, or special symbols trigger automatic fallback to UCS-2, reducing your message capacity by more than half. For cost-effective bulk SMS campaigns, validate message content to avoid unexpected UCS-2 encoding.
How do I validate international phone numbers for SMS marketing?
Use the
libphonenumber-js
library to validate and format phone numbers to E.164 international standard (e.g., +15551234567). This tutorial's SubscriberService includes_validateAndFormatNumber()
method that parses input numbers, validates them across 200+ countries, and consistently stores them in E.164 format for reliable Plivo API integration and duplicate prevention.What is the Plivo SDK and how does it integrate with Fastify?
Plivo's Node.js SDK (v4.74.0 as of October 2024) provides a JavaScript interface for Plivo's cloud communications APIs, enabling SMS sending, MMS, phone calls, and carrier lookup. This tutorial integrates Plivo SDK with Fastify through a dedicated PlivoService plugin that handles authentication, message sending with retry logic, webhook signature validation, and delivery status tracking via async-retry patterns.
Should I use BullMQ for SMS campaign job queues?
Yes, for production SMS campaigns requiring asynchronous bulk message processing, BullMQ provides Redis-backed job queues with exactly-once semantics, horizontal scaling, and concurrent job processing. BullMQ is production-proven (used by Microsoft, Vendure, and Datawrapper) and essential for handling high-volume SMS campaigns without blocking your Fastify API endpoints or risking message loss during server restarts.
How do I handle SMS delivery status callbacks from Plivo?
Implement a webhook endpoint (e.g.,
/webhooks/plivo/status
) that validates Plivo's signature using yourPLIVO_AUTH_TOKEN
, parses the delivery status payload, and updates your database with message states (queued, sent, delivered, failed, rejected). This tutorial includes webhook signature validation using the Plivo SDK'splivo.validate_signature()
method to prevent spoofed callbacks and ensure data integrity.What database should I use for SMS marketing subscriber management?
This tutorial uses PostgreSQL with Prisma ORM (v6.x, migrated from Rust to TypeScript for faster cold starts). PostgreSQL provides ACID compliance, robust indexing for phone number lookups, and production reliability. The schema includes
Subscriber
andCampaign
models with unique constraints on phone numbers (E.164 format), opt-in status tracking, and created/updated timestamps for TCPA compliance auditing.How do I deploy a Fastify SMS marketing application to production?
Containerize your application with Docker, configure environment variables for Plivo credentials and database connection strings, deploy to platforms like Fly.io or AWS ECS, and implement CI/CD pipelines using GitHub Actions. Essential production considerations include: enabling Helmet for security headers, configuring rate limiting (100 requests/minute default), setting up Sentry for error tracking, and using Redis-backed BullMQ for job queue persistence across deployments.
What are the SMS message length limits I need to know?
SMS messages are limited by character encoding: GSM-7 allows 160 characters per message (153 when concatenated into segments), while UCS-2 allows 70 characters (67 when concatenated). Messages exceeding these limits are automatically split into multiple segments, increasing costs proportionally. For optimal SMS marketing campaigns, keep messages under 160 GSM-7 characters or 70 UCS-2 characters, and avoid non-GSM characters (emojis, special symbols) unless necessary.
Summary
You now understand how to build production-ready SMS marketing campaigns using Fastify (70,000+ req/s), Plivo APIs (v4.74.0), and PostgreSQL with Prisma ORM (v6.x). You've learned to validate international phone numbers with
libphonenumber-js
, implement TCPA-compliant opt-in/opt-out functionality (10-day opt-out processing), handle SMS character encoding (GSM-7 vs. UCS-2), and process delivery status webhooks with signature validation.The architecture includes subscriber management with E.164 phone number formatting, campaign creation and bulk message sending, retry logic with
async-retry
, security headers via Helmet, rate limiting (100 req/min), and Sentry error tracking. You've configured Docker containerization, GitHub Actions CI/CD, and deployment to platforms like Fly.io with environment-based configuration management.Extend your implementation with:
Footnotes
Fastify Performance Benchmarks 2024-2025. Fastify v5 delivers 70,000-80,000 requests per second vs. Express 20,000-30,000 req/s. Source: Fastify Official Benchmarks and Express vs Fastify 2025 Performance Comparison. ↩
Plivo Node.js SDK. Latest version 4.74.0 published October 2024. Compatible with Node.js 5.5+. Source: Plivo NPM Package and Plivo Node.js SDK Documentation. ↩
Prisma ORM Version 6.x. Migrated from Rust to TypeScript for faster cold starts and improved developer experience. Version 6.16.3 published October 2025. Source: Prisma Official Website and Prisma GitHub Releases. ↩
TCPA SMS Marketing Compliance 2024. Requirements include Prior Express Written Consent (PEWC), opt-out processing within 10 business days (updated from 30 days), penalties of $500-$1,500 per violation, timing restrictions (8 AM - 9 PM), and DNC registry compliance. 15 U.S. states have additional specific requirements. Sources: TCPA Compliance Rule Changes 2024-2025 and ActiveProspect TCPA Text Messages Guide 2025. ↩ ↩2
BullMQ Production Readiness. Redis-based job queue used in production by Microsoft, Vendure, and Datawrapper. Provides exactly-once queue semantics, horizontal scaling, and concurrent job processing. Source: BullMQ Official Documentation and BullMQ Going to Production Guide. ↩
SMS Character Encoding Standards. GSM-7 encoding supports 160 characters (153 per segment when concatenated). UCS-2 encoding supports 70 characters (67 per segment). Non-GSM characters trigger automatic fallback to UCS-2, reducing capacity. Sources: Twilio SMS Character Limits and GSM 03.38 Wikipedia. ↩ ↩2