Building a Node.js Express service for SMS marketing campaigns with Infobip
This guide provides a step-by-step walkthrough for building a robust Node.js and Express application to send SMS messages using the Infobip API. We'll focus on creating a scalable and secure service foundation suitable for powering SMS marketing campaigns, transactional notifications, or other communication needs.
We'll leverage the official Infobip Node.js SDK for simplified integration, handle essential configurations like API keys securely, implement basic API endpoints, and cover crucial aspects like error handling and verification. By the end, you'll have a functional Express service capable of sending SMS messages via Infobip.
Project overview and goals
Goal: To create a dedicated Node.js Express microservice responsible for sending SMS messages via the Infobip platform. This service will expose a simple API endpoint that other applications within your infrastructure can call to trigger SMS sends.
Problem Solved: Centralizes SMS sending logic, securely manages Infobip credentials, provides a consistent interface for sending messages, and simplifies integration for other services needing SMS capabilities.
Technologies:
- Node.js: A JavaScript runtime built on Chrome's V8 engine, ideal for building fast, scalable network applications.
- Express: A minimal and flexible Node.js web application framework, providing a robust set of features for web and mobile applications.
- Infobip API: A comprehensive communication platform-as-a-service (CPaaS) offering various APIs, including SMS.
- Infobip Node.js SDK (
@infobip-api/sdk
): Simplifies interaction with the Infobip API by providing pre-built methods and handling authentication. - dotenv: A zero-dependency module that loads environment variables from a
.env
file intoprocess.env
.
Why these choices?
- Node.js and Express offer a popular, efficient, and widely supported stack for building APIs.
- Infobip provides reliable global SMS delivery.
- Using the official Infobip SDK significantly reduces boilerplate code compared to direct HTTP requests, handles authentication details, and is maintained by Infobip.
dotenv
is standard practice for managing environment-specific configurations and secrets securely in development.
System Architecture:
+-----------------+ +---------------------+ +-----------------+ +-----------------+ +-----------------+
| Client App | ---> | Node.js/Express | ---> | Infobip Node.js | ---> | Infobip API | ---> | SMS Recipient |
| (e.g., Web FE, | | SMS Service (API) | | SDK | | (SMS GW) | | (Mobile Phone)|
| Backend Job) | +---------------------+ +-----------------+ +-----------------+ +-----------------+
| | | - API Endpoint | | - Authentication| | | | |
| - Makes API call| | - Request Validation| | - API Call | | - Sends SMS | | - Receives SMS |
| with number & | | - Calls Infobip SDK | | Abstraction | | | | |
| message | | - Handles Secrets | +-----------------+ +-----------------+ +-----------------+
+-----------------+ +---------------------+
Prerequisites:
- An active Infobip account.
- Node.js (LTS version recommended) and npm (or yarn) installed.
- A text editor or IDE (like VS Code).
- Basic understanding of JavaScript, Node.js, Express, and REST APIs.
- A phone number registered and verified with your Infobip account (especially if using a free trial).
Final Outcome: A running Express application with a /api/sms/send
endpoint that accepts a phone number and message text, sends the SMS via Infobip, and returns a success or error response.
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.
mkdir infobip-sms-service cd infobip-sms-service
-
Initialize Node.js Project: This command creates a
package.json
file to manage project details and dependencies. You can accept the defaults.npm init -y
(Alternatively, use
yarn init -y
if you prefer Yarn) -
Install Dependencies: We need Express for the web server, the Infobip SDK for SMS sending, and
dotenv
for managing environment variables.npm install express @infobip-api/sdk dotenv
(Or
yarn add express @infobip-api/sdk dotenv
) -
Create Project Structure: Set up a basic structure for organization.
mkdir src mkdir src/routes mkdir src/controllers mkdir src/services touch src/server.js touch src/routes/smsRoutes.js touch src/controllers/smsController.js touch src/services/infobipService.js touch .env touch .gitignore
src/
: Contains our main application code.src/routes/
: Defines API routes.src/controllers/
: Handles incoming requests and interacts with services.src/services/
: Contains business logic, like interacting with the Infobip SDK.src/server.js
: The main entry point for our Express application..env
: Stores environment variables (API keys, etc.). Never commit this file to Git..gitignore
: Specifies files and directories that Git should ignore.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.# .gitignore node_modules/ .env npm-debug.log* yarn-debug.log* yarn-error.log*
-
Set up Basic Express Server (
src/server.js
): Create a minimal Express server to ensure setup is working.// src/server.js require('dotenv').config(); // Load .env variables early const express = require('express'); const smsRoutes = require('./routes/smsRoutes'); // We'll create this next const app = express(); const PORT = process.env.PORT || 3000; // Middleware to parse JSON bodies app.use(express.json()); // Mount the SMS routes app.use('/api/sms', smsRoutes); // Basic health check route app.get('/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() }); }); // Global error handler (minimal example) // NOTE: This is very basic. Production apps need more robust error handling. app.use((err, req, res, next) => { console.error('Unhandled Error:', err); // In production, you should log the full error stack trace using a proper logger // and potentially send error details to an error tracking service (e.g., Sentry). // Avoid sending detailed internal error messages to the client. res.status(500).json({ success: false, error: 'Internal Server Error' }); }); app.listen(PORT, () => { console.log(`SMS Service listening on port ${PORT}`); });
require('dotenv').config()
: Loads variables from the.env
file.express.json()
: Middleware to parse incoming JSON request bodies.- We define a basic
/health
check endpoint. - We mount placeholder routes for
/api/sms
. - A simple global error handler is included. Note: This is intentionally minimal for the example; a production application requires more sophisticated error handling, including structured logging and potentially integration with error tracking services.
-
Update
package.json
Scripts: Add a convenientstart
script to run the server.// package.json (add/update the ""scripts"" section) { ""name"": ""infobip-sms-service"", ""version"": ""1.0.0"", ""description"": ""Express service to send SMS via Infobip"", ""main"": ""src/server.js"", ""scripts"": { ""start"": ""node src/server.js"", ""test"": ""echo \""Error: no test specified\"" && exit 1"" }, ""keywords"": [ ""infobip"", ""sms"", ""express"", ""node"" ], ""license"": ""ISC"", ""dependencies"": { ""@infobip-api/sdk"": ""^2.0.0"", ""dotenv"": ""^16.0.0"", ""express"": ""^4.17.0"" } }
Note: The dependency versions listed (
^2.0.0
,^16.0.0
,^4.17.0
) are examples. Ensure you install compatible versions or update them as needed based on the latest stable releases.
You now have a basic Node.js Express project structure ready for implementing the SMS functionality.
2. Implementing core functionality (Infobip Service)
We'll encapsulate the Infobip SDK interaction within a dedicated service file.
-
Configure Environment Variables (
.env
): Open the.env
file and add placeholders for your Infobip Base URL and API Key. You will obtain these from your Infobip account dashboard in the next step.# .env INFOBIP_BASE_URL=your_infobip_base_url_here INFOBIP_API_KEY=your_infobip_api_key_here # Optional: Default sender ID # INFOBIP_SENDER_ID=InfoSMS
-
Create Infobip Service (
src/services/infobipService.js
): This service will initialize the Infobip client and provide a function to send SMS.// src/services/infobipService.js const { Infobip, AuthType } = require('@infobip-api/sdk'); // Validate that environment variables are set if (!process.env.INFOBIP_BASE_URL || !process.env.INFOBIP_API_KEY) { console.error('ERROR: Infobip Base URL or API Key not found in environment variables.'); console.error('Ensure INFOBIP_BASE_URL and INFOBIP_API_KEY are set in your .env file or environment.'); process.exit(1); // Exit if configuration is missing } // Initialize the Infobip client instance const infobipClient = new Infobip({ baseUrl: process.env.INFOBIP_BASE_URL, apiKey: process.env.INFOBIP_API_KEY, authType: AuthType.ApiKey, // Use API Key authentication }); /** * Sends an SMS message using the Infobip SDK. * @param {string} recipientNumber - The destination phone number in international format (e.g., 447123456789). * @param {string} messageText - The text content of the SMS message. * @param {string} [senderId] - Optional sender ID (alphanumeric). Defaults to process.env.INFOBIP_SENDER_ID or undefined. * @returns {Promise<object>} - A promise that resolves with the Infobip API response data. * @throws {Error} - Throws an error if the API call fails. */ const sendSms = async (recipientNumber, messageText, senderId = process.env.INFOBIP_SENDER_ID) => { console.log(`Attempting to send SMS to ${recipientNumber}`); // Basic validation (CRITICAL: Enhance significantly for production) if (!recipientNumber || !messageText) { throw new Error('Recipient number and message text are required.'); // Production requires robust validation: // - Use regex or libraries like libphonenumber-js for phone number format. // - Check message length against SMS limits (e.g., 160 chars for GSM-7). // - Sanitize inputs if necessary. } const payload = { messages: [ { destinations: [{ to: recipientNumber }], text: messageText, // Only include 'from' if senderId is provided ...(senderId && { from: senderId }), }, ], // You can add more options here, e.g., tracking, scheduling, flash SMS etc. // See Infobip documentation for the /sms/2/text/advanced endpoint options }; try { // Use the SDK's method to send the SMS const response = await infobipClient.channels.sms.send(payload); console.log('Infobip API Response:', JSON.stringify(response.data, null, 2)); // Check for success indication within the response if needed // The SDK might throw an error for non-2xx responses, but you can add checks here if (response.data && response.data.messages && response.data.messages.length > 0) { const messageStatus = response.data.messages[0].status; console.log(`Message status for ${recipientNumber}: ${messageStatus.groupName} (${messageStatus.name})`); // PENDING or ACCEPTED usually means success, REJECTED means failure at Infobip's end. } return response.data; // Return the body of the response } catch (error) { console.error('Error sending SMS via Infobip:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message); // Re-throw a more specific error or handle it as needed throw new Error(`Failed to send SMS: ${error.response?.data?.requestError?.serviceException?.text || error.message}`); } }; // Export the function(s) for use in controllers module.exports = { sendSms, // Add other Infobip-related functions here if needed (e.g., check balance, get logs) };
- We import the
Infobip
client andAuthType
enum from the SDK. - Crucially, we check if the required environment variables are present before initializing the client.
- The
infobipClient
is instantiated using the Base URL and API Key from.env
. - The
sendSms
function takes the recipient, message, and optional sender ID. - It performs basic validation, but it's critical to enhance this with stricter checks (e.g., phone number format using regex or libraries like
libphonenumber-js
, message length) for production use. - It constructs the payload according to the Infobip API specification (
/sms/2/text/advanced
endpoint, which the SDK uses). - It calls
infobipClient.channels.sms.send()
within atry...catch
block. - It logs the response and status for debugging.
- It handles errors, logging relevant details from the Infobip error response if available.
- We import the
3. Building the API layer (Express Route and Controller)
Now, let's create the Express route and controller to handle incoming requests to send SMS.
-
Create SMS Controller (
src/controllers/smsController.js
): This controller handles the logic for the/send
route. We'll add more robust validation in section 7.// src/controllers/smsController.js const infobipService = require('../services/infobipService'); const sendSingleSms = async (req, res, next) => { // 1. Extract data from request body const { recipientNumber, messageText, senderId } = req.body; // 2. Basic Input Validation (Will be replaced by middleware later) if (!recipientNumber || !messageText) { return res.status(400).json({ success: false, error: 'Missing required fields: recipientNumber and messageText are required.', }); } // Note: More robust validation will be added using middleware in the Security section. try { // 3. Call the Infobip service function const result = await infobipService.sendSms(recipientNumber, messageText, senderId); // 4. Send success response // The result object from Infobip contains details like messageId and status res.status(200).json({ success: true, message: 'SMS submitted successfully to Infobip.', infobipResponse: result, // Include the response from Infobip }); } catch (error) { // 5. Handle errors from the service console.error('Error in sendSingleSms controller:', error.message); // Pass error to the global error handler, or return a specific error response // Depending on the error type, you might return different status codes res.status(500).json({ success: false, error: `Failed to send SMS: ${error.message}`, }); // Alternatively: next(error); // To use the global error handler in server.js } }; // Add other controller functions here (e.g., sendBulkSms, getSmsStatus) module.exports = { sendSingleSms, // We will export validation rules later };
- It requires the
infobipService
. - It extracts
recipientNumber
andmessageText
fromreq.body
. - It performs minimal validation initially. This will be replaced by dedicated validation middleware later for better structure and robustness.
- It calls
infobipService.sendSms
within atry...catch
block. - It returns a
200 OK
response with the Infobip result on success. - It returns a
400 Bad Request
for validation errors or500 Internal Server Error
for service errors.
- It requires the
-
Define SMS Routes (
src/routes/smsRoutes.js
): This file defines the specific endpoints under the/api/sms
prefix.// src/routes/smsRoutes.js const express = require('express'); const smsController = require('../controllers/smsController'); const router = express.Router(); // POST /api/sms/send - Endpoint to send a single SMS // Validation middleware will be added here later router.post('/send', smsController.sendSingleSms); // Define other SMS-related routes here if needed // e.g., router.post('/send-bulk', smsController.sendBulkSms); // e.g., router.get('/status/:messageId', smsController.getSmsStatus); module.exports = router;
- It requires the
smsController
. - It defines a
POST
route at/send
that maps to thesendSingleSms
controller function.
- It requires the
Your API layer is now set up. The server.js
file already mounts these routes under /api/sms
.
4. Integrating with Infobip (Credentials)
This step is crucial for connecting your application to your Infobip account.
-
Log in to Infobip: Go to the Infobip Portal and log in. Disclaimer: The exact navigation within the Infobip portal may change over time.
-
Find your API Key:
- Navigate to the ""Developers"" section or your account settings. Look for API Keys management.
- If you don't have an API key, create a new one. Give it a descriptive name (e.g., ""NodeExpressSMSService"").
- Important: Copy the generated API key immediately and store it securely. You often cannot view the key again after closing the creation dialog.
-
Find your Base URL:
- Still in the Developers section or API documentation area, find your personalized Base URL. It usually looks like
xxxxx.api.infobip.com
. - Copy this Base URL.
- Still in the Developers section or API documentation area, find your personalized Base URL. It usually looks like
-
Update
.env
file: Paste the copied values into your.env
file:# .env INFOBIP_BASE_URL=xxxxx.api.infobip.com # Replace with your actual Base URL INFOBIP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Replace with your actual API Key # Optional: Default sender ID allowed for your account # INFOBIP_SENDER_ID=YourBrand
-
Sender ID (Optional but Recommended):
- Using a custom Sender ID (like your brand name) instead of a generic number improves recognition.
- Sender ID registration and regulations vary by country. Check Infobip documentation and local regulations. You may need to register your desired Sender ID via the Infobip portal.
- If you have an approved Sender ID, you can set it as default in
.env
or pass it in the API request body (senderId
field).
Security: Your Infobip API key grants access to your account and potentially incurs costs. Never commit your .env
file or expose your API key in client-side code or public repositories. Use environment variables in your deployment environment.
5. Error handling and logging
Robust error handling and logging are vital for production systems.
- Service Layer (
infobipService.js
):- The
try...catch
block already catches errors from the SDK call. - We log the detailed error response from Infobip (
error.response.data
) when available, which is crucial for debugging API issues (e.g., invalid number format, insufficient funds, permission errors). - We throw a new, slightly cleaner error message for the controller layer.
- The
- Controller Layer (
smsController.js
):- The
try...catch
block catches errors thrown by the service layer. - It logs the error message.
- It returns appropriate HTTP status codes:
400 Bad Request
/422 Unprocessable Entity
for client-side errors (missing parameters, invalid format - see validation section).500 Internal Server Error
for server-side or downstream errors (Infobip API unavailable, unexpected exceptions).
- The response body includes a
success: false
flag and anerror
message.
- The
- Server Layer (
server.js
):- The basic global error handler catches any unhandled exceptions. As noted earlier, this is minimal. For production, use a more sophisticated handler, integrating structured logging (e.g.,
winston
,pino
) and potentially error tracking services (e.g., Sentry).
- The basic global error handler catches any unhandled exceptions. As noted earlier, this is minimal. For production, use a more sophisticated handler, integrating structured logging (e.g.,
- Logging:
- We are currently using
console.log
andconsole.error
. For production:- Use a structured logging library (Winston, Pino) to output logs in JSON format, making them easier to parse and analyze.
- Include timestamps, log levels (INFO, WARN, ERROR), and potentially request IDs for tracing.
- Configure log destinations (e.g., file, standard output, external logging service).
- We are currently using
- Retry Mechanisms (Optional Enhancement):
- For transient network errors or temporary Infobip issues (e.g., 5xx status codes), implementing a retry strategy with exponential backoff can improve resilience. This is an optional enhancement for robustness, not part of the core initial setup.
- Libraries like
async-retry
can simplify this. You would wrap theinfobipClient.channels.sms.send
call within the retry logic ininfobipService.js
. - Be cautious not to retry non-retryable errors (like 4xx errors indicating bad input or invalid credentials).
// Example using async-retry (install: npm install async-retry)
// This is an OPTIONAL enhancement for resilience, place inside src/services/infobipService.js sendSms function
const retry = require('async-retry');
// ... inside sendSms function, replace the direct SDK call ...
try {
const response = await retry(
async (bail, attempt) => {
console.log(`Attempting Infobip API call, attempt number ${attempt}...`);
// Check if the error is a client error (4xx) which shouldn't be retried.
// The SDK throws an error which we catch below. If it's a non-retryable error,
// the 'bail' function can be called to stop retrying.
// Example: if (error.response && error.response.status >= 400 && error.response.status < 500) { bail(error); return; }
const sdkResponse = await infobipClient.channels.sms.send(payload);
// Optional: Check for specific Infobip statuses within the response data that might warrant a retry_
// though typically relying on HTTP status codes (handled by the SDK's error throwing) is sufficient.
return sdkResponse; // Return successful response from SDK
}_
{
retries: 3_ // Number of retries
factor: 2_ // Exponential backoff factor
minTimeout: 1000_ // Initial delay ms
onRetry: (error_ attempt) => {
console.warn(`Retrying Infobip API call (attempt ${attempt}) due to error: ${error.message}`);
// Add check here to prevent retrying client errors
if (error.response && error.response.status >= 400 && error.response.status < 500) {
console.warn(`Not retrying on client error (${error.response.status}).`);
throw error; // Re-throw the original error to stop retries
}
}_
}
);
console.log('Infobip API Response:'_ JSON.stringify(response.data_ null_ 2));
// ... rest of success handling ...
return response.data;
} catch (error) {
// This catch block now handles errors after retries have failed_ or non-retryable errors
console.error('Error sending SMS via Infobip after retries (or non-retryable error):'_ error.response ? JSON.stringify(error.response.data_ null_ 2) : error.message);
throw new Error(`Failed to send SMS after retries: ${error.response?.data?.requestError?.serviceException?.text || error.message}`);
}
6. Database schema and data layer (Conceptual)
While this guide focuses purely on the sending mechanism_ a real-world marketing campaign system requires data persistence. This service could be extended or interact with other services managing:
- Contacts/Subscribers: Storing phone numbers_ opt-in status_ names_ segments_ etc.
Contacts (contact_id PK_ phone_number UNIQUE_ first_name_ last_name_ opt_in_status_ created_at_ updated_at)
- Campaigns: Defining marketing campaigns_ messages_ target segments_ schedules.
Campaigns (campaign_id PK_ name_ message_template_ target_segment_ schedule_time_ status_ created_at)
- Message Logs: Tracking each sent message_ its status_ recipient_ campaign association_ cost_ etc. This is crucial for analytics and troubleshooting.
MessageLogs (log_id PK_ infobip_message_id UNIQUE_ contact_id FK_ campaign_id FK_ status_ status_group_ error_code_ sent_at_ delivered_at_ cost)
- Opt-Outs: Managing unsubscribe requests.
OptOuts (phone_number PK_ opted_out_at)
Implementation:
- You would typically use an ORM (like Prisma_ Sequelize_ TypeORM) or a query builder (like Knex.js) to interact with a database (e.g._ PostgreSQL_ MySQL).
- Database migrations would be essential for managing schema changes.
- This SMS service might read contact lists or receive campaign instructions from another service_ or it could be expanded to include this logic itself_ depending on your overall architecture.
Scope: Implementing the full database layer is beyond the scope of this specific Infobip integration guide.
7. Adding security features
Security is paramount_ especially when handling user data and external APIs.
-
Secure API Key Management: Already covered by using
.env
and environment variables in production. Never hardcode keys. -
Input Validation:
-
The initial controller (
smsController.js
) has minimal validation. We'll replace it usingexpress-validator
for robust_ declarative validation. -
Install the library:
npm install express-validator # or: yarn add express-validator
-
Update Controller (
src/controllers/smsController.js
): Define validation rules and a middleware function to handle errors. Export these along with the main controller function.// src/controllers/smsController.js const { body_ validationResult } = require('express-validator'); const infobipService = require('../services/infobipService'); // Define validation rules for the send SMS endpoint const sendSingleSmsValidationRules = () => { return [ body('recipientNumber') .trim() // Remove leading/trailing whitespace .notEmpty().withMessage('recipientNumber is required.') // Use a more specific validator like isMobilePhone or a custom regex for international format // Example: .matches(/^\d{11,15}$/).withMessage('recipientNumber must be 11-15 digits.') .isMobilePhone('any', { strictMode: false }).withMessage('recipientNumber must be a valid phone number format.'), // Basic check body('messageText') .trim() .notEmpty().withMessage('messageText is required.') .isLength({ min: 1, max: 1600 }).withMessage('messageText must be between 1 and 1600 characters.'), // Consider SMS segment limits body('senderId') .optional() // Allows senderId to be absent .trim() .isAlphanumeric().withMessage('senderId must be alphanumeric.') .isLength({ min: 1, max: 11 }).withMessage('senderId must be between 1 and 11 characters.'), ]; }; // Middleware to handle validation results const validate = (req, res, next) => { const errors = validationResult(req); if (errors.isEmpty()) { return next(); // Proceed to the controller if no errors } // Format errors for the response const extractedErrors = []; errors.array().map(err => extractedErrors.push({ [err.path]: err.msg })); // Return 422 Unprocessable Entity for validation failures return res.status(422).json({ success: false, errors: extractedErrors, }); }; // Controller function (no longer needs internal basic validation) const sendSingleSms = async (req, res, next) => { // Validation has already passed via middleware const { recipientNumber, messageText, senderId } = req.body; try { const result = await infobipService.sendSms(recipientNumber, messageText, senderId); res.status(200).json({ success: true, message: 'SMS submitted successfully to Infobip.', infobipResponse: result, }); } catch (error) { console.error('Error in sendSingleSms controller:', error.message); // Optionally, pass to global error handler: next(error) res.status(500).json({ success: false, error: `Failed to send SMS: ${error.message}`, }); } }; module.exports = { sendSingleSmsValidationRules, // Export rules array function validate, // Export validation middleware sendSingleSms, // Export controller function };
-
Update Routes (
src/routes/smsRoutes.js
): Apply the validation rules and middleware to the route definition before the controller function.// src/routes/smsRoutes.js const express = require('express'); // Import the specific functions needed from the controller const { sendSingleSms, sendSingleSmsValidationRules, validate } = require('../controllers/smsController'); const router = express.Router(); // POST /api/sms/send - Apply validation rules and middleware, then the controller router.post( '/send', sendSingleSmsValidationRules(), // Define the rules for this route validate, // Run the validation check sendSingleSms // If validation passes, proceed to the controller ); // Define other SMS-related routes here if needed module.exports = router;
-
-
Rate Limiting: Protect your API from abuse and accidental loops. Use middleware like
express-rate-limit
.npm install express-rate-limit # or: yarn add express-rate-limit
// src/server.js // ... other requires const rateLimit = require('express-rate-limit'); const smsRoutes = require('./routes/smsRoutes'); // ... app setup ... app.use(express.json()); // Apply rate limiting specifically to the SMS API routes const smsApiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: { success: false, error: 'Too many requests, please try again after 15 minutes.' }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Apply the limiter only to paths starting with /api/sms app.use('/api/sms', smsApiLimiter); // Mount the SMS routes (AFTER the limiter) app.use('/api/sms', smsRoutes); // ... health check, error handler, app.listen ...
-
HTTPS: Always use HTTPS in production to encrypt data in transit. This is typically handled at the load balancer or reverse proxy level (e.g., Nginx, Caddy, Cloudflare, AWS ELB), not directly in the Node.js application code.
-
Authentication/Authorization: If this service is exposed externally or consumed by multiple internal clients, implement proper authentication (e.g., API keys required in headers, JWT validation) to ensure only authorized systems can trigger SMS sends. This is beyond the scope of this basic setup but crucial for real-world deployments.