Frequently Asked Questions
Use the Sinch SMS API with Node.js and Express to send SMS messages. This involves setting up an Express server, configuring the Sinch API, and implementing routes and controllers to handle message sending and delivery reports. A dedicated service like 'sinchService.js' helps encapsulate the API interaction logic.
The Sinch SMS API enables sending and receiving SMS messages globally within your Node.js applications. It provides a RESTful interface accessible using libraries like 'node-fetch', allowing developers to integrate SMS functionality into their projects.
Create a Node.js Express app with API endpoints for accepting campaign details (recipients, message content). Use environment variables to securely store your Sinch API credentials. Use a service to handle interaction with the Sinch API, and include error handling and logging.
Sinch uses callback URLs (webhooks) to deliver real-time updates on message delivery status. When you send a message, specify a callback URL in the API request. Sinch will POST delivery reports to this URL, including the status (Delivered, Failed) and other details. This is how you know whether your message was successfully sent and delivered.
Use ngrok during development to expose your local server and test Sinch webhooks. Ngrok provides a public HTTPS URL that Sinch can reach, essential for receiving delivery reports while your app is running locally.
The client_reference
is a unique identifier you generate to track individual SMS messages within your system. Include it in the API request when sending messages, and Sinch will return it in delivery reports, allowing you to correlate reports back to your internal message records.
Create a specific route in your Express app to receive POST requests from Sinch (e.g., '/api/webhooks/delivery-reports'). In the route handler, process the JSON payload, validate it, and update your database based on the delivery status and unique client_reference
.
Use npm or yarn to install the required packages. npm install express dotenv node-fetch
will install Express for the webserver, dotenv for handling environment variables, and node-fetch for making API requests.
You'll need Node.js and npm installed, a Sinch account with a Service Plan ID and API Token, a Sinch phone number, and optionally ngrok for webhook testing. Basic knowledge of Node.js, Express, and REST APIs is also helpful.
Create directories for routes, controllers, and services. Create files for app configuration, server setup, and route definitions. Use .env
to store sensitive information, and .gitignore
to exclude files from version control.
Dotenv loads environment variables from a .env
file into process.env
, making it easy to manage configuration values like API keys and other sensitive information. This helps secure your credentials and keeps them separate from your code.
Storing sensitive information like API keys directly in your code is a security risk. Environment variables, loaded with dotenv, provide a more secure way to configure your Sinch integration without exposing credentials.
Use a strong validation library for API requests, implement robust input sanitization, and avoid logging sensitive data in production. If available, implement webhook signature validation to ensure requests come from Sinch.
Yes, ngrok creates a secure tunnel that allows you to receive webhooks locally, enabling testing of delivery reports during development without deploying your application.
Building SMS Marketing Campaigns with Sinch, Node.js & Express (2025 Guide)
This guide provides a comprehensive walkthrough for building a robust SMS marketing campaign system using Node.js, Express, and the Sinch SMS API. We'll cover everything from initial project setup to sending messages, handling delivery reports, security considerations, and deployment.
By the end of this tutorial, you will have a functional Express application capable of accepting campaign details via an API endpoint, sending SMS messages to a list of recipients using Sinch, and handling delivery status updates via webhooks. This solves the common need for businesses to programmatically send targeted SMS communications for marketing or notification purposes.
Framework Compatibility Note (2025): This guide uses Express 5.x, which became the default on npm as of March 31, 2025. Express 5 requires Node.js 18 or higher and includes breaking changes such as improved async error handling and removal of legacy middleware. This guide is compatible with Node.js 22.x (current LTS, Active until October 2025) and Node.js 24.x (released May 2025).
Technologies Used:
.env
file intoprocess.env
. (Current version: 17.2.3)fetch
API to Node.js, used for making HTTP requests to the Sinch API. (Note: Node.js 18+ includes native fetch support; node-fetch is optional)System Architecture:
Prerequisites:
ngrok
installed globally (npm install ngrok -g
).Note on Native Fetch: Node.js 18 includes experimental native fetch support (stable in Node.js 21+). If using Node.js 18 or higher, you can use native
fetch()
instead of installingnode-fetch
. This guide usesnode-fetch
for broader compatibility, but native fetch is recommended for production use with modern Node.js versions.1. Project Setup
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal and create a new directory for the project.
Initialize npm: Initialize the project using npm. You can accept the defaults.
This creates a
package.json
file.Install Dependencies: Install Express for the web server,
dotenv
for environment variables, andnode-fetch
for making API requests.Alternative for Node.js 18+: If using Node.js 18 or higher, you can omit
node-fetch
and use nativefetch()
instead:Project Structure: Create a basic directory structure for organization.
src/
: Contains all source code.src/routes/
: Defines API endpoints.src/controllers/
: Handles request logic.src/services/
: Contains business logic, like interacting with Sinch.src/app.js
: Configures the Express application (middleware, routes).src/server.js
: Starts the HTTP server..env
: Stores sensitive configuration (API keys, etc.). Never commit this file..gitignore
: Specifies files/folders Git should ignore.Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.2. Sinch Configuration
Securely store your Sinch credentials and configuration using environment variables.
Edit
.env
file: Open the.env
file and add your Sinch details. Replace the placeholder values with your actual credentials.SINCH_SERVICE_PLAN_ID
: Found on your Sinch Customer Dashboard (SMS -> APIs -> Select your API).SINCH_API_TOKEN
: Found on the same page as the Service Plan ID. Click ""Show"" to reveal it. Keep this secret.SINCH_BASE_URL
: The regional endpoint for the Sinch SMS API. Ensure this matches your account's region (e.g.,https://us.sms.api.sinch.com
,https://eu.sms.api.sinch.com
).SINCH_NUMBER
: The virtual phone number you acquired from Sinch, in E.164 format (e.g.,+12025550142
). This will be the sender ID for your messages.PORT
: The port your Express server will listen on.BASE_URL
: The public base URL where your application is accessible. This is crucial for constructing the webhookcallback_url
. Use yourngrok
HTTPS URL during development/testing, and your actual domain name in production.Load Environment Variables: Configure
dotenv
at the very beginning of your application entry point (src/server.js
) to load these variables.3. Implementing Core Functionality (Sinch Service)
Create a dedicated service to handle communication with the Sinch API. This encapsulates the logic and makes it reusable.
Edit
src/services/sinchService.js
: Implement the function to send SMS messages.from
: Your Sinch number.to
: An array of recipient E.164 phone numbers.body
: The message content.delivery_report
: Set to"full"
to receive detailed status updates via webhook.callback_url
: The URL Sinch will POST delivery reports to (we'll define this route later).client_reference
: (Optional) A unique ID you generate (e.g., UUID, database message ID) to correlate delivery reports back to your internal records. Useful for reliable lookups in the webhook handler.node-fetch
to make a POST request with the correct headers (including theAuthorization: Bearer
token).Enable ES Modules: Since we are using
import/export
syntax, add"type": "module"
to yourpackage.json
:(Ensure your Node.js version supports ES Modules and native fetch. Node.js 18+ recommended, Node.js 22+ for full LTS support).
4. Building the API Layer (Express)
Set up the Express server and define the API endpoints for sending campaigns and receiving webhooks.
Configure Express App (
src/app.js
): Set up middleware and routes.express.json()
andexpress.urlencoded()
to parse incoming request bodies./healthz
endpoint for monitoring./api/campaigns
and/api/webhooks
.Create Server Entry Point (
src/server.js
): Load environment variables and start the server.dotenv.config()
is called first.app
fromapp.js
.PORT
.BASE_URL
.Define Campaign Routes (
src/routes/campaignRoutes.js
): Create the endpoint for sending campaigns.Implement Campaign Controller (
src/controllers/campaignController.js
): Handle the logic for the/send
endpoint.recipients
andmessage
from the request body.callbackUrl
dynamically using theBASE_URL
environment variable. Warns ifBASE_URL
is not set. Production Note: For production environments,ngrok
is unsuitable. You'll need a publicly accessible server with a static IP or domain name. YourBASE_URL
environment variable should then be set to this public URL (e.g.,https://yourapp.yourdomain.com
) so Sinch can reach your webhook.client_reference
usingcrypto.randomUUID()
. Crucially, this should be stored in your database before callingsendSms
so you can look it up when the webhook arrives.sinchService.sendSms
function, passing thecallbackUrl
andclientReference
.202 Accepted
status, indicating the request is processing, along with thebatch_id
andclient_reference
.// TODO:
comments indicating where database interactions would typically occur.next(error)
to delegate error handling.Test Sending:
npm start
(ornpm run dev
if using Node >= 18).curl
or Postman to send a POST request:(Replace
+1RECIPIENT_PHONE_NUMBER
with a valid test number in E.164 format).You should see logs in your terminal and receive an SMS on the recipient phone. The response should look similar to:
5. Integrating Third-Party Services (Sinch Webhooks)
Configure and handle incoming delivery reports from Sinch.
Define Webhook Routes (
src/routes/webhookRoutes.js
):Implement Webhook Controller (
src/controllers/webhookController.js
):client_reference
orbatch_id
, status updates, error handling within the handler).batch_id
,status
,code
,recipient
, andclient_reference
.client_reference
for reliability.200 OK
response back to Sinch quickly to acknowledge receipt and prevent unnecessary retries. Discusses how to handle internal processing errors gracefully.Expose Localhost with
ngrok
(Development/Testing Only):npm run dev
), open another terminal window.https
URL (e.g.,https://abcd-1234-5678.ngrok.io
).ngrok
is for development and testing only. For production, you need a publicly hosted server with a stable URL.Set
BASE_URL
Environment Variable:.env
file and set theBASE_URL
to thengrok
HTTPS URL you copied.npm run dev
ornpm start
). It should now log the correctBASE_URL
.Configure Callback URL in Sinch Dashboard:
BASE_URL
plus the route path:https://YOUR_NGROK_HTTPS_URL/api/webhooks/delivery-reports
(e.g.,https://abcd-1234-5678.ngrok.io/api/webhooks/delivery-reports
)Test Webhook:
BASE_URL
set) andngrok
are running.curl
command from Step 4.5. Make sure the server logs show it's using the correctngrok
basedcallbackUrl
.'--- Received Sinch Delivery Report ---'
log message followed by the JSON payload from Sinch indicating the message status (Delivered
,Failed
, etc.) and including theclient_reference
you sent.6. Database Schema and Data Layer (Conceptual)
While this guide uses in-memory processing, a production system requires persistent storage.
Conceptual Schema (e.g., PostgreSQL):
Implementation Considerations:
client_reference
before calling the Sinch API to ensure you can correlate webhook delivery reports.client_reference
column for fast lookups when processing webhooks.status
,campaign_id
, andsent_at
for query performance.Frequently Asked Questions (FAQ)
What Node.js version do I need for Express 5 and this tutorial?
You need Node.js 18 or higher to use Express 5.x, which became the default on npm as of March 31, 2025. We recommend using Node.js 22.x (current LTS, Active until October 2025) or Node.js 24.x (released May 2025) for production applications. Express 5 includes breaking changes such as improved async error handling and requires the removal of legacy middleware patterns.
How many recipients can I send to in a single Sinch SMS API request?
As of 2024, Sinch supports a maximum of 1000 recipients per request. This limit was increased from the previous 100-recipient cap. If you need to send to more than 1000 recipients, implement batching logic to split your recipient list into groups of 1000 or fewer and process each batch sequentially with appropriate delays to respect rate limits.
Should I use node-fetch or native fetch() in Node.js?
If you're using Node.js 18 or higher, use the native
fetch()
function, which is built into Node.js (experimental in v18, stable in v21+). Native fetch provides better performance and eliminates an external dependency. Only usenode-fetch
if you need to support older Node.js versions or require specific features not available in the native implementation.How do Sinch delivery reports work with webhooks?
Sinch sends HTTP POST requests to your configured
callback_url
when message status changes occur (delivered, failed, etc.). Your webhook endpoint must return a200 OK
response quickly to acknowledge receipt. Use theclient_reference
field (a UUID you generate before sending) to correlate delivery reports with your database records. Sinch may retry webhook delivery if your endpoint returns an error or times out.What are the rate limits for Sinch SMS API?
Rate limits are plan-specific and set the maximum messages per second. Status queries have a limit of 1 request per second per IP address, with a maximum of 700 requests per second per IP for all endpoints. Each recipient in a batch counts as one message for rate limiting purposes. Contact your Sinch account manager to adjust rate limits for your specific use case.
How do I handle STOP/unsubscribe requests for SMS marketing?
Implement an inbound SMS webhook handler to process messages containing keywords like STOP, UNSUBSCRIBE, or END. When you receive these messages, immediately add the sender's number to your suppression list in your
recipients
table (setis_opted_out = TRUE
) and send a confirmation message. Always filter opted-out numbers from your recipient lists before sending campaigns to maintain compliance with telecommunications regulations and avoid penalties.Can I use this code with Next.js or NextAuth instead of Express?
This tutorial uses Express.js for the API server. To integrate with Next.js, create API routes in the
pages/api
orapp/api
directory and adapt the controller logic to Next.js API route handlers. For NextAuth integration, you can add authentication middleware to protect your campaign endpoints and associate campaigns with authenticated users. The Sinch service layer code remains largely the same regardless of the framework.What's the difference between batch_id and client_reference in Sinch?
The
batch_id
is generated by Sinch and returned when you send a message batch – it identifies the batch in Sinch's system. Theclient_reference
is a unique identifier you generate (typically a UUID) before sending, allowing you to correlate Sinch's delivery reports with your internal database records. Always useclient_reference
for reliable lookups in webhook handlers, as it's under your control and can be stored before the API call.How do I test webhooks locally during development?
Use ngrok to expose your local development server to the internet. Run
ngrok http 3000
to get a public HTTPS URL, set this as yourBASE_URL
environment variable, and configure the full webhook endpoint URL in your Sinch dashboard (e.g.,https://your-ngrok-id.ngrok.io/api/webhooks/delivery-reports
). Remember that ngrok URLs change each time you restart, so update your.env
file and Sinch dashboard configuration accordingly. Never use ngrok in production – deploy to a server with a stable public URL.What security measures should I implement for production SMS campaigns?
Essential security measures include: API key authentication or JWT tokens for campaign endpoints, input validation using libraries like
express-validator
for phone numbers and message content, rate limiting withexpress-rate-limit
to prevent abuse, HTTPS-only communication (TLS), webhook signature verification if supported by Sinch, secure storage of API credentials using environment variables or secrets managers (AWS Secrets Manager, HashiCorp Vault), IP allowlisting for webhook endpoints, and proper error handling that doesn't expose sensitive information in responses.