How to Send SMS with Sinch API in Node.js Express (2025 Guide)
Learn how to send SMS messages using Sinch REST API with Node.js and Express. Step-by-step tutorial covering authentication, API integration, error handling, and production best practices.
Build a production-ready Node.js application that sends SMS messages via the Sinch SMS REST API using Express. This comprehensive tutorial covers everything from initial setup and credential management to error handling and deployment strategies.
Last Updated: January 2025
What You'll Learn: Building a Sinch SMS API Integration
What You'll Build:
Create a minimal but functional Express API with a single endpoint (/send-sms). This endpoint accepts POST requests containing a recipient phone number and message body, then uses the Sinch SMS API to send the message.
Common Use Cases for SMS Integration:
This guide enables developers to quickly integrate SMS sending capabilities into their Node.js applications for various purposes, such as:
User notifications and alerts
Appointment reminders
Two-factor authentication (basic trigger, not full verification flow)
Marketing messages (ensure compliance with regulations)
Technologies Used:
Node.js: A JavaScript runtime environment for building server-side applications.
Express: A minimal and flexible Node.js web application framework used to create the API endpoint.
Sinch SMS REST API: The third-party service used to dispatch the SMS messages. We'll use its simple /batches endpoint.
axios: A promise-based HTTP client for Node.js to make requests to the Sinch API.
dotenv: A module to load environment variables from a .env file, keeping sensitive credentials secure.
Note on Implementation Approach: This guide demonstrates direct REST API calls using axios. Sinch also provides an official Node.js SDK (@sinch/sdk-core v1.2.1+) that simplifies authentication and API interactions. For production applications, consider using the SDK for better type safety and maintenance. This tutorial focuses on the REST API approach to provide deeper understanding of the underlying HTTP mechanics.
Basic Terminal/Command Line Knowledge: Familiarity with navigating directories and running commands.
Text Editor or IDE: Such as VS Code, Sublime Text, or WebStorm.
Final Outcome:
At the end of this guide, you'll have a running Node.js Express server that accepts API requests to send SMS messages using your Sinch account credentials.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory:
Open your terminal or command prompt and create a new directory for the project, then navigate into it.
Bash
mkdir sinch-sms-sender
cd sinch-sms-sender
Initialize Node.js Project:
Initialize the project using npm. The -y flag accepts the default settings.
Bash
npm init -y
This creates a package.json file in your directory.
Install Dependencies:
Install Express for the server, axios for making HTTP requests, and dotenv for environment variable management.
Bash
npm install express axios dotenv
(Note: We will install express-rate-limit later when it's introduced in Section 7.)
Project Structure:
Create the necessary files. Your basic structure should look like this:
.env: Stores sensitive credentials (API keys, phone numbers). Crucially, add .env to your .gitignore file later to prevent committing secrets.
index.js: Contains our Express application logic.
package.json: Defines project metadata and dependencies.
Create .gitignore:
Create a file named .gitignore in the root of your project and add the following lines to prevent committing sensitive information and dependency folders:
Navigate to SMS API Settings: In the left-hand menu, go to SMS -> APIs.
Retrieve SERVICE_PLAN_ID and API_TOKEN:
Under the REST API section, you will find your Service plan ID. Copy this value.
You will also see an API token. Click Show to reveal it, then copy the value. Treat this token like a password – keep it secure.
Retrieve Your Sinch Number:
You need a virtual phone number associated with your Service Plan ID to send messages from.
If you don't have one, you may need to acquire one through the Sinch dashboard (Numbers section, ensure it's configured for SMS).
To find numbers associated with your plan, click on your Service plan ID link on the SMS -> APIs page. Scroll down to find associated numbers or details on how to assign one. Copy the number in E.164 format (e.g., +12025550184).
Determine Your Region:
Sinch operates in different regions (e.g., US, EU, AU, CA, BR). The API endpoint URL depends on your account's region.
Check your dashboard or account settings. Common base URLs are:
US (United States):https://us.sms.api.sinch.com
EU (Europe):https://eu.sms.api.sinch.com
AU (Australia):https://au.sms.api.sinch.com
CA (Canada):https://ca.sms.api.sinch.com
BR (Brazil):https://br.sms.api.sinch.com
Note the correct base URL for your region. The API endpoint we'll use is /xms/v1/{SERVICE_PLAN_ID}/batches.
Important: Using the wrong regional endpoint will result in authentication or routing errors. Verify your region in the Sinch Dashboard under SMS → APIs.
Configure Environment Variables:
Open the .env file you created earlier and add your Sinch credentials and configuration. Replace the placeholder values with your actual details.
.env
# .env
# Sinch Credentials
SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID_HERE
SINCH_API_TOKEN=YOUR_API_TOKEN_HERE
SINCH_NUMBER=+YOUR_SINCH_PHONE_NUMBER_HERE # Use E.164 format (e.g., +12025550184)
# Sinch API Configuration
SINCH_REGION_BASE_URL=https://us.sms.api.sinch.com # Adjust if your region is different (e.g., eu)
# Application Port
PORT=3000
SINCH_SERVICE_PLAN_ID: Your service plan ID from the dashboard.
SINCH_API_TOKEN: Your API token from the dashboard.
SINCH_NUMBER: The Sinch virtual number you'll send messages from.
SINCH_REGION_BASE_URL: The base URL for your account's region.
PORT: The port your Express server will listen on.
3. Building the Express Server with SMS Functionality
Now, let's set up the basic Express server structure in index.js.
JavaScript
// index.jsrequire('dotenv').config(); // Load environment variables from .env file firstconst express = require('express');
const axios = require('axios');
// --- Environment Variable Validation (Basic) ---// Ensure essential Sinch variables are loadedif (!process.env.SINCH_SERVICE_PLAN_ID || !process.env.SINCH_API_TOKEN || !process.env.SINCH_NUMBER || !process.env.SINCH_REGION_BASE_URL) {
// Use single quotes for the error message string for consistencyconsole.error('FATAL ERROR: Missing required Sinch environment variables. Check your .env file.');
process.exit(1); // Exit if essential config is missing
}
constSERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID;
constAPI_TOKEN = process.env.SINCH_API_TOKEN;
constSINCH_NUMBER = process.env.SINCH_NUMBER;
constSINCH_API_URL = `${process.env.SINCH_REGION_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`;
constPORT = process.env.PORT || 3000; // Default to 3000 if not set// --- Express App Setup ---const app = express();
app.use(express.json()); // Middleware to parse JSON request bodies// --- Health Check Endpoint ---
app.get('/', (req, res) => {
res.status(200).json({ status: 'OK', message: 'Sinch SMS Sender API is running.' });
});
// --- SMS Sending Endpoint (To be implemented next) ---// app.post('/send-sms', ...)// --- Start Server ---// Assign the server instance returned by app.listen to a variableconst server = app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
// --- Graceful Shutdown (Optional but Recommended) ---
process.on('SIGTERM', () => {
console.info('SIGTERM signal received: closing HTTP server');
// Now 'server.close()' works correctly
server.close(() => {
console.log('HTTP server closed');
// Add any other cleanup logic here before exiting
process.exit(0);
});
});
process.on('SIGINT', () => { // Handle Ctrl+C gracefully as wellconsole.info('SIGINT signal received: closing HTTP server');
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
});
Explanation:
require('dotenv').config();: Loads variables from the .env file into process.env. This must be called early.
Validation: We perform a basic check to ensure critical environment variables are present. In a production app, more robust validation (e.g., using Joi or Zod) is recommended.
Constants: We store the environment variables and construct the full Sinch API endpoint URL.
app = express();: Creates an Express application instance.
app.use(express.json());: Enables the server to automatically parse incoming JSON request bodies, making req.body available.
Health Check: A simple / GET route confirms the server is running.
const server = app.listen(): Starts the server, makes it listen for connections on the specified PORT, and assigns the server instance to the server variable.
Graceful Shutdown: Basic handling for SIGTERM and SIGINT (Ctrl+C) signals is included, using the server variable to properly close connections before exiting.
You can run the server now to test the basic setup:
Bash
node index.js
You should see Server running on http://localhost:3000. Accessing http://localhost:3000 in your browser should show the health check JSON response. Press Ctrl+C to stop the server (which should now trigger the graceful shutdown log).
4. Implementing the SMS Sending Logic
Let's create the function responsible for interacting with the Sinch API.
Add the following function before the // --- Express App Setup --- section in index.js:
JavaScript
// index.js// ... (require statements and environment variable validation) ...constSERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID;
constAPI_TOKEN = process.env.SINCH_API_TOKEN;
constSINCH_NUMBER = process.env.SINCH_NUMBER;
constSINCH_API_URL = `${process.env.SINCH_REGION_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`;
constPORT = process.env.PORT || 3000;
// --- Sinch SMS Sending Function ---asyncfunctionsendSinchSms(recipientNumber, messageBody) {
console.log(`Attempting to send SMS to: ${recipientNumber}`); // Basic loggingconst payload = {
from: SINCH_NUMBER,
to: [recipientNumber], // API expects an array of recipientsbody: messageBody,
};
const config = {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
}
};
try {
const response = await axios.post(SINCH_API_URL, payload, config);
console.log('Sinch API Response Status:', response.status);
// Pretty print JSON for console readability; consider structured logging for productionconsole.log('Sinch API Response Data:', JSON.stringify(response.data, null, 2));
// Basic success check (Sinch usually returns 201 Created for successful batch submissions)if (response.status === 200 || response.status === 201) {
// The batch ID can be used for tracking if neededconst batchId = response.data?.id;
console.log(`SMS batch submitted successfully. Batch ID: ${batchId}`);
return { success: true, batchId: batchId, details: response.data };
} else {
// Handle unexpected success statuses if necessaryconsole.warn(`Unexpected success status from Sinch: ${response.status}`);
return { success: false, error: 'Unexpected status from Sinch API', details: response.data };
}
} catch (error) {
console.error('Error sending SMS via Sinch:', error.message);
// Log more detailed error info if available (e.g., response from Sinch)if (error.response) {
console.error('Sinch Error Response Status:', error.response.status);
// Pretty print JSON for console readability; consider structured logging for productionconsole.error('Sinch Error Response Data:', JSON.stringify(error.response.data, null, 2));
// Extract specific Sinch error details if possibleconst sinchError = error.response.data?.request_error?.service_exception?.text || 'Unknown Sinch error';
return { success: false, error: `Sinch API Error: ${sinchError}`, status: error.response.status, details: error.response.data };
} elseif (error.request) {
// Request was made but no response receivedconsole.error('Sinch Error Request:', error.request);
return { success: false, error: 'No response received from Sinch API' };
} else {
// Something else happened in setting up the requestreturn { success: false, error: `Client setup error: ${error.message}` };
}
}
}
// --- Express App Setup ---// ... (rest of the code from section 3) ...
Explanation:
async function sendSinchSms: Defines an asynchronous function to handle the SMS sending logic, making it easier to use await with the axios promise.
payload: Constructs the JSON body required by the Sinch /batches endpoint.
from: Your Sinch virtual number (loaded from .env).
to: An array containing the recipient's phone number(s) in E.164 format.
body: The text message content.
config: Defines the request configuration for axios.
headers: Sets the necessary Authorization header using the Bearer scheme with your API_TOKEN and the Content-Type header.
axios.post: Sends the POST request to the SINCH_API_URL with the payload and config.
Success Handling: Checks if the response status is 200 or 201 (Created), logs success, and returns a success object containing the batchId from the response.
Error Handling (catch block):
Logs the error message.
Checks if the error includes a response from Sinch (error.response). If so, logs the status and data from Sinch's error response, providing more specific details. It attempts to extract a meaningful error message from the nested Sinch response structure.
Checks for request errors where no response was received (error.request).
(Note on Logging: JSON.stringify(..., null, 2) is used for readable console output during development. For production, consider structured logging, e.g., plain JSON objects, for better machine parsing.)
5. Creating the /send-sms API Endpoint
Now, let's create the Express endpoint that will use the sendSinchSms function.
Add the following route definition inside index.js, replacing the placeholder comment // --- SMS Sending Endpoint (To be implemented next) ---:
JavaScript
// index.js// ... (require statements, validation, constants, sendSinchSms function) ...// --- Express App Setup ---const app = express();
app.use(express.json());
// --- Health Check Endpoint ---
app.get('/', (req, res) => {
res.status(200).json({ status: 'OK', message: 'Sinch SMS Sender API is running.' });
});
// --- SMS Sending Endpoint ---
app.post('/send-sms', async (req, res) => {
const { recipient, message } = req.body; // Destructure from request body// --- Basic Input Validation ---if (!recipient || !message) {
console.warn('Validation Error: Missing recipient or message in request body.');
return res.status(400).json({ success: false, error: 'Missing required fields: recipient, message' });
}
// Basic E.164 format check (very simple, consider a library for production)if (!/^\+\d{1,15}$/.test(recipient)) {
console.warn(`Validation Error: Invalid recipient format: ${recipient}`);
return res.status(400).json({ success: false, error: 'Invalid recipient phone number format. Use E.164 (e.g., +12025550184).' });
}
// --- Call the Sending Logic ---try {
const result = awaitsendSinchSms(recipient, message);
if (result.success) {
res.status(202).json({ // 202 Accepted: Request accepted, processing underwaysuccess: true,
message: 'SMS batch submitted successfully to Sinch.',
batchId: result.batchId,
// Avoid sending back full Sinch details unless needed by client// sinchDetails: result.details
});
} else {
// Determine appropriate status code based on Sinch error if possibleconst statusCode = result.status || 500; // Default to 500 if specific status unknownconsole.error(`Failed to send SMS: ${result.error}`);
res.status(statusCode).json({
success: false,
error: result.error,
// Optionally include Sinch error details during development/debugging// sinchDetails: result.details
});
}
} catch (internalError) {
// Catch unexpected errors within the endpoint handler itselfconsole.error('Internal Server Error in /send-sms handler:', internalError);
res.status(500).json({ success: false, error: 'Internal Server Error' });
}
});
// --- Start Server ---// ... (server assignment, app.listen and graceful shutdown) ...
Explanation:
app.post('/send-sms', ...): Defines a route that listens for POST requests on the /send-sms path.
const { recipient, message } = req.body;: Extracts the recipient phone number and the message text from the JSON payload sent in the request body.
Input Validation:
Checks if both recipient and message are provided. Returns a 400 Bad Request error if not.
Performs a very basic regular expression check to see if the recipient looks like E.164 format (starts with +, followed by digits). For production, use a dedicated phone number parsing/validation library (like libphonenumber-js).
Call sendSinchSms: Calls the asynchronous function we created earlier, passing the validated recipient and message. Uses await to wait for the result.
Handle Result:
If result.success is true, sends a 202 Accepted response back to the client, indicating the request was received and passed to Sinch. Includes the batchId.
If result.success is false, logs the error and sends an appropriate error response (e.g., 500 Internal Server Error, or potentially a more specific code like 400 if the error from Sinch indicated a client-side problem like an invalid number format not caught by basic validation).
Internal Error Catch: A top-level try...catch within the route handler catches any unexpected errors during request processing.
Testing the Endpoint with curl:
Start the server:node index.js
Open another terminal and run the following curl command.
Important: Replace +1xxxxxxxxxx with a valid recipient phone number in E.164 format (your own mobile is fine for testing). Also, ensure the SINCH_NUMBER in your .env file is correctly set to your provisioned Sinch number (e.g., +YOUR_SINCH_NUMBER_HERE).
Bash
curl -X POST http://localhost:3000/send-sms \
-H "Content-Type: application/json" \
-d '{
"recipient": "+1xxxxxxxxxx",
"message": "Hello from Node.js and Sinch!"
}'
-X POST: Specifies the HTTP method.
http://localhost:3000/send-sms: The URL of your endpoint.
-H "Content-Type: application/json": Sets the request header. Note the correct quoting for the header value.
-d '{...}': Provides the JSON data payload. Note the correct quoting for JSON keys and string values.
(Note: The multi-line format with \ works in Unix-like shells like bash or zsh. For Windows Command Prompt, you might need to put the entire command on one line or handle line breaks differently.)
{"success":true,"message":"SMS batch submitted successfully to Sinch.","batchId":"01ARZ3NDEKTSV4RRFFQ69G5FAV"}
(Example Batch ID shown)
Expected Server Logs (Terminal running node index.js):
Text
Server running on http://localhost:3000
Attempting to send SMS to: +1xxxxxxxxxx
Sinch API Response Status: 201
Sinch API Response Data: {
"id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
"to": [
"+1xxxxxxxxxx"
],
"from": "+YOUR_SINCH_NUMBER_HERE",
"canceled": false,
"body": "Hello from Node.js and Sinch!",
"type": "mt_batch",
"created_at": "2023-10-26T10:00:00.123Z",
"modified_at": "2023-10-26T10:00:00.123Z",
"delivery_report": "none",
"send_at": null,
"expire_at": null,
"flash_message": false,
"feedback_enabled": false
}
SMS batch submitted successfully. Batch ID: 01ARZ3NDEKTSV4RRFFQ69G5FAV
(Example timestamps and Batch ID shown; from number should match your .env)
You should also receive the SMS on the recipient phone shortly after.
6. Error Handling and Logging Best Practices
We've implemented basic error handling, but let's refine it slightly.
Consistent Error Format: Our API endpoint and the sendSinchSms function return consistent { success: false, error: '...' } objects. This is good practice.
Logging: We are using console.log and console.error. For production, consider using a more robust logging library like winston or pino. These enable:
Different log levels (debug, info, warn, error).
Structured logging (JSON format for easier parsing by log aggregators).
Outputting logs to files or external services.
Sinch API Errors: The sendSinchSms function attempts to parse specific errors from the Sinch response (error.response.data). You can enhance this by checking specific error.response.status codes (e.g., 400 for bad request, 401/403 for auth issues, 429 for rate limiting) and returning more specific errors or potentially implementing retries for transient issues (like 5xx errors from Sinch, although retries are complex and not implemented here).
Example: Testing an Error Scenario
Stop the server (Ctrl+C).
Temporarily change the SINCH_API_TOKEN in your .env file to an invalid value (e.g., add INVALID at the end).
Restart the server: node index.js
Run the curl command again (using a valid recipient number).
Expected curl Response:
JSON
{"success":false,"error":"Sinch API Error: Invalid credentials or service plan ID"}
(Or similar message based on Sinch's exact response)
Expected Server Logs:
Text
Server running on http://localhost:3000
Attempting to send SMS to: +1xxxxxxxxxx
Error sending SMS via Sinch: Request failed with status code 401
Sinch Error Response Status: 401
Sinch Error Response Data: {
"request_error": {
"service_exception": {
"message_id": "AUTH1001",
"text": "Invalid credentials or service plan ID",
"variables": []
}
}
}
Failed to send SMS: Sinch API Error: Invalid credentials or service plan ID
(Example Sinch 401 Error shown)
Reminder: Don't forget to change the SINCH_API_TOKEN back to its correct value in your .env file after completing this test!
7. Security Best Practices for SMS APIs
While this is a basic guide, keep these security points in mind for real applications:
Credential Management:Never hardcode API keys, tokens, or phone numbers directly in your source code. Always use environment variables (.env locally, secure configuration management in deployment). Ensure .env is in your .gitignore.
Input Validation: We added basic validation for recipient and message. Robust validation is crucial to prevent injection attacks or malformed requests causing errors. Use libraries like joi, zod, or express-validator. Sanitize message content if necessary, depending on how it's used.
Rate Limiting: Protect your API endpoint from abuse (accidental or malicious) by implementing rate limiting. Libraries like express-rate-limit make this straightforward.
First, install the dependency:
Bash
npm install express-rate-limit
Then, add the rate limiting middleware in index.js:
JavaScript
// index.js// ... require statements ...const rateLimit = require('express-rate-limit'); // Require the package// ... environment variable validation, constants, sendSinchSms function ...const app = express();
app.use(express.json());
// Apply rate limiting to the SMS endpointconst smsLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutesmax: 100, // Limit each IP to 100 requests per windowMsmessage: 'Too many requests sent from this IP, please try again after 15 minutes',
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headerslegacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
app.use('/send-sms', smsLimiter); // Apply limiter specifically to the /send-sms route// ... Health Check Endpoint, SMS Sending Endpoint ...// ... Start Server, Graceful Shutdown ...
Authentication/Authorization: This example API is open. In a real application, you would secure the /send-sms endpoint so only authorized clients or users can trigger SMS sends. This typically involves API keys, JWT tokens, or session management.
HTTPS: Always use HTTPS in production to encrypt traffic between the client and your server. Deployment platforms often handle this via load balancers or reverse proxies.
8. Special Cases and International SMS
Consider these points for expansion or specific scenarios:
Character Encoding/Limits: Standard SMS messages have character limits (160 for GSM-7 encoding, 70 for UCS-2). Longer messages might be split into multiple segments or require concatenation support (Sinch handles some of this automatically). Be mindful of message length to manage costs and user experience. Check Sinch documentation for details on encoding and concatenation.
Internationalization: Ensure recipient numbers are always provided in E.164 format (+ followed by country code and number). Be aware of varying regulations, sender ID requirements, and potential filtering regarding SMS sending in different countries.
9. Performance Optimization Strategies
For applications sending a higher volume of messages:
Asynchronous Operations: Node.js is inherently non-blocking due to its event loop. Using async/await correctly, as shown in the sendSinchSms function and the endpoint handler, ensures the server remains responsive while waiting for the network request to Sinch to complete.
Queuing: For high-throughput scenarios, avoid calling the Sinch API directly within the HTTP request handler. Instead, push the message details (recipient, body) onto a message queue (e.g., RabbitMQ, Redis Queue, AWS SQS, Google Cloud Pub/Sub). Have separate worker processes consume messages from the queue and make the calls to the Sinch API. This decouples the API response time from the actual SMS sending process, improves fault tolerance (e.g., if Sinch is temporarily unavailable), and helps manage Sinch API rate limits more gracefully.
10. Monitoring & Observability
For production applications, implement monitoring:
Logging: As mentioned, use a structured logging library (winston, pino) and configure appropriate log levels and destinations (files, log aggregation services). Log key events like request received, validation success/failure, Sinch API call attempt, Sinch API response (success/error), and final response sent to the client. Include correlation IDs (e.g., the Sinch batchId) to trace requests.
Health Checks: Our / route is a basic liveness check. Consider adding a readiness check that verifies essential dependencies are available (e.g., perhaps by making a lightweight, non-sending call to Sinch if available, or checking database connectivity if used).
Metrics: Track key performance indicators (KPIs) for your service and the /send-sms endpoint:
Request count
Request latency (average, percentiles)
Error rates (total, per error type like 4xx, 5xx)
Sinch API call latency and error rates.
Use tools like Prometheus/Grafana or Datadog APM.
Error Tracking: Integrate an error tracking service (e.g., Sentry, Datadog Error Tracking, Rollbar) to capture, aggregate, and alert on runtime exceptions and unhandled errors in your Node.js application.
Sinch Dashboard: Regularly monitor your SMS delivery status, usage statistics, and billing information within the Sinch Customer Dashboard. Use the batchId logged by the application to correlate specific requests with Sinch's delivery reports.
11. Common Issues and Troubleshooting
Error 401/403 (Unauthorized/Forbidden):
Double-check SINCH_SERVICE_PLAN_ID and SINCH_API_TOKEN in your environment variables are correct and exactly match your dashboard values.
Ensure the Authorization: Bearer YOUR_API_TOKEN header is being correctly constructed and sent by axios.
Verify your Sinch account/plan is active and explicitly enabled for sending SMS messages via the REST API.
Confirm you are using the correct regional base URL (SINCH_REGION_BASE_URL) for your account.
Error 400 (Bad Request):
Check the recipient number format. It must be in E.164 format (+ followed by country code and number, with no spaces, dashes, or other characters).
Ensure the from number (SINCH_NUMBER in .env) is a valid Sinch virtual number associated with your service plan and is also correctly formatted in E.164.
Verify the request body structure sent to Sinch exactly matches their API specification: { "from": "...", "to": ["..."], "body": "..." }. Pay close attention to to being an array. Check that the Content-Type: application/json header is correctly set on the request to Sinch.
The message body might be empty, invalid (e.g., containing unsupported characters if not using appropriate encoding), or exceed length limits.
Examine the detailed error message provided within the Sinch error response (error.response.data in the server logs) for specific clues (e.g., INVALID_PARAMETER_VALUE, MISSING_PARAMETER).
SMS Not Received:
Verify the recipient number is absolutely correct and the device is active and capable of receiving SMS.
Check for potential carrier filtering or spam blocks on the recipient's network or device. This is often outside your direct control.
Use the batchId returned by the API and logged by your server to look up the specific message status and delivery reports in the Sinch Customer Dashboard. This is the most reliable way to diagnose delivery issues.
Ensure your Sinch account has sufficient balance or credit if using a paid plan. Trial accounts may have restrictions.
Rate Limits (Error 429): Sinch enforces API rate limits. If you send too many requests in a short period, you might receive 429 Too Many Requests errors from their API. Your own express-rate-limit middleware can also return 429 if the client exceeds the limits you set. Implement appropriate delays between requests or use a queuing system (see Section 9) for high-volume sending.
Costs: Sending SMS messages typically incurs costs based on destination country, message volume, and potentially message segments (for long messages). Monitor your usage and billing carefully in the Sinch dashboard.
12. Deployment and CI/CD (Conceptual)
Deploying this Node.js application typically involves these steps:
Choose a Hosting Provider: Select a platform suitable for Node.js applications, such as Heroku, Render, AWS (EC2, Elastic Beanstalk, Lambda, Fargate), Google Cloud (App Engine, Cloud Run, GKE), Azure (App Service, AKS), or others (DigitalOcean App Platform, Fly.io).
Configure Environment Variables: Securely configure the production environment variables (SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN, SINCH_NUMBER, SINCH_REGION_BASE_URL, PORT, NODE_ENV=production) within your chosen hosting platform's configuration management system. Crucially, do not commit your .env file or hardcode secrets in your repository.
Build Step (If Necessary): For this simple JavaScript application, a dedicated build step is likely not required. If using TypeScript or a bundler, you would add a build process (e.g., npm run build) to compile/bundle your code before deployment.
Run the Application: Configure the hosting platform to start your application using a command like npm start (if defined in package.json) or node index.js. Use a process manager like pm2 (if managing your own server/VM) or rely on the platform's built-in mechanisms (like Heroku dynos or container orchestration) to run the application reliably, monitor its health, and restart it if it crashes.
CI/CD Pipeline (Recommended): Automate the testing and deployment process using CI/CD tools like GitHub Actions, GitLab CI, Jenkins, CircleCI, etc. A typical pipeline would:
Trigger on code pushes/merges.
Install dependencies (npm ci).
Lint the code (npm run lint, if configured).
Run automated tests (unit, integration - npm test, if configured).
Build the application (if necessary).
Deploy the application artifact to a staging environment for further testing.
Deploy to the production environment (potentially after manual approval).
HTTPS: Ensure your deployment setup provides HTTPS termination. Most PaaS providers (Heroku, Render) and load balancers/reverse proxies (like Nginx or those provided by cloud platforms) handle SSL certificate management and terminate HTTPS connections, forwarding plain HTTP traffic to your Node.js application listening on its configured PORT.
13. Testing Your SMS Integration
Manual Verification Checklist:
Start the server locally (node index.js).
Send a valid POST request using curl or a tool like Postman to http://localhost:3000/send-sms with a correct recipient E.164 number and message body.
Verify a 202 Accepted success response (with success: true and a batchId) is received by the client.
Check the server console logs for messages indicating successful submission to Sinch, including the batchId.
Confirm the actual SMS message arrives on the recipient's mobile device.
Send requests with missing fields (e.g., no recipient or no message) and verify appropriate 400 Bad Request error responses are received.
Send a request with an invalid recipient number format (e.g., 12345) and verify a 400 Bad Request error response related to the format is received.
Temporarily invalidate credentials in the .env file (e.g., modify SINCH_API_TOKEN), restart the server, send a valid request, and verify an appropriate error response (likely 401 Unauthorized or similar, based on the logged Sinch error) is returned by the API. Remember to restore the correct credentials afterwards.
Automated Testing (Conceptual):
Unit Tests: Use a testing framework like Jest or Mocha/Chai to test individual functions in isolation. For sendSinchSms, mock the axios.post call to simulate successful and various error responses from the Sinch API without making actual network requests. Verify that the function handles responses correctly and returns the expected { success: ..., ... } object.
Integration Tests: Use a library like supertest to make HTTP requests to your running Express application (potentially in a test environment with mocked dependencies). Test the /send-sms endpoint:
Send valid requests and assert the correct status code (202) and response body structure.
Send invalid requests (missing fields, bad format) and assert the correct error status codes (400) and error messages.
Test the rate limiting middleware (if implemented) by sending multiple requests quickly.
Mock the sendSinchSms function within these tests to control its behavior and prevent actual calls to Sinch during testing.
End-to-End Tests (Use Sparingly): Tests that actually send an SMS via a dedicated test Sinch account and verify reception (e.g., using a Sinch test number or a dedicated test phone). These are more complex, potentially costly, and slower, so use them judiciously for critical paths.
Frequently Asked Questions About Sinch SMS API
How do I get my Sinch Service Plan ID and API Token?
Log in to the Sinch Dashboard and navigate to SMS → APIs. Your Service Plan ID appears in the SERVICE PLAN ID column. Click "Show" in the API TOKEN column to reveal your API token. Treat this token as a password and never commit it to version control.
Which Sinch regional endpoint should I use?
The regional endpoint depends on where your Sinch account is provisioned. Check your Sinch Dashboard under SMS → APIs to verify your region. Common endpoints include https://us.sms.api.sinch.com (United States), https://eu.sms.api.sinch.com (Europe), https://au.sms.api.sinch.com (Australia), https://ca.sms.api.sinch.com (Canada), and https://br.sms.api.sinch.com (Brazil). Using the wrong endpoint will cause authentication errors.
Should I use the Sinch Node.js SDK or the REST API directly?
The official Sinch Node.js SDK (@sinch/sdk-core v1.2.1+) provides better type safety, simplified authentication, and easier maintenance for production applications. This tutorial uses direct REST API calls with axios to demonstrate the underlying HTTP mechanics, which helps you understand how the API works. For production, consider the SDK for reduced boilerplate and built-in error handling.
What phone number format does Sinch require?
Sinch requires all phone numbers in E.164 format: a plus sign (+) followed by the country code and number with no spaces, dashes, or parentheses. For example: +12025550184 (US) or +442071234567 (UK). The basic regex /^\+\d{1,15}$/ validates this format, but use a library like libphonenumber-js for production validation.
How do I handle Sinch API errors in Node.js?
Sinch returns detailed error information in the response body. Check error.response.status for HTTP status codes (401 for auth issues, 400 for bad requests, 429 for rate limits) and error.response.data.request_error.service_exception.text for specific error messages. Log these details for debugging and return appropriate error responses to your API clients.
What is the Sinch SMS API rate limit?
Sinch enforces rate limits that vary by account type and region. Implement client-side rate limiting using express-rate-limit to protect your API. For high-volume applications, use a message queue (RabbitMQ, Redis, AWS SQS) to decouple API requests from Sinch API calls and manage throughput more effectively.
How much does sending SMS with Sinch cost?
SMS costs vary by destination country, message segments (160 characters for GSM-7 encoding, 70 for Unicode), and your Sinch plan. Monitor usage and billing in the Sinch Dashboard. Longer messages may be split into multiple segments, increasing costs. Check Sinch's pricing page for current rates by country.
Can I send SMS to international numbers with Sinch?
Yes, Sinch supports international SMS delivery to most countries. Ensure recipient numbers are in E.164 format with the correct country code. Be aware of country-specific regulations, potential carrier filtering, and varying delivery rates. Some countries require sender ID registration or have restrictions on commercial messaging.
This guide provides a step-by-step process to send SMS messages using Node.js with the Express framework and the Sinch SMS REST API. It covers setting up a project, acquiring Sinch credentials, building an API endpoint, handling errors, and implementing security best practices like rate limiting.
The Sinch SMS REST API is a third-party service used to send SMS messages programmatically. This guide uses Sinch's /batches endpoint to send messages and provides instructions on retrieving the necessary API credentials from the Sinch dashboard.
Dotenv is used to load environment variables from a .env file. This helps securely manage sensitive credentials like API keys and tokens, keeping them separate from your codebase and preventing accidental exposure in version control.
Rate limiting is crucial for protecting your API from abuse. It prevents excessive requests from a single IP address, which could lead to service disruptions or increased costs. This guide recommends using the express-rate-limit library for this purpose.
Yes, you can send international SMS messages. Ensure recipient numbers are in E.164 format (+[country code][number]). Be aware of international SMS regulations and potential variations in cost and deliverability.
Log into your Sinch dashboard, navigate to SMS -> APIs to find your Service plan ID and API token. Retrieve or provision a Sinch virtual number, note your Sinch region base URL, and store these values securely in a .env file.
The health check endpoint, typically at the root path ('/'), allows you to quickly confirm if the server is running and responding. This is helpful for monitoring and ensuring basic functionality is available.
Axios is a promise-based HTTP client for Node.js. It simplifies making HTTP requests to external APIs like the Sinch SMS API, providing clear error handling and support for asynchronous operations using async/await.
The provided example code includes detailed error handling using try-catch blocks. It catches errors during the API call, logs relevant information, including any response from Sinch, and returns consistent error objects to the client.
For high-throughput scenarios where sending many SMS messages is necessary, a message queue is recommended. This decouples message processing from the API request/response cycle, improving performance, scalability, and fault tolerance.
While the example uses a basic regex, it's strongly recommended to use a dedicated phone number validation library like libphonenumber-js for production to ensure accurate and robust validation according to international standards.
Key security practices include never hardcoding API keys, using environment variables and .gitignore, implementing robust input validation, using rate limiting, and securing the endpoint with proper authentication/authorization in production.
Check Sinch's dashboard for detailed delivery reports correlated with the batchId returned by the API. Also, verify the recipient number is correct and consider potential carrier filtering or spam blocks at the recipient's end.
Leverage Node.js's asynchronous nature using async/await, and for high volumes, consider using a message queue to handle sending asynchronously with worker processes to maximize throughput and reliability.
Build a production-ready Node.js application that sends SMS messages via the Sinch SMS REST API using Express. This comprehensive tutorial covers everything from initial setup and credential management to error handling and deployment strategies.
Last Updated: January 2025
What You'll Learn: Building a Sinch SMS API Integration
What You'll Build:
Create a minimal but functional Express API with a single endpoint (
/send-sms). This endpoint accepts POST requests containing a recipient phone number and message body, then uses the Sinch SMS API to send the message.Common Use Cases for SMS Integration:
This guide enables developers to quickly integrate SMS sending capabilities into their Node.js applications for various purposes, such as:
Technologies Used:
/batchesendpoint..envfile, keeping sensitive credentials secure.Note on Implementation Approach: This guide demonstrates direct REST API calls using
axios. Sinch also provides an official Node.js SDK (@sinch/sdk-corev1.2.1+) that simplifies authentication and API interactions. For production applications, consider using the SDK for better type safety and maintenance. This tutorial focuses on the REST API approach to provide deeper understanding of the underlying HTTP mechanics.Architecture Diagram:
Prerequisites:
node -vandnpm -v.Final Outcome:
At the end of this guide, you'll have a running Node.js Express server that accepts API requests to send SMS messages using your Sinch account credentials.
1. Setting up the Project
Let's initialize our Node.js project and install the necessary dependencies.
Create Project Directory: Open your terminal or command prompt and create a new directory for the project, then navigate into it.
mkdir sinch-sms-sender cd sinch-sms-senderInitialize Node.js Project: Initialize the project using npm. The
-yflag accepts the default settings.This creates a
package.jsonfile in your directory.Install Dependencies: Install Express for the server, axios for making HTTP requests, and dotenv for environment variable management.
(Note: We will install
express-rate-limitlater when it's introduced in Section 7.)Project Structure: Create the necessary files. Your basic structure should look like this:
.env: Stores sensitive credentials (API keys, phone numbers). Crucially, add.envto your.gitignorefile later to prevent committing secrets.index.js: Contains our Express application logic.package.json: Defines project metadata and dependencies.Create
.gitignore: Create a file named.gitignorein the root of your project and add the following lines to prevent committing sensitive information and dependency folders:2. Sinch Account Setup and API Credentials
Before writing code, you need to retrieve necessary credentials and information from your Sinch account.
Log in to Sinch: Access the Sinch Customer Dashboard.
Navigate to SMS API Settings: In the left-hand menu, go to SMS -> APIs.
Retrieve
SERVICE_PLAN_IDandAPI_TOKEN:Retrieve Your Sinch Number:
+12025550184).Determine Your Region:
https://us.sms.api.sinch.comhttps://eu.sms.api.sinch.comhttps://au.sms.api.sinch.comhttps://ca.sms.api.sinch.comhttps://br.sms.api.sinch.com/xms/v1/{SERVICE_PLAN_ID}/batches.Configure Environment Variables: Open the
.envfile you created earlier and add your Sinch credentials and configuration. Replace the placeholder values with your actual details.SINCH_SERVICE_PLAN_ID: Your service plan ID from the dashboard.SINCH_API_TOKEN: Your API token from the dashboard.SINCH_NUMBER: The Sinch virtual number you'll send messages from.SINCH_REGION_BASE_URL: The base URL for your account's region.PORT: The port your Express server will listen on.3. Building the Express Server with SMS Functionality
Now, let's set up the basic Express server structure in
index.js.// index.js require('dotenv').config(); // Load environment variables from .env file first const express = require('express'); const axios = require('axios'); // --- Environment Variable Validation (Basic) --- // Ensure essential Sinch variables are loaded if (!process.env.SINCH_SERVICE_PLAN_ID || !process.env.SINCH_API_TOKEN || !process.env.SINCH_NUMBER || !process.env.SINCH_REGION_BASE_URL) { // Use single quotes for the error message string for consistency console.error('FATAL ERROR: Missing required Sinch environment variables. Check your .env file.'); process.exit(1); // Exit if essential config is missing } const SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID; const API_TOKEN = process.env.SINCH_API_TOKEN; const SINCH_NUMBER = process.env.SINCH_NUMBER; const SINCH_API_URL = `${process.env.SINCH_REGION_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`; const PORT = process.env.PORT || 3000; // Default to 3000 if not set // --- Express App Setup --- const app = express(); app.use(express.json()); // Middleware to parse JSON request bodies // --- Health Check Endpoint --- app.get('/', (req, res) => { res.status(200).json({ status: 'OK', message: 'Sinch SMS Sender API is running.' }); }); // --- SMS Sending Endpoint (To be implemented next) --- // app.post('/send-sms', ...) // --- Start Server --- // Assign the server instance returned by app.listen to a variable const server = app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); }); // --- Graceful Shutdown (Optional but Recommended) --- process.on('SIGTERM', () => { console.info('SIGTERM signal received: closing HTTP server'); // Now 'server.close()' works correctly server.close(() => { console.log('HTTP server closed'); // Add any other cleanup logic here before exiting process.exit(0); }); }); process.on('SIGINT', () => { // Handle Ctrl+C gracefully as well console.info('SIGINT signal received: closing HTTP server'); server.close(() => { console.log('HTTP server closed'); process.exit(0); }); });Explanation:
require('dotenv').config();: Loads variables from the.envfile intoprocess.env. This must be called early.app = express();: Creates an Express application instance.app.use(express.json());: Enables the server to automatically parse incoming JSON request bodies, makingreq.bodyavailable./GET route confirms the server is running.const server = app.listen(): Starts the server, makes it listen for connections on the specifiedPORT, and assigns the server instance to theservervariable.SIGTERMandSIGINT(Ctrl+C) signals is included, using theservervariable to properly close connections before exiting.You can run the server now to test the basic setup:
You should see
Server running on http://localhost:3000. Accessinghttp://localhost:3000in your browser should show the health check JSON response. PressCtrl+Cto stop the server (which should now trigger the graceful shutdown log).4. Implementing the SMS Sending Logic
Let's create the function responsible for interacting with the Sinch API.
Add the following function before the
// --- Express App Setup ---section inindex.js:// index.js // ... (require statements and environment variable validation) ... const SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID; const API_TOKEN = process.env.SINCH_API_TOKEN; const SINCH_NUMBER = process.env.SINCH_NUMBER; const SINCH_API_URL = `${process.env.SINCH_REGION_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`; const PORT = process.env.PORT || 3000; // --- Sinch SMS Sending Function --- async function sendSinchSms(recipientNumber, messageBody) { console.log(`Attempting to send SMS to: ${recipientNumber}`); // Basic logging const payload = { from: SINCH_NUMBER, to: [recipientNumber], // API expects an array of recipients body: messageBody, }; const config = { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }; try { const response = await axios.post(SINCH_API_URL, payload, config); console.log('Sinch API Response Status:', response.status); // Pretty print JSON for console readability; consider structured logging for production console.log('Sinch API Response Data:', JSON.stringify(response.data, null, 2)); // Basic success check (Sinch usually returns 201 Created for successful batch submissions) if (response.status === 200 || response.status === 201) { // The batch ID can be used for tracking if needed const batchId = response.data?.id; console.log(`SMS batch submitted successfully. Batch ID: ${batchId}`); return { success: true, batchId: batchId, details: response.data }; } else { // Handle unexpected success statuses if necessary console.warn(`Unexpected success status from Sinch: ${response.status}`); return { success: false, error: 'Unexpected status from Sinch API', details: response.data }; } } catch (error) { console.error('Error sending SMS via Sinch:', error.message); // Log more detailed error info if available (e.g., response from Sinch) if (error.response) { console.error('Sinch Error Response Status:', error.response.status); // Pretty print JSON for console readability; consider structured logging for production console.error('Sinch Error Response Data:', JSON.stringify(error.response.data, null, 2)); // Extract specific Sinch error details if possible const sinchError = error.response.data?.request_error?.service_exception?.text || 'Unknown Sinch error'; return { success: false, error: `Sinch API Error: ${sinchError}`, status: error.response.status, details: error.response.data }; } else if (error.request) { // Request was made but no response received console.error('Sinch Error Request:', error.request); return { success: false, error: 'No response received from Sinch API' }; } else { // Something else happened in setting up the request return { success: false, error: `Client setup error: ${error.message}` }; } } } // --- Express App Setup --- // ... (rest of the code from section 3) ...Explanation:
async function sendSinchSms: Defines an asynchronous function to handle the SMS sending logic, making it easier to useawaitwith theaxiospromise.payload: Constructs the JSON body required by the Sinch/batchesendpoint.from: Your Sinch virtual number (loaded from.env).to: An array containing the recipient's phone number(s) in E.164 format.body: The text message content.config: Defines the request configuration foraxios.headers: Sets the necessaryAuthorizationheader using theBearerscheme with yourAPI_TOKENand theContent-Typeheader.axios.post: Sends the POST request to theSINCH_API_URLwith thepayloadandconfig.batchIdfrom the response.catchblock):error.response). If so, logs the status and data from Sinch's error response, providing more specific details. It attempts to extract a meaningful error message from the nested Sinch response structure.error.request).{ success: false, error: '...' }.JSON.stringify(..., null, 2)is used for readable console output during development. For production, consider structured logging, e.g., plain JSON objects, for better machine parsing.)5. Creating the /send-sms API Endpoint
Now, let's create the Express endpoint that will use the
sendSinchSmsfunction.Add the following route definition inside
index.js, replacing the placeholder comment// --- SMS Sending Endpoint (To be implemented next) ---:// index.js // ... (require statements, validation, constants, sendSinchSms function) ... // --- Express App Setup --- const app = express(); app.use(express.json()); // --- Health Check Endpoint --- app.get('/', (req, res) => { res.status(200).json({ status: 'OK', message: 'Sinch SMS Sender API is running.' }); }); // --- SMS Sending Endpoint --- app.post('/send-sms', async (req, res) => { const { recipient, message } = req.body; // Destructure from request body // --- Basic Input Validation --- if (!recipient || !message) { console.warn('Validation Error: Missing recipient or message in request body.'); return res.status(400).json({ success: false, error: 'Missing required fields: recipient, message' }); } // Basic E.164 format check (very simple, consider a library for production) if (!/^\+\d{1,15}$/.test(recipient)) { console.warn(`Validation Error: Invalid recipient format: ${recipient}`); return res.status(400).json({ success: false, error: 'Invalid recipient phone number format. Use E.164 (e.g., +12025550184).' }); } // --- Call the Sending Logic --- try { const result = await sendSinchSms(recipient, message); if (result.success) { res.status(202).json({ // 202 Accepted: Request accepted, processing underway success: true, message: 'SMS batch submitted successfully to Sinch.', batchId: result.batchId, // Avoid sending back full Sinch details unless needed by client // sinchDetails: result.details }); } else { // Determine appropriate status code based on Sinch error if possible const statusCode = result.status || 500; // Default to 500 if specific status unknown console.error(`Failed to send SMS: ${result.error}`); res.status(statusCode).json({ success: false, error: result.error, // Optionally include Sinch error details during development/debugging // sinchDetails: result.details }); } } catch (internalError) { // Catch unexpected errors within the endpoint handler itself console.error('Internal Server Error in /send-sms handler:', internalError); res.status(500).json({ success: false, error: 'Internal Server Error' }); } }); // --- Start Server --- // ... (server assignment, app.listen and graceful shutdown) ...Explanation:
app.post('/send-sms', ...): Defines a route that listens for POST requests on the/send-smspath.const { recipient, message } = req.body;: Extracts therecipientphone number and themessagetext from the JSON payload sent in the request body.recipientandmessageare provided. Returns a 400 Bad Request error if not.recipientlooks like E.164 format (starts with+, followed by digits). For production, use a dedicated phone number parsing/validation library (likelibphonenumber-js).sendSinchSms: Calls the asynchronous function we created earlier, passing the validated recipient and message. Usesawaitto wait for the result.result.successis true, sends a 202 Accepted response back to the client, indicating the request was received and passed to Sinch. Includes thebatchId.result.successis false, logs the error and sends an appropriate error response (e.g., 500 Internal Server Error, or potentially a more specific code like 400 if the error from Sinch indicated a client-side problem like an invalid number format not caught by basic validation).try...catchwithin the route handler catches any unexpected errors during request processing.Testing the Endpoint with
curl:Start the server:
node index.jsOpen another terminal and run the following
curlcommand. Important: Replace+1xxxxxxxxxxwith a valid recipient phone number in E.164 format (your own mobile is fine for testing). Also, ensure theSINCH_NUMBERin your.envfile is correctly set to your provisioned Sinch number (e.g.,+YOUR_SINCH_NUMBER_HERE).curl -X POST http://localhost:3000/send-sms \ -H "Content-Type: application/json" \ -d '{ "recipient": "+1xxxxxxxxxx", "message": "Hello from Node.js and Sinch!" }'-X POST: Specifies the HTTP method.http://localhost:3000/send-sms: The URL of your endpoint.-H "Content-Type: application/json": Sets the request header. Note the correct quoting for the header value.-d '{...}': Provides the JSON data payload. Note the correct quoting for JSON keys and string values. (Note: The multi-line format with\works in Unix-like shells like bash or zsh. For Windows Command Prompt, you might need to put the entire command on one line or handle line breaks differently.)Expected Success Response (Terminal running
curl):{ "success": true, "message": "SMS batch submitted successfully to Sinch.", "batchId": "01ARZ3NDEKTSV4RRFFQ69G5FAV" }(Example Batch ID shown)
Expected Server Logs (Terminal running
node index.js):(Example timestamps and Batch ID shown;
fromnumber should match your.env)You should also receive the SMS on the recipient phone shortly after.
6. Error Handling and Logging Best Practices
We've implemented basic error handling, but let's refine it slightly.
sendSinchSmsfunction return consistent{ success: false, error: '...' }objects. This is good practice.console.logandconsole.error. For production, consider using a more robust logging library likewinstonorpino. These enable:sendSinchSmsfunction attempts to parse specific errors from the Sinch response (error.response.data). You can enhance this by checking specificerror.response.statuscodes (e.g., 400 for bad request, 401/403 for auth issues, 429 for rate limiting) and returning more specific errors or potentially implementing retries for transient issues (like 5xx errors from Sinch, although retries are complex and not implemented here).Example: Testing an Error Scenario
Ctrl+C).SINCH_API_TOKENin your.envfile to an invalid value (e.g., addINVALIDat the end).node index.jscurlcommand again (using a valid recipient number).Expected
curlResponse:{ "success": false, "error": "Sinch API Error: Invalid credentials or service plan ID" }(Or similar message based on Sinch's exact response)
Expected Server Logs:
(Example Sinch 401 Error shown)
Reminder: Don't forget to change the
SINCH_API_TOKENback to its correct value in your.envfile after completing this test!7. Security Best Practices for SMS APIs
While this is a basic guide, keep these security points in mind for real applications:
Credential Management: Never hardcode API keys, tokens, or phone numbers directly in your source code. Always use environment variables (
.envlocally, secure configuration management in deployment). Ensure.envis in your.gitignore.Input Validation: We added basic validation for
recipientandmessage. Robust validation is crucial to prevent injection attacks or malformed requests causing errors. Use libraries likejoi,zod, orexpress-validator. Sanitize message content if necessary, depending on how it's used.Rate Limiting: Protect your API endpoint from abuse (accidental or malicious) by implementing rate limiting. Libraries like
express-rate-limitmake this straightforward.First, install the dependency:
Then, add the rate limiting middleware in
index.js:// index.js // ... require statements ... const rateLimit = require('express-rate-limit'); // Require the package // ... environment variable validation, constants, sendSinchSms function ... const app = express(); app.use(express.json()); // Apply rate limiting to the SMS endpoint const smsLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests sent from this IP, please try again after 15 minutes', standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); app.use('/send-sms', smsLimiter); // Apply limiter specifically to the /send-sms route // ... Health Check Endpoint, SMS Sending Endpoint ... // ... Start Server, Graceful Shutdown ...Authentication/Authorization: This example API is open. In a real application, you would secure the
/send-smsendpoint so only authorized clients or users can trigger SMS sends. This typically involves API keys, JWT tokens, or session management.HTTPS: Always use HTTPS in production to encrypt traffic between the client and your server. Deployment platforms often handle this via load balancers or reverse proxies.
8. Special Cases and International SMS
Consider these points for expansion or specific scenarios:
+followed by country code and number). Be aware of varying regulations, sender ID requirements, and potential filtering regarding SMS sending in different countries.9. Performance Optimization Strategies
For applications sending a higher volume of messages:
async/awaitcorrectly, as shown in thesendSinchSmsfunction and the endpoint handler, ensures the server remains responsive while waiting for the network request to Sinch to complete.10. Monitoring & Observability
For production applications, implement monitoring:
winston,pino) and configure appropriate log levels and destinations (files, log aggregation services). Log key events like request received, validation success/failure, Sinch API call attempt, Sinch API response (success/error), and final response sent to the client. Include correlation IDs (e.g., the SinchbatchId) to trace requests./route is a basic liveness check. Consider adding a readiness check that verifies essential dependencies are available (e.g., perhaps by making a lightweight, non-sending call to Sinch if available, or checking database connectivity if used)./send-smsendpoint:batchIdlogged by the application to correlate specific requests with Sinch's delivery reports.11. Common Issues and Troubleshooting
SINCH_SERVICE_PLAN_IDandSINCH_API_TOKENin your environment variables are correct and exactly match your dashboard values.Authorization: Bearer YOUR_API_TOKENheader is being correctly constructed and sent byaxios.SINCH_REGION_BASE_URL) for your account.recipientnumber format. It must be in E.164 format (+followed by country code and number, with no spaces, dashes, or other characters).fromnumber (SINCH_NUMBERin.env) is a valid Sinch virtual number associated with your service plan and is also correctly formatted in E.164.{ "from": "...", "to": ["..."], "body": "..." }. Pay close attention totobeing an array. Check that theContent-Type: application/jsonheader is correctly set on the request to Sinch.bodymight be empty, invalid (e.g., containing unsupported characters if not using appropriate encoding), or exceed length limits.error.response.datain the server logs) for specific clues (e.g.,INVALID_PARAMETER_VALUE,MISSING_PARAMETER).batchIdreturned by the API and logged by your server to look up the specific message status and delivery reports in the Sinch Customer Dashboard. This is the most reliable way to diagnose delivery issues.express-rate-limitmiddleware can also return 429 if the client exceeds the limits you set. Implement appropriate delays between requests or use a queuing system (see Section 9) for high-volume sending.12. Deployment and CI/CD (Conceptual)
Deploying this Node.js application typically involves these steps:
SINCH_SERVICE_PLAN_ID,SINCH_API_TOKEN,SINCH_NUMBER,SINCH_REGION_BASE_URL,PORT,NODE_ENV=production) within your chosen hosting platform's configuration management system. Crucially, do not commit your.envfile or hardcode secrets in your repository.npm run build) to compile/bundle your code before deployment.npm start(if defined inpackage.json) ornode index.js. Use a process manager likepm2(if managing your own server/VM) or rely on the platform's built-in mechanisms (like Heroku dynos or container orchestration) to run the application reliably, monitor its health, and restart it if it crashes.npm ci).npm run lint, if configured).npm test, if configured).PORT.13. Testing Your SMS Integration
node index.js).curlor a tool like Postman tohttp://localhost:3000/send-smswith a correct recipient E.164 number and message body.202 Acceptedsuccess response (withsuccess: trueand abatchId) is received by the client.batchId.recipientor nomessage) and verify appropriate400 Bad Requesterror responses are received.12345) and verify a400 Bad Requesterror response related to the format is received..envfile (e.g., modifySINCH_API_TOKEN), restart the server, send a valid request, and verify an appropriate error response (likely401 Unauthorizedor similar, based on the logged Sinch error) is returned by the API. Remember to restore the correct credentials afterwards.sendSinchSms, mock theaxios.postcall to simulate successful and various error responses from the Sinch API without making actual network requests. Verify that the function handles responses correctly and returns the expected{ success: ..., ... }object.supertestto make HTTP requests to your running Express application (potentially in a test environment with mocked dependencies). Test the/send-smsendpoint:sendSinchSmsfunction within these tests to control its behavior and prevent actual calls to Sinch during testing.Frequently Asked Questions About Sinch SMS API
How do I get my Sinch Service Plan ID and API Token?
Log in to the Sinch Dashboard and navigate to SMS → APIs. Your Service Plan ID appears in the SERVICE PLAN ID column. Click "Show" in the API TOKEN column to reveal your API token. Treat this token as a password and never commit it to version control.
Which Sinch regional endpoint should I use?
The regional endpoint depends on where your Sinch account is provisioned. Check your Sinch Dashboard under SMS → APIs to verify your region. Common endpoints include
https://us.sms.api.sinch.com(United States),https://eu.sms.api.sinch.com(Europe),https://au.sms.api.sinch.com(Australia),https://ca.sms.api.sinch.com(Canada), andhttps://br.sms.api.sinch.com(Brazil). Using the wrong endpoint will cause authentication errors.Should I use the Sinch Node.js SDK or the REST API directly?
The official Sinch Node.js SDK (
@sinch/sdk-corev1.2.1+) provides better type safety, simplified authentication, and easier maintenance for production applications. This tutorial uses direct REST API calls withaxiosto demonstrate the underlying HTTP mechanics, which helps you understand how the API works. For production, consider the SDK for reduced boilerplate and built-in error handling.What phone number format does Sinch require?
Sinch requires all phone numbers in E.164 format: a plus sign (+) followed by the country code and number with no spaces, dashes, or parentheses. For example:
+12025550184(US) or+442071234567(UK). The basic regex/^\+\d{1,15}$/validates this format, but use a library likelibphonenumber-jsfor production validation.How do I handle Sinch API errors in Node.js?
Sinch returns detailed error information in the response body. Check
error.response.statusfor HTTP status codes (401 for auth issues, 400 for bad requests, 429 for rate limits) anderror.response.data.request_error.service_exception.textfor specific error messages. Log these details for debugging and return appropriate error responses to your API clients.What is the Sinch SMS API rate limit?
Sinch enforces rate limits that vary by account type and region. Implement client-side rate limiting using
express-rate-limitto protect your API. For high-volume applications, use a message queue (RabbitMQ, Redis, AWS SQS) to decouple API requests from Sinch API calls and manage throughput more effectively.How much does sending SMS with Sinch cost?
SMS costs vary by destination country, message segments (160 characters for GSM-7 encoding, 70 for Unicode), and your Sinch plan. Monitor usage and billing in the Sinch Dashboard. Longer messages may be split into multiple segments, increasing costs. Check Sinch's pricing page for current rates by country.
Can I send SMS to international numbers with Sinch?
Yes, Sinch supports international SMS delivery to most countries. Ensure recipient numbers are in E.164 format with the correct country code. Be aware of country-specific regulations, potential carrier filtering, and varying delivery rates. Some countries require sender ID registration or have restrictions on commercial messaging.
Related Resources and Further Reading