code examples
code examples
Build Node.js Marketing Campaigns with Infobip & Express
A guide to building a Node.js/Express backend for sending SMS via the Infobip API, covering setup, core logic, error handling, and deployment considerations.
This guide provides a complete walkthrough for building a robust backend system using Node.js and Express to send SMS marketing messages via the Infobip API. We'll cover everything from project setup and core functionality to security, error handling, deployment, and testing, laying a solid foundation for building towards a production-ready application.
By the end of this tutorial, you'll have a functional Express API capable of accepting requests to send targeted SMS messages through Infobip. It covers essential aspects like configuration management, basic error handling, and security considerations, serving as a solid foundation for building more complex marketing automation workflows.
Project Overview and Goals
What We're Building:
We will construct a Node.js application using the Express framework. This application will expose an API endpoint designed to receive requests containing a phone number and a message. Upon receiving a valid request, the application will use the Infobip API to send an SMS message to the specified recipient.
Problem Solved:
This project provides a scalable and maintainable backend solution for businesses needing to integrate SMS marketing or notifications into their workflows. It abstracts the complexities of direct API interaction with Infobip into a simple, reusable service within your Node.js application.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications. Chosen for its asynchronous, event-driven nature, making it efficient for I/O operations like API calls.
- Express: A minimal and flexible Node.js web application framework. Provides a robust set of features for building web APIs quickly and easily.
- Infobip SMS API: A powerful cloud communication platform API enabling SMS sending globally. Chosen for its reliability, developer-friendly documentation, and feature set.
- Axios: A popular promise-based HTTP client for Node.js. Used for making requests to the Infobip API. (We chose Axios for this guide to demonstrate direct HTTP interaction and keep dependencies minimal; the official
infobip-api-node-sdkis a great alternative for more complex integrations.) - dotenv: A zero-dependency module that loads environment variables from a
.envfile intoprocess.env. Essential for managing sensitive configuration like API keys.
System Architecture:
The basic architecture is straightforward:
+-----------------+ +----------------------+ +----------------+
| Client (e.g., |----->| Node.js/Express API |----->| Infobip API |
| Frontend/Admin) | | (Our Application) | | (sms/2/text/..)|
+-----------------+ +----------------------+ +----------------+
(HTTP Request) (Validates & Formats) (Sends SMS)
|
+-> (Optional: Database for contacts/logs)
- A client (like a web frontend, mobile app, or another backend service) sends an HTTP POST request to our Express API endpoint (e.g.,
/api/campaigns/send-sms). - Our Express application receives the request, validates the input (phone number, message).
- The application retrieves necessary credentials (Infobip API Key, Base URL) from environment variables.
- It constructs the appropriate request payload for the Infobip SMS API.
- Using Axios, it sends the request to the Infobip API endpoint (
https://<your-base-url>/sms/2/text/advanced). - Infobip processes the request and sends the SMS message to the recipient's phone.
- Infobip returns a response (success or error) to our Express application.
- Our application processes Infobip's response and sends an appropriate HTTP response back to the original client.
Prerequisites:
- Node.js and npm (or yarn) installed on your system.
- An active Infobip account. If you don't have one, you can sign up for a free trial.
- Your Infobip API Key and Base URL (details on obtaining these below).
- Basic understanding of JavaScript, Node.js, Express, and REST APIs.
- A code editor (like VS Code).
- A tool for making API requests (like
curlor Postman).
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 your project. Navigate into it.
bashmkdir infobip-sms-campaign-api cd infobip-sms-campaign-api -
Initialize Node.js Project: Run
npm initand follow the prompts. You can accept the defaults by pressing Enter repeatedly, or customize as needed. This creates apackage.jsonfile.bashnpm init -y # The -y flag accepts all default settings -
Install Dependencies: We need Express for the web server, Axios for HTTP requests, and dotenv for environment variables.
bashnpm install express axios dotenv -
Set up Project Structure: Create the following directories and files for a structured application:
infobip-sms-campaign-api/ ├── node_modules/ ├── config/ │ └── index.js # Centralized configuration ├── controllers/ │ └── campaignController.js # Request handling logic ├── middleware/ # For middleware like error handling, rate limiting │ └── errorHandler.js ├── routes/ │ └── campaignRoutes.js # API route definitions ├── services/ │ └── infobipService.js # Infobip API interaction logic ├── .env # Environment variables (DO NOT COMMIT) ├── .env.example # Example environment variables (Commit this) ├── .gitignore # Specifies intentionally untracked files ├── index.js # Main application entry point └── package.jsonYou can create these using your file explorer or terminal commands:
bashmkdir config controllers middleware routes services touch config/index.js controllers/campaignController.js middleware/errorHandler.js routes/campaignRoutes.js services/infobipService.js .env .env.example .gitignore index.js -
Configure
.gitignore: Prevent sensitive files and unnecessary directories from being committed to version control. Add the following to your.gitignorefile:text# .gitignore # Dependencies node_modules/ # Environment variables .env # Logs logs/ *.log npm-debug.log* yarn-debug.log* yarn-error.log* pids/ *.pid *.seed *.pid.lock # Optional files .DS_Store -
Set up Environment Variables:
-
Obtain Infobip Credentials:
- Log in to your Infobip account.
- Navigate to the Homepage or Dashboard.
- Your API Key and Base URL should be visible on the main page. The Base URL will look something like
xxxxxx.api.infobip.com. Note: Always copy the exact Base URL provided in your Infobip dashboard, as the specific format can vary. - Crucially, ensure you copy both the API Key and the entire Base URL.
-
Create
.env.example: Add the following structure to.env.example. This file serves as a template and should be committed.dotenv# .env.example # Server Configuration PORT=3000 # Infobip API Credentials INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY_HERE INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL_HERE #(e.g., xxxxxx.api.infobip.com) -
Create
.env: Create the.envfile (which is not committed thanks to.gitignore) and populate it with your actual credentials:dotenv# .env (DO NOT COMMIT THIS FILE) # Server Configuration PORT=3000 # Infobip API Credentials INFOBIP_API_KEY=abcdef1234567890abcdef1234567890-abcdef12-abcd-1234-abcd-abcdef123456 INFOBIP_BASE_URL=1a2b3c.api.infobip.comReplace the placeholder values with your real key and base URL.
-
-
Configure Centralized Settings: Load environment variables securely using
dotenvand centralize access.- Edit
config/index.js:
javascript// config/index.js require('dotenv').config(); // Load .env file contents into process.env const config = { port: process.env.PORT || 3000, infobip: { apiKey: process.env.INFOBIP_API_KEY, baseUrl: process.env.INFOBIP_BASE_URL, }, nodeEnv: process.env.NODE_ENV || 'development', // Track environment }; // Validate essential configuration if (!config.infobip.apiKey || !config.infobip.baseUrl) { console.error('FATAL ERROR: Infobip API Key or Base URL is not defined in the environment variables.'); process.exit(1); // Exit if critical config is missing } module.exports = config;Why this approach? Centralizing configuration makes it easier to manage settings and ensures required variables are present at startup. Loading
dotenvearly makes variables available throughout the application. - Edit
-
Set up the Main Express App: Initialize the Express application and set up basic middleware.
- Edit
index.js:
javascript// index.js const express = require('express'); const config = require('./config'); const campaignRoutes = require('./routes/campaignRoutes'); const errorHandler = require('./middleware/errorHandler'); const app = express(); // Middleware app.use(express.json()); // Enable parsing JSON request bodies // API Routes app.use('/api/campaigns', campaignRoutes); // Health Check Route app.get('/health', (req, res) => { res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() }); }); // Global Error Handler (Should be LAST middleware) app.use(errorHandler); // Add error handler // Start the server app.listen(config.port, () => { console.log(`Server running on port ${config.port} in ${config.nodeEnv} mode`); console.log(`Infobip Base URL configured: ${config.infobip.baseUrl}`); }); // Basic error handling for unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); // Application specific logging, throwing an error, or other logic here // Consider exiting gracefully in production after logging // process.exit(1); }); process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); // Application specific logging, cleanup, and graceful shutdown process.exit(1); // Mandatory shutdown after uncaught exception }); module.exports = app; // Export for testing purposesWhy
express.json()? This middleware is crucial for parsing incoming JSON request bodies, which is how we'll receive the phone number and message. - Edit
2. Implementing Core Functionality (Sending SMS)
Now, let's implement the logic to interact with the Infobip API. We'll encapsulate this in a dedicated service file.
-
Create the Infobip Service: This service will handle constructing and sending requests to Infobip.
- Edit
services/infobipService.js:
javascript// services/infobipService.js const axios = require('axios'); const config = require('../config'); /** * Constructs the full URL for the Infobip Send SMS API endpoint. * @returns {string} The API endpoint URL. */ const buildUrl = () => { // Ensure no double slashes if baseUrl already ends with one const baseUrl = config.infobip.baseUrl.replace(/\/$/, ''); return `https://${baseUrl}/sms/2/text/advanced`; }; /** * Constructs the necessary HTTP headers for Infobip API authentication. * @returns {object} Headers object including Authorization and Content-Type. */ const buildHeaders = () => { return { 'Authorization': `App ${config.infobip.apiKey}`, 'Content-Type': 'application/json', 'Accept': 'application/json', // Good practice to accept JSON responses }; }; /** * Constructs the request body for sending a single SMS message. * Follows the structure required by the /sms/2/text/advanced endpoint. * @param {string} destinationNumber - The recipient's phone number (E.164 format recommended). * @param {string} messageText - The text content of the SMS. * @returns {object} The request body object. */ const buildRequestBody = (destinationNumber, messageText) => { // Basic validation (more robust validation should happen in the controller) if (!destinationNumber || !messageText) { throw new Error('Destination number and message text are required.'); } return { messages: [ { destinations: [{ to: destinationNumber }], text: messageText, // You can add more options here, like 'from', 'notifyUrl', etc. // from: ""YourSenderID"" // Optional: Specify sender ID if configured }, ], // You could add bulkId, tracking options here if needed }; }; /** * Sends a single SMS message using the Infobip API. * @param {string} destinationNumber - The recipient's phone number. * @param {string} messageText - The text content of the SMS. * @returns {Promise<object>} A promise that resolves with the Infobip API response data. * @throws {Error} Throws an error if the API request fails. */ const sendSingleSms = async (destinationNumber, messageText) => { const url = buildUrl(); const headers = buildHeaders(); const requestBody = buildRequestBody(destinationNumber, messageText); console.log(`Sending SMS to ${destinationNumber} via Infobip URL: ${url}`); // Basic logging try { const response = await axios.post(url, requestBody, { headers }); console.log('Infobip API Success Response:', JSON.stringify(response.data, null, 2)); // Check for specific success indicators if needed, based on Infobip docs // For /sms/2/text/advanced, a 2xx status usually means accepted for processing. // The response body contains detailed status per message. if (response.status >= 200 && response.status < 300) { return response.data; // Return the full response body from Infobip } else { // This case might not be hit often with Axios default behavior, // but included for robustness. throw new Error(`Infobip API request failed with status: ${response.status}`); } } catch (error) { console.error('Infobip API Request Error:', error.message); if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.error('Error Response Body:', JSON.stringify(error.response.data, null, 2)); console.error('Error Response Status:', error.response.status); console.error('Error Response Headers:', error.response.headers); // Re-throw a more specific error or handle it const errorMessage = error.response.data?.requestError?.serviceException?.text || error.message; const errorStatus = error.response.status; const serviceError = new Error(`Infobip API Error (${errorStatus}): ${errorMessage}`); serviceError.status = errorStatus; // Attach status code if needed by error handler serviceError.infobipError = error.response.data; // Attach full error details throw serviceError; } else if (error.request) { // The request was made but no response was received console.error('Error Request Data:', error.request); throw new Error('Infobip API Error: No response received from server.'); } else { // Something happened in setting up the request that triggered an Error console.error('Error Message:', error.message); throw new Error(`Infobip API Setup Error: ${error.message}`); } } }; module.exports = { sendSingleSms, // Add other functions here later (e.g., sendBulkSms, getDeliveryReports) };Why separate service? This follows the principle of Separation of Concerns. The service handles how to talk to Infobip, while controllers (next step) handle what to do with incoming requests and when to call the service. This makes the code modular, testable, and easier to maintain. Why
async/await? It provides a cleaner syntax for handling asynchronous operations (like network requests) compared to raw Promises or callbacks. - Edit
3. Building the API Layer
Let's define the API route and the controller function that uses our infobipService.
-
Create the Controller: The controller receives the HTTP request, performs validation, calls the service, and sends the HTTP response.
- Edit
controllers/campaignController.js:
javascript// controllers/campaignController.js const infobipService = require('../services/infobipService'); /** * Handles the request to send a single SMS message. * Validates input, calls the Infobip service, and sends response. */ const sendSms = async (req, res, next) => { const { destinationNumber, message } = req.body; // --- Basic Input Validation --- // More robust validation (e.g., using Joi or express-validator) is recommended for production if (!destinationNumber || typeof destinationNumber !== 'string') { // Use next(error) for consistent error handling via middleware const error = new Error(""Missing or invalid 'destinationNumber' parameter (string required).""); error.status = 400; return next(error); } // Basic E.164 format check (adjust regex as needed for stricter validation) if (!/^\+?[1-9]\d{1,14}$/.test(destinationNumber)) { const error = new Error(""Invalid phone number format. E.164 format recommended (e.g., +14155552671).""); error.status = 400; return next(error); } if (!message || typeof message !== 'string' || message.trim() === '') { const error = new Error(""Missing or invalid 'message' parameter (non-empty string required).""); error.status = 400; return next(error); } // Add message length validation if necessary (SMS limits apply) try { const result = await infobipService.sendSingleSms(destinationNumber, message); // The Infobip response structure for /sms/2/text/advanced includes a `messages` array const messageStatus = result.messages && result.messages[0] ? result.messages[0].status : null; // Respond with success and relevant details from Infobip's response res.status(200).json({ message: 'SMS submitted successfully to Infobip.', infobipResponse: { bulkId: result.bulkId, messageId: result.messages && result.messages[0] ? result.messages[0].messageId : 'N/A', status: messageStatus ? { groupId: messageStatus.groupId, groupName: messageStatus.groupName, id: messageStatus.id, name: messageStatus.name, description: messageStatus.description, } : 'Status N/A', } }); } catch (error) { // Log the detailed error on the server (already done in service) console.error(`Error in sendSms controller processing: ${error.message}`); // Pass the error to the centralized error handler next(error); } }; module.exports = { sendSms, }; - Edit
-
Define the Route: Connect the API endpoint path to the controller function.
- Edit
routes/campaignRoutes.js:
javascript// routes/campaignRoutes.js const express = require('express'); const campaignController = require('../controllers/campaignController'); // Add rate limiting middleware later if needed // const { smsLimiter } = require('../middleware/rateLimiter'); const router = express.Router(); // POST /api/campaigns/send-sms // Apply rate limiting here if desired: router.post('/send-sms', smsLimiter, campaignController.sendSms); router.post('/send-sms', campaignController.sendSms); // Add other campaign-related routes here (e.g., get status, manage lists) module.exports = router; - Edit
-
Implement Global Error Handler: Ensure the error handler in
middleware/errorHandler.jsis set up to catch errors passed vianext().- Edit
middleware/errorHandler.js:
javascript// middleware/errorHandler.js const config = require('../config'); const errorHandler = (err, req, res, next) => { // Log the error internally (consider using a structured logger) console.error('Error caught by handler:', err.message); if (config.nodeEnv !== 'production') { console.error(err.stack); // Log stack trace in dev } if (err.infobipError) { console.error('Infobip Error Details:', JSON.stringify(err.infobipError, null, 2)); } const statusCode = err.status || 500; // Use error's status or default to 500 let message = 'An unexpected error occurred on the server.'; // Provide more specific (but safe) messages for client errors (4xx) if (statusCode >= 400 && statusCode < 500) { message = err.message || 'Bad Request.'; // Use the error message for client errors } // Avoid sending detailed internal error messages (like Infobip API specifics) in production else if (config.nodeEnv !== 'production') { message = err.message; // Show detailed message in dev/test } res.status(statusCode).json({ error: true, status: statusCode, message: message, }); }; module.exports = errorHandler;(Ensure
app.use(errorHandler);is present and is the LAST middleware inindex.js) - Edit
-
Testing the Endpoint: Start your server:
bashnode index.js # Or npm run dev if you installed nodemonOpen another terminal or use Postman to send a POST request:
-
Using
curl: Replace placeholders with your verified phone number (if using a free trial Infobip account) or any target number (if using a paid account) and your message. Use a valid E.164 format number.bashcurl -X POST http://localhost:3000/api/campaigns/send-sms \ -H 'Content-Type: application/json' \ -d '{ ""destinationNumber"": ""+14155552671"", ""message"": ""Hello from our Node.js Infobip App! Sent on [Date]"" }' -
Using Postman:
- Set the request type to
POST. - Enter the URL:
http://localhost:3000/api/campaigns/send-sms - Go to the ""Body"" tab, select ""raw"", and choose ""JSON"" from the dropdown.
- Enter the JSON payload:
json
{ ""destinationNumber"": ""+14155552671"", ""message"": ""Hello from our Node.js Infobip App via Postman! Sent on [Date]"" } - Click ""Send"".
- Set the request type to
-
Expected Success Response (JSON): You should receive a
200 OKstatus and a JSON body similar to this (IDs and status will vary):json{ ""message"": ""SMS submitted successfully to Infobip."", ""infobipResponse"": { ""bulkId"": ""some-bulk-id-from-infobip"", ""messageId"": ""some-message-id-from-infobip"", ""status"": { ""groupId"": 1, ""groupName"": ""PENDING"", ""id"": 26, ""name"": ""PENDING_ACCEPTED"", ""description"": ""Message accepted and pending processing."" } } }You should also receive the SMS on the target phone shortly after.
-
Example Error Response (JSON): If you provide an invalid phone number format:
json// Status: 400 Bad Request { ""error"": true, ""status"": 400, ""message"": ""Invalid phone number format. E.164 format recommended (e.g., +14155552671)."" }If the Infobip API key is wrong (resulting in a 401 from Infobip, caught and returned as 401 by our handler):
json// Status: 401 Unauthorized { ""error"": true, ""status"": 401, ""message"": ""Infobip API Error (401): Invalid login details"" // Message might vary slightly based on exact error }
-
4. Integrating with Third-Party Services (Infobip Focus)
This section consolidates the key integration points with Infobip, already touched upon:
- Configuration:
- API Key (
INFOBIP_API_KEY) and Base URL (INFOBIP_BASE_URL) are mandatory. - Obtained from the Infobip dashboard homepage after login.
- Stored securely in a
.envfile (excluded from Git via.gitignore). - Loaded at application startup via
dotenvand accessed through theconfig/index.jsmodule. - Purpose:
INFOBIP_API_KEYauthenticates your requests.INFOBIP_BASE_URLdirects requests to your specific Infobip API endpoint cluster.
- API Key (
- API Interaction:
- Handled within
services/infobipService.js. - Uses
axiosto make HTTP POST requests tohttps://<INFOBIP_BASE_URL>/sms/2/text/advanced. - Headers include
Authorization: App <INFOBIP_API_KEY>andContent-Type: application/json. - Request body is structured according to Infobip API specifications for sending messages.
- Handled within
- Secrets Management:
- The primary secret is the
INFOBIP_API_KEY. - Using
.envlocally is standard practice. - For production deployment, use the hosting provider's mechanism for setting environment variables securely (e.g., Heroku Config Vars, AWS Secrets Manager, Docker secrets). Never commit
.envfiles containing production keys.
- The primary secret is the
- Fallback Mechanisms (Conceptual):
- Retries: For transient network errors or specific Infobip error codes (like temporary throttling), implement a retry strategy (see Section 5).
- Queuing: For extended Infobip outages or high-volume sending, consider using a message queue (e.g., RabbitMQ, Redis Queue, AWS SQS). Your API endpoint would add the message job to the queue, and a separate worker process would consume jobs from the queue and attempt to send via Infobip. This decouples the API response from the actual sending attempt, improving responsiveness and resilience. (Implementation of queuing is beyond this basic guide).
5. Error Handling, Logging, and Retry Mechanisms
Production applications require robust error handling and logging.
-
Consistent Error Handling Strategy:
- Service Layer: The
infobipService.jscatches errors from theaxiosrequest. It logs detailed error information (status code, response body) to the console (or a proper logger). It then throws a new, potentially enriched error (attaching status code, etc.) upwards. - Controller Layer: The
campaignController.jsuses atry...catchblock. Validation errors are passed tonext(error). Errors from the service layer are caught and also passed tonext(error). - Global Error Handler: The
middleware/errorHandler.js(created in Step 3.3) acts as a centralized place to catch all errors passed vianext(). It logs the error and sends a consistently formatted, user-friendly JSON response to the client, avoiding leakage of sensitive internal details in production.
- Service Layer: The
-
Logging:
- Current: Using
console.logandconsole.error. Sufficient for basic development. - Production: Implement a structured logger like
winstonorpino.- Configure different log levels (debug, info, warn, error).
- Output logs to files or log management services (e.g., Datadog, Logstash, CloudWatch).
- Include contextual information (timestamps, request IDs, user info if applicable).
- Log in JSON format for easier parsing by log aggregation tools.
Example using Winston (conceptual setup):
bashnpm install winstonjavascript// Example logger setup (e.g., in config/logger.js) const winston = require('winston'); const config = require('./config'); // To access nodeEnv const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), // Log stack traces winston.format.json() // Log in JSON format ), defaultMeta: { service: 'infobip-sms-api' }, // Add service context transports: [ // Write all logs with level `error` and below to `error.log` new winston.transports.File({ filename: 'error.log', level: 'error' }), // Write all logs with level `info` and below to `combined.log` new winston.transports.File({ filename: 'combined.log' }), ], }); // If not in production then log to the `console` with simple format if (config.nodeEnv !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), })); } module.exports = logger; // Replace console.log/error with logger.info, logger.error etc. throughout the app // Example: logger.error('Infobip API Request Error:', { message: error.message, status: error.response?.status }); - Current: Using
-
Retry Mechanisms: Network issues or temporary service unavailability can cause API calls to fail. Implement retries with exponential backoff.
- Using
axios-retry(Recommended): A simple way to add retry logic to Axios requests.Modifybashnpm install axios-retryservices/infobipService.js:javascript// services/infobipService.js const axios = require('axios'); const axiosRetry = require('axios-retry').default; // Use .default for CJS const config = require('../config'); // Assuming logger setup, replace console with logger if implemented // const logger = require('../config/logger'); // Create an Axios instance for Infobip calls const infobipAxiosInstance = axios.create(); // Apply retry logic to the instance axiosRetry(infobipAxiosInstance, { retries: 3, // Number of retry attempts retryDelay: (retryCount, error) => { // logger.warn(`Retry attempt ${retryCount} for ${error.config.url} due to ${error.message}`); console.warn(`Retry attempt ${retryCount} for ${error.config.url} due to ${error.message}`); // Use console for now return retryCount * 1000; // Exponential backoff (1s, 2s, 3s) }, retryCondition: (error) => { // Retry on network errors or specific server errors (e.g., 5xx) // Avoid retrying on client errors (4xx) like invalid API key or bad request (400, 401, 403 etc.) const shouldRetry = axiosRetry.isNetworkOrIdempotentRequestError(error) || (error.response && error.response.status >= 500); if (!shouldRetry && error.response) { // logger.error(`Not retrying request due to status ${error.response.status}`); console.error(`Not retrying request due to status ${error.response.status}`); // Use console for now } return shouldRetry; }, shouldResetTimeout: true, // Reset timeout on retries }); // ... buildUrl, buildHeaders, buildRequestBody functions remain the same ... const sendSingleSms = async (destinationNumber, messageText) => { const url = buildUrl(); const headers = buildHeaders(); const requestBody = buildRequestBody(destinationNumber, messageText); // logger.info(`Sending SMS to ${destinationNumber} via Infobip URL: ${url}`); console.log(`Sending SMS to ${destinationNumber} via Infobip URL: ${url}`); // Use console for now try { // Use the Axios instance with retry logic configured const response = await infobipAxiosInstance.post(url, requestBody, { headers }); // logger.info('Infobip API Success Response:', { data: response.data }); console.log('Infobip API Success Response:', JSON.stringify(response.data, null, 2)); // Use console for now if (response.status >= 200 && response.status < 300) { return response.data; } else { throw new Error(`Infobip API request failed with status: ${response.status}`); } } catch (error) { // logger.error('Infobip API Request Error:', { message: error.message }); console.error('Infobip API Request Error:', error.message); // Use console for now if (error.response) { // logger.error('Error Response Body:', { data: error.response.data }); // logger.error('Error Response Status:', { status: error.response.status }); console.error('Error Response Body:', JSON.stringify(error.response.data, null, 2)); // Use console for now console.error('Error Response Status:', error.response.status); console.error('Error Response Headers:', error.response.headers); const errorMessage = error.response.data?.requestError?.serviceException?.text || error.message; const errorStatus = error.response.status; const serviceError = new Error(`Infobip API Error (${errorStatus}): ${errorMessage}`); serviceError.status = errorStatus; serviceError.infobipError = error.response.data; throw serviceError; } else if (error.request) { // logger.error('Error Request Data:', { request: error.request }); console.error('Error Request Data:', error.request); // Use console for now throw new Error('Infobip API Error: No response received from server.'); } else { // logger.error('Error Message:', { message: error.message }); console.error('Error Message:', error.message); // Use console for now throw new Error(`Infobip API Setup Error: ${error.message}`); } } }; module.exports = { sendSingleSms, // ... other exports ... };
- Using
Frequently Asked Questions
How to send SMS messages with Node.js and Infobip?
Use Node.js with the Express framework and integrate the Infobip SMS API. The Express app will receive requests with phone numbers and messages, validate the input, then use the Infobip API to send the SMS messages. This setup offers a scalable solution for incorporating SMS into various workflows.
What is the Infobip SMS API used for in this project?
The Infobip SMS API is the core communication component, enabling the Node.js application to send SMS messages globally. It's chosen for its reliability, comprehensive documentation, and robust feature set for sending SMS messages efficiently.
Why use Node.js for SMS marketing campaigns?
Node.js is an asynchronous, event-driven runtime environment. This characteristic enhances efficiency in I/O-bound operations, like frequent API calls required for SMS campaigns, making it well-suited for real-time communication.
When should I use the official Infobip Node.js SDK?
While this tutorial demonstrates direct HTTP interaction with Axios for simplicity, the official `infobip-api-node-sdk` is highly recommended for production or more complex integrations with Infobip's services, offering more features and easier management.
What is the purpose of dotenv in the Node.js project?
Dotenv is used for managing environment variables, which are essential for storing sensitive information like API keys. It loads variables from a .env file into process.env, ensuring secure handling of credentials.
How to integrate Infobip API into Express app?
First obtain API Key and Base URL from Infobip Dashboard. Store these securely in a .env file and load using dotenv in a config file. Construct the API URL and headers in an Infobip service file, which will then be called by controllers, abstracting away the complexities of Infobip interactions.
What is the role of Axios in the project?
Axios acts as the HTTP client, facilitating communication between the Node.js application and the Infobip API. It handles the construction and sending of POST requests to the API's send SMS endpoint. It simplifies the process of making API requests and handling responses.
How to handle errors when sending SMS via Infobip?
Implement a multi-layered approach. The Infobip service should catch Axios errors and provide specifics. The controller passes errors via next(error), and a global error handler catches and formats consistent JSON error responses for clients.
What is the project's system architecture for SMS campaigns?
A client sends an HTTP request to the Node.js/Express API. The API validates the request, interacts with the Infobip API to send the SMS, processes the response from Infobip, and then sends a corresponding response back to the client.
How to structure a Node.js project for Infobip integration?
Structure your project with directories like config, controllers, middleware, routes, and services. This promotes maintainability by separating concerns and keeps the codebase organized for easier scalability.
Why does this project use Express.js?
Express.js is a minimal and flexible Node.js web application framework, chosen for its ease of use and robust features. It simplifies the process of building web APIs and handling HTTP requests, allowing you to focus on core functionality.
When to implement retry mechanisms for sending SMS?
Retry mechanisms are crucial for handling transient network issues or temporary Infobip service disruptions. Implement retries with exponential backoff using libraries like axios-retry to enhance the resilience of your application.
How to manage Infobip API credentials securely?
Locally, use a .env file excluded from version control. In production, utilize your hosting platform's secure environment variable storage solutions to prevent exposing sensitive credentials.