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-sdk
is a great alternative for more complex integrations.) - dotenv: A zero-dependency module that loads environment variables from a
.env
file 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
curl
or 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.
mkdir infobip-sms-campaign-api cd infobip-sms-campaign-api
-
Initialize Node.js Project: Run
npm init
and follow the prompts. You can accept the defaults by pressing Enter repeatedly, or customize as needed. This creates apackage.json
file.npm 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.
npm 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.json
You can create these using your file explorer or terminal commands:
mkdir 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.gitignore
file:# .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.# .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.env
file (which is not committed thanks to.gitignore
) and populate it with your actual credentials:# .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.com
Replace the placeholder values with your real key and base URL.
-
-
Configure Centralized Settings: Load environment variables securely using
dotenv
and centralize access.- Edit
config/index.js
:
// 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
dotenv
early 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
:
// 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 purposes
Why
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
:
// 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
:
// 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
:
// 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.js
is set up to catch errors passed vianext()
.- Edit
middleware/errorHandler.js
:
// 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:
node index.js # Or npm run dev if you installed nodemon
Open 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.curl -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:
{ ""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 OK
status and a JSON body similar to this (IDs and status will vary):{ ""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:
// 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):
// 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
.env
file (excluded from Git via.gitignore
). - Loaded at application startup via
dotenv
and accessed through theconfig/index.js
module. - Purpose:
INFOBIP_API_KEY
authenticates your requests.INFOBIP_BASE_URL
directs requests to your specific Infobip API endpoint cluster.
- API Key (
- API Interaction:
- Handled within
services/infobipService.js
. - Uses
axios
to 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
.env
locally 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
.env
files 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.js
catches errors from theaxios
request. 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.js
uses atry...catch
block. 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.log
andconsole.error
. Sufficient for basic development. - Production: Implement a structured logger like
winston
orpino
.- 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):
npm install winston
// 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.Modifynpm install axios-retry
services/infobipService.js
:// 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