Frequently Asked Questions
You need Node.js, npm (or yarn), access to a PostgreSQL database, a Redis instance, a Plivo account, and a basic understanding of Node.js, Express, REST APIs, and databases. ngrok is essential for local development and testing.
Utilize the Plivo Node.js SDK along with a job queue system like BullMQ and Redis. This allows asynchronous sending of bulk SMS messages directly from your Node.js backend, ensuring reliable and scalable delivery, especially for personalized campaigns. The provided example uses a job queue to handle sending in the background, improving responsiveness.
Express.js acts as the API layer, handling HTTP requests and responses. It routes incoming requests to appropriate controllers, which then interact with services for business logic and Plivo for SMS functionality. Express.js provides the framework for structuring your backend application and routes communication through clear entrypoints like controllers.
PostgreSQL is a relational database used to store subscriber data, campaign details, and message logs, offering structured data management. Prisma simplifies database interactions by providing a modern ORM (Object-Relational Mapper), making it easier to work with the database in Node.js and providing typesafe methods for database access.
ngrok is crucial during *development* to expose your local server to the internet so Plivo webhooks can reach it. Plivo needs a public URL for incoming messages and status updates, hence ngrok acts as the temporary tunnel during development. Remember to replace the ngrok URL with your production URL when deploying.
Yes, for supported countries outside the US and Canada, you can use an Alphanumeric Sender ID instead of a phone number. Configure this in your Plivo account under Messaging > Sender IDs. Alphanumeric Sender IDs enable message personalization and branding but are limited by country support, so US/Canada usage generally requires a phone number instead.
The `subscriberService.setOptOutStatus` function handles opt-outs by updating the `isActive` flag in the subscriber record. The system can process incoming "STOP" messages via webhooks and automatically opt-out users. Ensure E.164 format is used consistently for matching.
BullMQ is a powerful job queue system that handles the asynchronous sending of SMS messages using Redis. By offloading message sending to a queue, the API remains responsive and avoids blocking on potentially long-running SMS operations, especially during bulk campaigns.
The `plivoService.validateWebhookSignature` function validates incoming webhooks using Plivo's signature. This is *crucial* to prevent unauthorized requests. The function requires access to the *raw* (unparsed) request body. The code example shows how to configure this, but it relies on the `req.rawBody` being populated correctly by middleware *before* any JSON body parsing.
Start by creating a directory, initializing npm, and installing required packages like Express, Plivo SDK, Prisma, BullMQ, Redis, and dotenv. Then, initialize Prisma, configure environment variables in the .env file, and set up `nodemon` for development. The article provides detailed commands for this initial setup.
Create a Plivo Application in the Plivo console (Messaging > Applications > XML), set the Message URL to your webhook endpoint (use ngrok during development), and link your purchased Plivo number to this application. This enables Plivo to forward incoming messages to your backend.
A production webhook URL is essential because ngrok URLs are temporary. For your deployed application, you need a stable, publicly accessible URL (provided by your hosting platform) so Plivo can consistently reach your webhooks for incoming messages and status updates.
Plivo sends message status updates (e.g., sent, failed, delivered) to the Delivery Report URL you configured in your Plivo Application settings. Your application receives these updates via webhooks, and you can then update the status of `SentMessage` records for logging and monitoring.
The `SentMessage` model logs individual message attempts, associating them with a specific `Campaign` and `Subscriber`. It stores Plivo's `messageUuid`, delivery status, and timestamps, enabling detailed tracking and reporting. The article also recommends including the full callback payload for status updates from Plivo via `statusCallback`.
Build Production-Ready SMS Campaigns with Node.js, Express, and Plivo
This guide provides a step-by-step walkthrough for building a robust SMS marketing campaign application using Node.js, the Express framework, and the Plivo communication platform API. You'll learn how to set up the project, send bulk SMS messages, handle replies and opt-outs, manage subscribers, and deploy your application securely and reliably.
We'll build a system capable of managing subscriber lists, creating SMS campaigns, sending messages asynchronously via a job queue, handling incoming messages (like replies or STOP requests), and logging message statuses. This approach solves the challenge of sending personalized or bulk SMS messages reliably and scalably, directly from your application backend.
Technologies Used:
.env
file.System Architecture:
Prerequisites:
ngrok
installed for local development webhook testing.ngrok
's free tier URLs are temporary and not suitable for production.By the end of this guide_ you will have a functional backend application capable of managing and executing SMS campaigns_ ready for further enhancement and deployment.
1. Project Setup and Configuration
Let's initialize the Node.js project and install necessary dependencies.
Create Project Directory:
Initialize Node.js Project:
Install Dependencies:
Install Development Dependencies:
prisma
: For Prisma CLI commands (migrations_ generation).nodemon
: Automatically restarts the server during development.jest
_supertest
: For testing.Initialize Prisma:
This creates a
prisma
directory with aschema.prisma
file and a.env
file.Configure Environment Variables (
.env
): Open the.env
file created by Prisma and add the following variables. Remove any unnecessary quotes.DATABASE_URL
: Connection string for your PostgreSQL database. Replace placeholders with your actual credentials. Use quotes if your password or user contains special characters.PLIVO_AUTH_ID
_PLIVO_AUTH_TOKEN
: Your Plivo API credentials.PLIVO_SENDER_ID
: The Plivo phone number (in E.164 format_ e.g._+14155551212
) or Alphanumeric Sender ID you'll use to send messages. Numbers are required for US/Canada.REDIS_URL
: Connection string for your Redis instance.PORT
: Port your Express application will run on.API_KEY
: A simple secret key for basic API authentication (use more robust methods like JWT for production).BASE_URL
: The public-facing base URL of your application. Critical for webhook configuration. Use yourngrok
URL during development_ and your production URL otherwise.Add
nodemon
script topackage.json
: Update thescripts
section in yourpackage.json
:Create Project Structure: Organize your project for maintainability:
Create these directories.
Basic Server Setup (
src/server.js
):Basic App Setup (
src/app.js
):This initial setup provides a solid foundation with essential dependencies, environment configuration, and a basic project structure.
2. Integrating with Plivo
Now, let's configure Plivo and set up the necessary components to interact with its API.
Sign Up/Log In to Plivo:
""[Plivo Trial]""
prefix added to messages. Purchase credits to remove these limitations.Get API Credentials:
.env
file forPLIVO_AUTH_ID
andPLIVO_AUTH_TOKEN
. Keep these secret! Do not commit them to version control.Get a Plivo Phone Number:
+14155551212
) and add it to your.env
file asPLIVO_SENDER_ID
.Configure Plivo Application for Webhooks: Plivo uses webhooks to notify your application about incoming messages and delivery status updates. You need to create a Plivo Application and link your Plivo number to it.
ngrok
.ngrok
:ngrok http 3000
(or yourPORT
).ngrok
(e.g.,https://<unique_id>.ngrok.io
).https://<unique_id>.ngrok.io/webhooks/plivo/incoming
(we'll create this endpoint later)..env
BASE_URL
to this ngrok URL during development.POST
.https://<unique_id>.ngrok.io/webhooks/plivo/status
. Set method toPOST
.Create Plivo Service (
src/services/plivoService.js
): This service encapsulates interaction with the Plivo SDK..env
.sendSms
handles sending a single message, including optional status callback configuration.createReplyXml
generates the XML Plivo expects for automatic replies via webhooks.validateWebhookSignature
is crucial for securing your webhook endpoints. Implementation Note: This function requires the raw request body for validation to work correctly with POST requests. Ensure your middleware setup provides this (see Section 6). The URL construction usingBASE_URL + req.originalUrl
might also need adjustment if your application runs behind complex proxies.3. Database Schema and Data Layer
We'll use Prisma to define our database schema and interact with PostgreSQL.
Define Schema (
prisma/schema.prisma
): Update your schema file with models for Subscribers, Campaigns, and potentially Sent Messages for tracking.isActive
).messageUuid
, and tracking delivery status. AddedonDelete: Cascade
for referential integrity.Run Database Migration: Apply the schema changes to your database. Prisma creates SQL migration files.
Generate Prisma Client: Whenever you change your schema, regenerate the Prisma Client.
This updates the
@prisma/client
library with typesafe methods based on your schema.Database Client Configuration (
src/config/db.js
):This sets up a singleton Prisma client instance and includes basic query logging.
4. Implementing Core Functionality (API & Services)
Let's build the API endpoints and service logic for managing subscribers and campaigns, and for sending messages.
Structure:
src/routes/
): Define API endpoints and link them to controllers. Useexpress.Router
.src/controllers/
): Handle HTTP requests, perform validation, call services, and send responses.src/services/
): Contain the core business logic, interacting with the database (Prisma) and external services (Plivo).4.1. Subscriber Management
Subscriber Routes (
src/routes/subscriberRoutes.js
):Subscriber Service (
src/services/subscriberService.js
):Subscriber Controller (
src/controllers/subscriberController.js
):