Frequently Asked Questions
Use the Vonage Messages API with the official Node.js SDK (@vonage/server-sdk). The vonage.messages.send method handles sending, supporting various message channels and providing robust delivery receipts. You'll need a Vonage API account, application, and rented number.
The Vonage Messages API is a versatile tool for sending and receiving messages across different channels, including SMS. It's used in this project for its reliable delivery tracking and webhook features, enabling real-time status updates and inbound message handling.
The Application ID and Private Key are required for authenticating with the Vonage Messages API and sending messages securely. This method is preferred over API Key/Secret for sending, although the latter might be needed for other Vonage API interactions or webhook signature verification.
Use ngrok during development to create a publicly accessible URL for your local server so Vonage can reach your webhook endpoints. Remember that ngrok URLs change frequently on the free tier, requiring updates in your Vonage Application settings.
While a simple in-memory store (like a Map) can be used for initial development, it's not suitable for production. Data will be lost on server restarts. Use a persistent database like PostgreSQL with Prisma for reliable data storage in a real-world application.
Set up a status webhook URL in your Vonage Application settings. Vonage will send POST requests to this endpoint with delivery updates. Use the client_ref parameter when sending messages to correlate statuses back to specific campaigns and contacts.
The client_ref is a custom identifier you can attach to outgoing messages. It's crucial for tracking message status and correlating webhooks back to your internal data. A good format is 'campaignId:contactId'.
Configure an inbound webhook URL in your Vonage Application settings. Implement logic in your webhook handler to process inbound SMS messages containing keywords like "STOP". Update the contact's status to "opted-out" to prevent future messages.
JWT (JSON Web Token) is a secure method for verifying the authenticity of webhooks. It ensures that incoming requests genuinely originate from Vonage, preventing unauthorized access or malicious activity. The specific Vonage JWT/signature check implementation depends on the current SDK guidance.
A job queue (like BullMQ with Redis) is essential for production SMS campaign sending. It handles asynchronous processing reliably, manages retries in case of failures, and enables proper concurrency control, unlike setImmediate which is unsafe and non-persistent
Vonage often has default rate limits (e.g., 1 message per second per long code). Implement delays between sending messages using setTimeout or similar methods. Adjust the delay based on your number type and account-specific limits. See Vonage documentation for details.
You'll need Node.js, npm (or yarn), a Vonage API account, a Vonage Application enabled for the Messages API with a Private Key, a rented Vonage Number, ngrok for local development, and basic JavaScript/Node.js knowledge, including async/await.
Create a new application in the Vonage dashboard, enable the "Messages" capability, configure Status and Inbound webhook URLs, generate and download the private key, note the Application ID, and link your Vonage Number to the application.
Prisma is an Object-Relational Mapper (ORM) that simplifies database interactions in Node.js. It makes it easier to work with PostgreSQL or other databases by providing a type-safe and convenient way to query and manage data.
Developer Guide: Building Node.js SMS Marketing Campaigns with Vonage
> ⚠️ CONTENT NOTE: This article's filename suggests MessageBird with Next.js and NextAuth, but the content actually covers Vonage (formerly Nexmo) with Express.js and basic API key authentication. If you're looking for MessageBird-specific or Next.js/NextAuth implementations, please refer to platform-specific documentation.
Learn how to build a production-ready SMS marketing campaign system using Node.js, Express.js, and the Vonage Messages API. This comprehensive guide covers everything from sending bulk SMS messages and tracking delivery status through webhooks to managing contact lists and handling opt-out compliance. You'll implement a complete backend application with database integration using Prisma and PostgreSQL, security best practices, and deployment-ready error handling for programmatic SMS marketing campaigns.
What You'll Build: SMS Marketing Campaign System Overview
Core Features:
This tutorial guides you through creating a backend system with these capabilities:
Real-World Use Cases:
This SMS marketing system solves common business needs for programmatic SMS communication:
Technology Stack:
@vonage/server-sdk(v3.24.1): Official Node.js SDK for Vonage API integrationdotenv: Secure environment variable managementngrok: Development tool for exposing local webhooks (includes 1 static domain since August 2023)System Architecture:
What You'll Learn:
By completing this tutorial_ you'll have a fully functional Node.js SMS marketing application with:
Prerequisites:
Before starting this tutorial_ ensure you have:
ngrok: Free tier with static domain (Download ngrok)Important: Create your Vonage API Account_ Application_ Private Key_ and rent a virtual number before starting Section 1 (Project Setup).
1. Setting up the Project
Let's create the project structure and install necessary dependencies.
Create Project Directory:
Initialize Node.js Project:
This creates a
package.jsonfile.Install Dependencies:
express: Web framework.@vonage/server-sdk: Vonage API client.dotenv: Loads environment variables.nodemon: Development utility to automatically restart the server on file changes.Install Database Dependencies (Optional - using Prisma & PostgreSQL):
If you plan to use a database (highly recommended for managing contacts and campaigns):
@prisma/client: The Prisma database client.prisma: Prisma CLI tool for migrations and generation.Initialize Prisma (Optional):
This creates a
prismadirectory with aschema.prismafile and a.envfile (if one doesn't exist).Configure
.envFile:Create a file named
.envin the project root. Prisma might have created one already. Add the following placeholders. We'll fill these in later.Important: Add
.envandprivate.keyto your.gitignorefile to avoid committing secrets.Create
private.keyPlaceholder:Create an empty file named
private.keyin the root. You will download the actual key from Vonage later and place its contents here. This file path method is used for setup; Section 12 discusses secure key handling for production.Project Structure:
Create the following directory structure:
src/controllers: Handle incoming HTTP requests_ validate input_ and call services.src/services: Contain the core business logic_ interact with external APIs (Vonage)_ and the database.src/routes: Define API endpoints and link them to controllers.src/middleware: Functions that run before controllers (e.g._ authentication_ logging).src/utils: Reusable utility functions.server.js: Main application entry point.private.key: Your Vonage private key file.Configure
package.jsonScripts:Add development scripts to your
package.json:Create Basic Server (
server.js):Run the Server (Development):
If everything is set up correctly, you should see
Server listening at http://localhost:3000. You can test the health check endpoint:curl http://localhost:3000/health.2. Implementing Core Functionality (Sending SMS)
Let's integrate the Vonage SDK to send messages.
Create Vonage Service (
src/services/vonageService.js):This service will encapsulate all interactions with the Vonage SDK.
MessagesAPI? We usevonage.messages.sendwhich corresponds to the more modern Messages API. It supports multiple channels and provides detailed status webhooks (/webhooks/status), which are crucial for tracking campaign delivery. The oldervonage.message.sendSmsuses the SMS API, which has different webhook formats and capabilities.try...catchblock captures errors during the API call. We log detailed errors and return a structured object indicating success or failure. We attempt to parse specific error details from the Vonage response if available.client_ref: This optional parameter is very useful. You can set it to a unique ID (e.g.,campaignId:contactId) to correlate status webhooks back to specific messages in your campaign.Test Sending (Manual Trigger):
Let's add a temporary route in
server.jsto test sending a single SMS. Remember to remove or secure this later.3. Building a Complete API Layer (Campaigns & Contacts)
We need API endpoints to manage contacts and initiate campaigns.
Simple In-Memory Store (or Database Service):
For simplicity initially, let's use an in-memory store. Replace this with database interactions (Section 6) for persistence.
Authentication Middleware (Simple API Key):
Protect our internal endpoints.
Contact Controller (
src/controllers/contactController.js):Campaign Controller (
src/controllers/campaignController.js):This controller will orchestrate fetching contacts and sending messages.
createAndSendCampaignfunction responds202 Acceptedimmediately after creating the campaign record. The actual sending logic inprocessCampaignSendingis triggered asynchronously usingsetImmediate. Crucially, for production, replacesetImmediatewith a robust background job queue (like BullMQ with Redis) to handle failures, retries, and concurrency properly.setTimeoutdelay is added between sending messages to comply with Vonage's default rate limits (often 1 message per second per long code number). AdjustdelayBetweenMessagesbased on your number type and account limits. See Section 11 for more on rate limits.clientReflikecampaignId:contactIdto uniquely identify each message sent. This is vital for correlating status webhooks later.pending,sending,completed,failed) and individual message results are stored.API Routes (
src/routes/contactRoutes.js,src/routes/campaignRoutes.js):Wire up Routes in
server.js:Uncomment and add the route wiring in
server.js:Testing API Endpoints:
Restart your server (
npm run dev). Usecurlor Postman:Add Contact:
Add a couple more contacts. Note their IDs (e.g.,
c1,c2).List Contacts:
Create & Send Campaign:
Check your server logs – you should see messages about processing the campaign and sending SMS. Check the target phones.
Get Campaign Status:
4. Integrating Vonage Webhooks for SMS Delivery Tracking
To track delivery status and handle opt-outs, we need to configure Vonage webhooks and create corresponding endpoints in our app.
Obtain Vonage Credentials & Configure Application:
.envfile (VONAGE_API_KEY,VONAGE_API_SECRET). While the Messages API primarily uses Application ID/Private Key for sending, the Key/Secret might be needed for other API interactions or webhook signature verification depending on the method chosen (though JWT is often preferred with Messages API webhooks).ngrokURLs here (e.g.,https://YOUR_NGROK_SUBDOMAIN.ngrok.io/webhooks/statusandhttps://YOUR_NGROK_SUBDOMAIN.ngrok.io/webhooks/inbound).private.keyfile. Place its contents into theprivate.keyfile in your project root. Do not commit this key..envfile (VONAGE_APPLICATION_ID)..env(VONAGE_NUMBER). Make sure this number is linked to the Vonage Application you created.Expose Local Server with
ngrok:If your server is running locally on port 3000:
ngrokwill provide a public HTTPS URL (e.g.,https://abcdef123456.ngrok.io). Use this base URL when configuring the webhook URLs in the Vonage dashboard. Remember to update the Vonage Application settings with the correctngrokURL each time you restartngrok, as the subdomain usually changes on the free tier.Create Webhook Controller (
src/controllers/webhookController.js):This handles incoming POST requests from Vonage.
client_refto link the status update back to the specific campaign and contact message result stored earlier. Updates the status (delivered,failed,rejected, etc.) in the data store.200 OKquickly, even if internal processing fails. This prevents Vonage from retrying the webhook unnecessarily. Errors are logged internally.Webhook Routes (
src/routes/webhookRoutes.js):Wire up Webhook Routes in
server.js:Make sure the webhook routes are correctly required and used in
server.js(as shown previously, uncomment the relevant lines). Ensure they are not placed behind therequireApiKeymiddleware if you applied it globally earlier.Testing Webhooks:
ngrokis running and the Vonage Application webhook URLs are updated.npm run dev)./api/campaigns).vonageService.webhookControlleras messages get delivered or fail. Check if the status is updated correctly in your (in-memory) data store by calling the/api/campaigns/:idendpoint again./api/contacts. The contact should now havestatus: 'opted-out'. Subsequent campaigns should skip this contact.Next Steps and Production Considerations
This completes the core setup for sending SMS and handling basic status and inbound messages. The next steps would involve replacing the in-memory store with a database, adding more robust error handling, implementing a proper job queue, and preparing for deployment.
Critical Production Requirements:
Related SMS Integration Tutorials:
For developers interested in other messaging platforms and frameworks: