code examples
code examples
Build a Node.js Express SMS Service with Infobip API Integration
Learn how to build a production-ready Node.js Express application to send SMS messages using the Infobip API. Complete guide with security, error handling, and validation.
Build a Node.js Express SMS Service with Infobip API Integration
Build a production-ready Node.js and Express application to send SMS messages using the Infobip API. This comprehensive guide covers creating a scalable SMS microservice for marketing campaigns, transactional notifications, and automated messaging with secure API integration, error handling, and validation.
You'll implement the official Infobip Node.js SDK for simplified API integration, configure secure environment variables for API authentication, build RESTful endpoints with Express, and implement production-grade error handling. By the end, you'll have a fully functional SMS service with security features and rate limiting.
Overview: Building an SMS microservice with Node.js and Infobip
Goal: Create a dedicated Node.js Express microservice to send SMS messages via the Infobip platform. This service exposes 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
.envfile 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 with carrier-grade infrastructure.
- The official Infobip SDK significantly reduces boilerplate code compared to direct HTTP requests, handles authentication details, and receives ongoing maintenance from Infobip.
dotenvis 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 with API access.
- 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 (required for trial accounts).
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.
Set up your Node.js SMS project
Initialize your Node.js project and install the necessary dependencies.
-
Create project directory: Open your terminal and create a new directory for the project.
bashmkdir infobip-sms-service cd infobip-sms-service -
Initialize Node.js project: Create a
package.jsonfile to manage project details and dependencies.bashnpm init -y(Alternatively, use
yarn init -yif you prefer Yarn) -
Install dependencies: Install Express for the web server, the Infobip SDK for SMS sending, and
dotenvfor managing environment variables.bashnpm install express @infobip-api/sdk dotenv(Or
yarn add express @infobip-api/sdk dotenv) -
Create project structure: Set up a basic structure for organization.
bashmkdir 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 .gitignoresrc/: Contains your 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 your 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_modulesand.envto your.gitignorefile to prevent committing them.text# .gitignore node_modules/ .env npm-debug.log* yarn-debug.log* yarn-error.log* -
Create basic Express server (
src/server.js): Create a minimal Express server to verify your setup works.javascript// src/server.js require('dotenv').config(); // Load .env variables early const express = require('express'); const smsRoutes = require('./routes/smsRoutes'); 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 (basic example) // Production apps need more robust error handling. app.use((err, req, res, next) => { console.error('Unhandled Error:', err); // In production, 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.envfile.express.json(): Middleware to parse incoming JSON request bodies.- Define a basic
/healthcheck endpoint. - Mount placeholder routes for
/api/sms. - Include a simple global error handler. 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.jsonscripts: Add a convenientstartscript to run the server.json{ "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. Install compatible versions or update them based on the latest stable releases.
You now have a basic Node.js Express project structure ready for implementing the SMS functionality.
Implement the Infobip SMS service layer
Encapsulate the Infobip SDK interaction within a dedicated service file.
-
Configure environment variables (
.env): Open the.envfile and add placeholders for your Infobip Base URL and API Key. Obtain these from your Infobip account dashboard in the next step.dotenv# .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 initializes the Infobip client and provides a function to send SMS.javascript// 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 characters 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 }), }, ], // 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) };- Import the
Infobipclient andAuthTypeenum from the SDK. - Check if the required environment variables are present before initializing the client.
- Instantiate the
infobipClientusing the Base URL and API Key from.env. - The
sendSmsfunction takes the recipient, message, and optional sender ID. - Perform basic validation, but enhance this with stricter checks (e.g., phone number format using regex or libraries like
libphonenumber-js, message length) for production use. - Construct the payload according to the Infobip API specification (
/sms/2/text/advancedendpoint, which the SDK uses). - Call
infobipClient.channels.sms.send()within atry...catchblock. - Log the response and status for debugging.
- Handle errors, logging relevant details from the Infobip error response if available.
- Import the
Build the Express API for SMS sending
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/sendroute. You'll add more robust validation in section 7.javascript// 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, // Export validation rules later };- Require the
infobipService. - Extract
recipientNumberandmessageTextfromreq.body. - Perform minimal validation initially. Replace this with dedicated validation middleware later for better structure and robustness.
- Call
infobipService.sendSmswithin atry...catchblock. - Return a
200 OKresponse with the Infobip result on success. - Return a
400 Bad Requestfor validation errors or500 Internal Server Errorfor service errors.
- Require the
-
Define SMS routes (
src/routes/smsRoutes.js): This file defines the specific endpoints under the/api/smsprefix.javascript// 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;- Require the
smsController. - Define a
POSTroute at/sendthat maps to thesendSingleSmscontroller function.
- Require the
Your API layer is now set up. The server.js file already mounts these routes under /api/sms.
Configure Infobip API credentials
Connect your application to your Infobip account.
-
Log in to Infobip: Go to the Infobip Portal and log in. 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:
- 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.
- In the Developers section or API documentation area, find your personalized Base URL. It usually looks like
-
Update
.envfile: Paste the copied values into your.envfile:dotenv# .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, set it as default in
.envor pass it in the API request body (senderIdfield).
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.
Implement production-grade error handling
Robust error handling and logging are vital for production systems.
- Service layer (
infobipService.js):- The
try...catchblock already catches errors from the SDK call. - 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). - Throw a new, cleaner error message for the controller layer.
- The
- Controller layer (
smsController.js):- The
try...catchblock catches errors thrown by the service layer. - Log the error message.
- Return appropriate HTTP status codes:
400 Bad Request/422 Unprocessable Entityfor client-side errors (missing parameters, invalid format – see validation section).500 Internal Server Errorfor server-side or downstream errors (Infobip API unavailable, unexpected exceptions).
- The response body includes a
success: falseflag and anerrormessage.
- 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:
- Currently using
console.logandconsole.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).
- Currently using
- Retry mechanisms (optional enhancement):
- For transient network errors or temporary Infobip issues (e.g., 5xx status codes), implement a retry strategy with exponential backoff to improve resilience. This is an optional enhancement for robustness, not part of the core initial setup.
- Libraries like
async-retrycan simplify this. Wrap theinfobipClient.channels.sms.sendcall 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 you 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 in 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}`);
}Design your SMS database schema (optional)
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: Store 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: Define marketing campaigns, messages, target segments, schedules.
Campaigns (campaign_id PK, name, message_template, target_segment, schedule_time, status, created_at)
- Message logs: Track 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: Manage unsubscribe requests.
OptOuts (phone_number PK, opted_out_at)
Implementation:
- 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 are 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.
Secure your SMS API with validation and rate limiting
Security is paramount, especially when handling user data and external APIs.
-
Secure API key management: Already covered by using
.envand environment variables in production. Never hardcode keys. -
Input validation:
-
The initial controller (
smsController.js) has minimal validation. Replace it usingexpress-validatorfor robust, declarative validation. -
Install the library:
bashnpm 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.javascript// 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 to 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.javascript// 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.bashnpm install express-rate-limit # or: yarn add express-rate-limitjavascript// 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.
Testing your Infobip SMS integration
Before deploying to production, test your SMS service thoroughly:
-
Test with curl or Postman:
bashcurl -X POST http://localhost:3000/api/sms/send \ -H "Content-Type: application/json" \ -d '{ "recipientNumber": "447123456789", "messageText": "Test message from Node.js SMS service", "senderId": "YourBrand" }' -
Verify response structure:
- Success:
200 OKwithsuccess: trueand Infobip response data - Validation error:
422 Unprocessable Entitywith error details - Server error:
500 Internal Server Errorwith error message
- Success:
-
Check Infobip dashboard:
- Log into the Infobip portal
- Navigate to the SMS logs section
- Verify message status (PENDING, DELIVERED, FAILED)
- Review delivery reports and error codes
-
Test error scenarios:
- Invalid phone number format
- Missing required fields
- Rate limit exceeded
- Invalid API credentials
Deploying your Node.js SMS service
Deploy your SMS service to a production environment:
Environment variables:
- Set
INFOBIP_BASE_URLandINFOBIP_API_KEYin your hosting environment - Configure
PORTif needed (default: 3000) - Set
NODE_ENV=productionfor production deployments
Hosting options:
- Docker: Containerize your application for consistent deployments
- Cloud platforms: Deploy to AWS (Elastic Beanstalk, ECS), Google Cloud (App Engine), Azure (App Service), or Heroku
- Serverless: Adapt to AWS Lambda or Google Cloud Functions for event-driven SMS sending
Production checklist:
- ✅ Enable HTTPS at the load balancer or reverse proxy level
- ✅ Configure structured logging (Winston, Pino) with log aggregation
- ✅ Set up monitoring and alerting for API errors and rate limits
- ✅ Implement authentication/authorization for API access
- ✅ Configure proper rate limiting based on your Infobip account limits
- ✅ Set up error tracking (Sentry, Rollbar) for production debugging
- ✅ Test failover and retry logic for transient errors
Frequently asked questions
How do I get an Infobip API key? Log into the Infobip Portal, navigate to the "Developers" section or account settings, and create a new API key. Copy the key immediately as you cannot view it again after creation.
What phone number format does Infobip require?
Infobip requires phone numbers in international E.164 format without the + prefix (e.g., 447123456789 for a UK number). Use the libphonenumber-js library to validate and format phone numbers correctly.
How much does it cost to send SMS via Infobip? SMS costs vary by destination country and message type. Check the Infobip pricing page or your account dashboard for current rates. Most countries charge per message segment (160 characters for GSM-7 encoding).
Can I send SMS to any country? Infobip supports SMS delivery to 190+ countries, but some countries have restrictions or require sender ID registration. Check Infobip's country-specific documentation for regulatory requirements.
How do I handle SMS delivery failures?
Check the status field in the Infobip API response. Common failure reasons include invalid phone numbers, insufficient account balance, or carrier rejections. Implement webhook handlers to receive delivery reports for message status updates.
What's the difference between sender ID and short code? A sender ID is an alphanumeric identifier (up to 11 characters) that appears as the message sender. A short code is a 5-6 digit number used primarily in the US for high-volume messaging. Sender ID availability and registration requirements vary by country.
How do I implement SMS opt-out compliance? Store opt-out requests in your database and check before sending. Include unsubscribe instructions in marketing messages (e.g., "Reply STOP to unsubscribe"). Process opt-out keywords automatically by implementing Infobip webhooks for incoming messages.
Can I schedule SMS messages for later delivery?
Yes, use the sendAt parameter in the Infobip API payload to schedule messages. Pass an ISO 8601 timestamp for the desired delivery time. Note that scheduling is subject to Infobip's scheduling limits.
Next steps for your SMS service
Enhance your SMS service with these features:
- Bulk SMS sending: Implement batch endpoints to send messages to multiple recipients efficiently
- Template management: Create reusable message templates with variable substitution
- Delivery webhooks: Configure Infobip webhooks to receive real-time delivery status updates
- Two-way SMS: Implement incoming message handlers for interactive SMS conversations
- A/B testing: Split test message content, timing, and sender IDs for optimization
- Analytics dashboard: Track send rates, delivery rates, costs, and campaign performance
- Integration with marketing automation: Connect with tools like HubSpot, Salesforce, or custom CRM systems
- Multi-channel support: Extend the service to support WhatsApp, Viber, and other Infobip channels
Frequently Asked Questions
How to send SMS messages with Infobip API?
Use the Infobip Node.js SDK and Express to build a service that interacts with the Infobip API. This allows you to send SMS messages programmatically by making API calls to your Infobip account, simplifying the integration process.
What is the Infobip Node.js SDK?
The Infobip Node.js SDK (@infobip-api/sdk) is a pre-built library that simplifies interaction with the Infobip API. It handles authentication and provides convenient methods for sending SMS messages and other communication tasks, reducing boilerplate code.
Why use Node.js and Express for SMS service?
Node.js and Express provide a popular, efficient, and well-supported foundation for building APIs and microservices. Their scalability makes them suitable for handling potentially high volumes of SMS requests.
When should I use a dedicated SMS microservice?
A dedicated microservice is beneficial when you need to centralize SMS sending logic, securely manage API credentials, provide a consistent interface, and simplify integration for multiple applications that require SMS capabilities.
Can I use a custom sender ID with Infobip?
Yes, you can use a custom alphanumeric sender ID (e.g., your brand name) to improve message recognition, subject to Infobip's guidelines and local regulations, which may require registration.
How to set up Infobip API credentials in Node.js?
Store your Infobip Base URL and API Key as environment variables (INFOBIP_BASE_URL and INFOBIP_API_KEY) in a .env file for development. Never hardcode these credentials directly in your code, and in production, use proper environment variable management within your deployment platform.
What is the purpose of dotenv in this project?
Dotenv is a package that loads environment variables from a .env file into process.env, making it easy to manage configuration values, especially API keys and other sensitive data that shouldn't be committed to version control.
How to handle errors when sending SMS with Infobip?
Implement try...catch blocks to handle errors from the Infobip SDK and service layer. Provide informative error messages and log details like the error response from Infobip for debugging. Consider using a dedicated error tracking service in production.
What data should I store for SMS marketing campaigns?
You should consider storing contact details (phone numbers, opt-in status), campaign information (messages, schedules), message logs (status, recipient, costs), and opt-out records for analytics, reporting, and compliance.
How to validate recipient phone numbers?
Use the isMobilePhone validator from express-validator or regular expressions to ensure phone numbers are in a valid international format and sanitize input to prevent vulnerabilities. Dedicated libraries like libphonenumber-js can help improve validation accuracy.
How can I improve SMS service security?
Secure your API keys, implement robust input validation using libraries like express-validator, use rate limiting (express-rate-limit) to prevent abuse, enforce HTTPS, and add authentication/authorization if your service is exposed externally.
What does express-rate-limit do?
Express-rate-limit is middleware that helps protect your API from abuse by limiting the number of requests from a given IP address within a specified time window. This can prevent overload and denial-of-service attacks.
How to structure a Node.js Express SMS service?
Organize your project with separate directories for routes, controllers, and services. Routes define API endpoints, controllers handle requests and interact with services, and services encapsulate the logic for sending SMS using the Infobip SDK.
Why is logging important for an SMS service?
Logging provides crucial information for debugging, monitoring, and troubleshooting. Use a structured logging library to record timestamps, log levels, request IDs, and error details, and configure appropriate log destinations for analysis.
What is the role of the /health endpoint?
The /health endpoint is a simple way to check the status and availability of your service. It typically returns a 200 OK response with a timestamp or other relevant information, allowing monitoring systems to verify that the service is running.