code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

Send SMS with Infobip API in Node.js: Complete Express Integration Guide

Build a robust Node.js application using Express to send SMS messages via the Infobip API. This step-by-step tutorial covers API authentication, 10DLC compliance, error handling, and production-ready logging with Winston.

Send SMS with Infobip API in Node.js: Complete Express Integration Guide

Build a robust Node.js application using Express to send SMS messages via the Infobip API for marketing campaigns. This step-by-step tutorial covers API authentication, 10DLC compliance, error handling, and production-ready logging with Winston.

You'll create a backend service that accepts requests to send targeted SMS messages, integrates securely with Infobip, handles errors gracefully, and follows production-ready best practices.

What you'll build:

  • Express API endpoint to trigger SMS sends
  • Secure Infobip API integration using API keys
  • Input validation and error handling
  • Structured application architecture
  • Production logging and API security

Technology Stack:

  • Node.js: JavaScript runtime environment
  • Express: Minimal Node.js web framework
  • Axios: Promise-based HTTP client for Infobip API requests
  • dotenv: Environment variable management for secure configuration
  • Winston: Structured logging library
  • Infobip API: SMS messaging service

System Architecture:

mermaid
graph LR
    A[Client/Trigger<br>(e.g., Frontend,<br>Scheduler, etc.)] --> B{Node.js/Express App<br>(API Endpoint)};
    B --> C[Infobip API<br>(SMS Sending)];
    subgraph B [Node.js/Express App]
        direction TB
        B1[Validate Request] --> B2[Build Infobip Payload];
        B2 --> B3[Call Infobip API];
        B3 --> B4[Handle Response/Error];
    end

Prerequisites:

  • Node.js and npm: Installed on your development machine (Download Node.js)
  • Infobip Account: Register at infobip.com/signup
  • Infobip API Key and Base URL: Obtain from your Infobip account dashboard
  • Verified Phone Number (Free Trial): Free trial accounts can only send SMS to verified numbers
  • ⚠️ Critical: 10DLC Campaign Registration (US Traffic): For US Application-to-Person (A2P) SMS using 10-digit long codes, you must register a Brand and Campaign through Infobip. This is mandatory and enforced by US carriers. Sending without proper registration will cause message failures. See Infobip 10DLC Documentation for the registration process. This guide assumes you have an approved campaign.

Final Outcome:

You'll have a functional Express application with a secured API endpoint (/api/campaign/send-sms) that:

  • Accepts destination phone number and message text
  • Validates input
  • Sends SMS via Infobip
  • Logs actions and errors
  • Returns structured results

Configuration uses environment variables for security.


1. Node.js Project Setup for Infobip SMS Integration

Initialize the Node.js project and install dependencies.

  1. Create Project Directory: Create a new directory and navigate into it:

    bash
    mkdir infobip-sms-campaign-app
    cd infobip-sms-campaign-app
  2. Initialize npm: Initialize the project (accept defaults or customize):

    bash
    npm init -y
  3. Install Dependencies: Install Express, Axios, dotenv, and Winston:

    bash
    npm install express axios dotenv winston
  4. Create Project Structure: Set up the directory structure:

    bash
    mkdir src
    mkdir src/routes
    mkdir src/services
    mkdir src/controllers
    mkdir src/config
    mkdir src/middleware
    touch src/server.js
    touch src/routes/campaignRoutes.js
    touch src/services/infobipService.js
    touch src/controllers/campaignController.js
    touch src/config/logger.js
    touch src/middleware/auth.js
    touch .env
    touch .gitignore

    Directory structure:

    • src/: All source code
    • src/routes/: Express route definitions
    • src/services/: External service interaction logic (Infobip)
    • src/controllers/: Request handlers
    • src/config/: Configuration files (logger)
    • src/middleware/: Custom middleware (authentication)
    • src/server.js: Main application entry point
    • .env: Environment variables (Never commit to Git)
    • .gitignore: Files Git should ignore
  5. Configure .gitignore: Add these entries to prevent committing sensitive files:

    plaintext
    # .gitignore
    
    node_modules
    .env
    npm-debug.log
    *.log
  6. Set up Environment Variables: Add placeholders to .env (replace with actual values later):

    dotenv
    # .env
    
    # Server Configuration
    PORT=3000
    NODE_ENV=development
    
    # Infobip API Credentials
    INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
    INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL
    
    # Internal API Security
    # Generate a strong random key for production
    INTERNAL_API_KEY=YOUR_SECRET_API_KEY_FOR_INTERNAL_ACCESS

    Environment variables:

    • PORT: Express server port
    • NODE_ENV: Environment (affects logging behavior)
    • INFOBIP_API_KEY: API key from Infobip
    • INFOBIP_BASE_URL: Your unique base URL (e.g., xxxxx.api.infobip.com)
    • INTERNAL_API_KEY: Secret key to protect your API endpoint

2. Building the Infobip SMS Service with Error Handling

Create a dedicated service to handle all Infobip API interactions. This promotes separation of concerns and improves testability.

  1. Configure Logger (src/config/logger.js): Set up Winston for structured logging.

    javascript
    // src/config/logger.js
    const winston = require('winston');
    
    const logger = winston.createLogger({
        level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', // Log level based on environment
        format: winston.format.combine(
            winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
            winston.format.errors({ stack: true }), // Log stack traces
            winston.format.splat(),
            winston.format.json() // Log in JSON format
        ),
        defaultMeta: { service: 'infobip-sms-service' }, // Add service context
        transports: [
            // In production, you might write to files or external services
            // new winston.transports.File({ filename: 'error.log', level: 'error' }),
            // new winston.transports.File({ filename: 'combined.log' }),
        ],
    });
    
    // If we're not in production, log to the `console` with a simpler format.
    if (process.env.NODE_ENV !== 'production') {
        logger.add(new winston.transports.Console({
            format: winston.format.combine(
                winston.format.colorize(),
                winston.format.simple()
            ),
        }));
    }
    
    module.exports = logger;
  2. Edit src/services/infobipService.js: This file will contain the logic for building the request and calling the Infobip ""Send SMS"" API endpoint (/sms/2/text/advanced), now using the logger.

    javascript
    // src/services/infobipService.js
    
    const axios = require('axios');
    const logger = require('../config/logger'); // Use the configured logger
    
    // Retrieve Infobip credentials from environment variables
    const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
    const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
    
    /**
     * Builds the full API endpoint URL.
     * @returns {string} The complete URL for the Infobip Send SMS API.
     * @throws {Error} If INFOBIP_BASE_URL is not set.
     */
    const buildUrl = () => {
        if (!INFOBIP_BASE_URL) {
            logger.error('INFOBIP_BASE_URL environment variable is not set.');
            throw new Error('Infobip configuration error: Base URL is missing.');
        }
        return `https://${INFOBIP_BASE_URL}/sms/2/text/advanced`;
    };
    
    /**
     * Constructs the necessary HTTP headers for Infobip API authentication.
     * @returns {object} Headers object including Content-Type and Authorization.
     * @throws {Error} If INFOBIP_API_KEY is not set.
     */
    const buildHeaders = () => {
        if (!INFOBIP_API_KEY) {
            logger.error('INFOBIP_API_KEY environment variable is not set.');
            throw new Error('Infobip configuration error: API Key is missing.');
        }
        return {
            'Authorization': `App ${INFOBIP_API_KEY}`,
            'Content-Type': 'application/json',
            'Accept': 'application/json',
        };
    };
    
    /**
     * Constructs the request body payload for the Infobip Send SMS API.
     * @param {string} destinationNumber - The recipient's phone number in E.164 format (e.g., +447...).
     * @param {string} messageText - The content of the SMS message.
     * @param {string} [sender] - Optional: The sender ID (Alphanumeric or Short Code/Long Code). **Crucially, this MUST be a valid, registered Sender ID appropriate for the destination country and regulations (e.g., pre-registered 10DLC for US A2P). Using an invalid/unregistered sender will likely cause message failure. Defaulting to 'InfoSMS' is often NOT valid for many regions/use cases.** Verify requirements with Infobip documentation and local regulations.
     * @returns {object} The request body payload.
     * @throws {Error} If destinationNumber or messageText is missing.
     */
    const buildRequestBody = (destinationNumber, messageText, sender) => {
        if (!destinationNumber || !messageText) {
            logger.warn('Attempted to build request body with missing destination or text.', { destinationNumber, messageText });
            throw new Error('Destination number and message text are required.');
        }
    
        const payload = {
            messages: [
                {
                    destinations: [{ to: destinationNumber }],
                    text: messageText,
                    // Only include 'from' if a valid sender is provided.
                    // Relying on Infobip's default/configured sender might be safer if unsure.
                    ...(sender && { from: sender }),
                    // Add campaignId, notifyUrl etc. here if required by your campaign setup
                },
            ],
            // tracking: { // Optional: Add tracking if needed for campaign analytics
            //    track: 'URL',
            //    type: 'MY_CAMPAIGN_TRACKER'
            // }
        };
        logger.debug('Built Infobip request payload.', { payload }); // Log payload at debug level
        return payload;
    };
    
    /**
     * Sends an SMS message using the Infobip API.
     * @param {string} destinationNumber - The recipient's phone number (E.164 format recommended).
     * @param {string} messageText - The message content.
     * @param {string} [sender] - Optional: The sender ID (see buildRequestBody docs for critical usage notes).
     * @returns {Promise<object>} A promise that resolves with the relevant part of the Infobip API response (e.g., message status and ID).
     * @throws {Error} If the API call fails or returns an error status.
     */
    const sendSms = async (destinationNumber, messageText, sender) => {
        let url, headers, requestBody;
        try {
            url = buildUrl();
            headers = buildHeaders();
            requestBody = buildRequestBody(destinationNumber, messageText, sender);
    
            logger.info(`Attempting to send SMS to ${destinationNumber} via Infobip...`);
    
            const response = await axios.post(url, requestBody, { headers: headers });
    
            logger.info('Infobip API Response Status:', { status: response.status });
            // Log only essential parts of the response, not the whole potentially large object
            logger.debug('Infobip API Response Body:', { data: response.data });
    
            // Basic check for success indication in the response structure
            const messageInfo = response.data?.messages?.[0];
            if (messageInfo) {
                const status = messageInfo.status;
                if (status?.groupName === 'PENDING' || status?.groupName === 'DELIVERED') {
                    logger.info(`SMS submitted successfully for ${destinationNumber}. Status: ${status.name}, Message ID: ${messageInfo.messageId}`);
                } else {
                    logger.warn(`SMS submission for ${destinationNumber} resulted in status: ${status?.name || 'Unknown'} (${status?.description || 'N/A'}), Group: ${status?.groupName || 'Unknown'}`, { messageId: messageInfo.messageId });
                }
                // Return curated info
                return {
                    messageId: messageInfo.messageId,
                    status: status,
                    to: messageInfo.to
                };
            } else {
                logger.warn(`Unexpected response structure received from Infobip for ${destinationNumber}.`, { responseData: response.data });
                // Return raw data if structure is unknown, but flag it
                return { rawResponse: response.data, warning: ""Unexpected response structure"" };
            }
    
        } catch (error) {
            logger.error('Error sending SMS via Infobip.', {
                message: error.message,
                url: url, // Log the URL attempted
                destination: destinationNumber, // Log relevant context
                stack: error.stack, // Include stack trace
            });
    
            let errorMessage = 'Failed to send SMS due to an internal error.'; // Generic message
    
            if (error.response) {
                // The request was made and the server responded with a non-2xx status code
                logger.error('Infobip API Error Response:', {
                    status: error.response.status,
                    headers: error.response.headers,
                    data: error.response.data, // Log the full error body from Infobip
                });
    
                // Attempt to extract a meaningful error message from Infobip's response
                const serviceException = error.response.data?.requestError?.serviceException;
                if (serviceException) {
                    errorMessage = `Infobip API Error: ${serviceException.text || 'Unknown error'} (ID: ${serviceException.messageId || 'N/A'})`;
                } else {
                    errorMessage = `Infobip API request failed with status ${error.response.status}.`;
                }
                 throw new Error(errorMessage); // Throw curated error message
    
            } else if (error.request) {
                // The request was made but no response was received
                logger.error('Infobip Error: No response received.', { request: error.request });
                errorMessage = 'No response received from Infobip API. Check network connectivity and Base URL.';
                 throw new Error(errorMessage);
            } else {
                // Something happened in setting up the request that triggered an Error
                errorMessage = `Error setting up Infobip request: ${error.message}`;
                 throw new Error(errorMessage); // Throw the setup error message
            }
        }
    };
    
    module.exports = {
        sendSms,
        // Expose helpers for potential testing
        _buildUrl: buildUrl,
        _buildHeaders: buildHeaders,
        _buildRequestBody: buildRequestBody
    };
    • Logging: Replaced console.* with logger.* calls using the Winston logger. Added more contextual information to logs.
    • Error Handling: Logs detailed technical errors server-side and throws a more user-friendly (but still informative) error message for the controller layer.
    • Sender ID: Added stronger warnings about the sender parameter in the JSDoc and implementation notes. It's now optional and only included if explicitly provided, encouraging reliance on Infobip's configured defaults if unsure.
    • Response Handling: Returns a curated object with key information (messageId, status, to) instead of the full raw response on success.

3. Creating the Express API Endpoint

Create the Express controller and route that will use the infobipService.

  1. Edit src/controllers/campaignController.js: This controller handles the incoming HTTP request, performs validation, calls the service, and sends back a curated response.

    javascript
    // src/controllers/campaignController.js
    
    const infobipService = require('../services/infobipService');
    const logger = require('../config/logger');
    
    // Basic E.164 format regex (simplified: starts with +, then digits)
    // For robust validation, consider libraries like 'libphonenumber-js'
    const E164_REGEX = /^\+[1-9]\d{1,14}$/;
    
    /**
     * Controller function to handle sending an SMS message.
     * POST /api/campaign/send-sms
     * Body: { "to": "+14155552671", "text": "messageContent", "sender": "OptionalSenderID" }
     */
    const sendCampaignSms = async (req, res) => {
        const { to, text, sender } = req.body; // Include optional sender
    
        // Input Validation
        const errors = [];
        if (!to) {
            errors.push('Missing required field: `to` (phone number)');
        } else if (!E164_REGEX.test(to)) {
            // Add more robust validation if needed (e.g., using libphonenumber-js)
            errors.push('Invalid phone number format for `to`. Use E.164 format (e.g., +14155552671).');
        }
        if (!text) {
            errors.push('Missing required field: `text` (message content)');
        } else if (text.length > 1600) { // Example: Arbitrary limit to prevent abuse
            errors.push('Message text exceeds maximum allowed length (1600 characters).');
        }
        // Optional: Validate 'sender' format if provided
    
        if (errors.length > 0) {
            logger.warn('Invalid request to send SMS.', { errors, requestBody: req.body });
            return res.status(400).json({
                success: false,
                message: 'Invalid request payload.',
                errors: errors,
            });
        }
    
        try {
            // Pass validated data to the service
            const result = await infobipService.sendSms(to, text, sender); // Pass sender if provided
    
            // Service logs details. Controller sends client-friendly, curated response.
            res.status(200).json({
                success: true,
                message: 'SMS submitted successfully to Infobip.',
                // Return only necessary info, e.g., messageId for tracking
                details: {
                    messageId: result?.messageId,
                    statusGroup: result?.status?.groupName,
                    recipient: result?.to,
                }
            });
    
        } catch (error) {
            // The service layer already logged the detailed technical error.
            // Log the error context at the controller level.
            logger.error('Error processing send SMS request in controller.', {
                 errorMessage: error.message,
                 recipient: to, // Log context
                 stack: error.stack // Log stack trace for controller-level issues
                });
    
            // Send a generic server error response to the client. Avoid exposing internal details.
            res.status(500).json({
                success: false,
                message: 'Failed to send SMS due to a server error. Please check server logs for details or contact support.',
                // Do NOT include error.message directly here in production
                // errorId: 'SOME_TRACKING_ID' // Optional: provide an ID for correlation
            });
        }
    };
    
    module.exports = {
        sendCampaignSms,
    };
    • Validation: Added basic E.164 regex validation for the to field and a length check for text. Returns specific error messages using backticks for field names.
    • Curated Responses: Success response now includes only messageId, statusGroup, and recipient from the service result. Error response is generic, advising to check logs, and explicitly does not include the raw error.message.
    • Logging: Logs validation failures and errors occurring within the controller itself.
  2. Edit src/routes/campaignRoutes.js: Define the Express route and link it to the controller function.

    javascript
    // src/routes/campaignRoutes.js
    
    const express = require('express');
    const campaignController = require('../controllers/campaignController');
    // Optional: Import authentication middleware
    // const { requireApiKey } = require('../middleware/auth');
    
    const router = express.Router();
    
    // Define the route for sending SMS
    // POST /api/campaign/send-sms
    // Consider adding authentication middleware here:
    // router.post('/send-sms', requireApiKey, campaignController.sendCampaignSms);
    router.post('/send-sms', campaignController.sendCampaignSms);
    
    // You could add more campaign-related routes here (e.g., getting campaign status)
    
    module.exports = router;
  3. Edit src/server.js: Set up the main Express application, load environment variables, configure middleware (including logging), and mount the routes.

    javascript
    // src/server.js
    
    // Load environment variables from .env file FIRST
    require('dotenv').config();
    
    const express = require('express');
    const helmet = require('helmet'); // For security headers
    const campaignRoutes = require('./routes/campaignRoutes');
    const logger = require('./config/logger'); // Import the logger
    
    // Create the Express application
    const app = express();
    
    // Security Middleware
    app.use(helmet()); // Set various security HTTP headers
    app.use(express.json()); // Middleware to parse JSON request bodies
    app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies
    
    // HTTP Request Logging Middleware (using Winston)
    app.use((req, res, next) => {
        // Log basic request info
        logger.info(`Incoming Request: ${req.method} ${req.path}`, {
            ip: req.ip,
            userAgent: req.get('User-Agent'),
            body: req.body // Be cautious logging full bodies in production (PII/secrets)
        });
        // Log response finish
        res.on('finish', () => {
            logger.info(`Request Finished: ${req.method} ${req.path} - Status: ${res.statusCode}`, {
                duration: `${Date.now() - res.locals.startTime}ms` // Requires setting startTime earlier
            });
        });
        res.locals.startTime = Date.now(); // Store start time for duration calculation
        next();
    });
    
    // Mount the campaign routes under the /api/campaign prefix
    // Make sure to apply any necessary authentication middleware here or in the router itself
    app.use('/api/campaign', campaignRoutes);
    
    // Simple health check endpoint
    app.get('/health', (req, res) => {
        res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
    });
    
    // 404 Handler for undefined routes
    app.use((req, res, next) => {
        res.status(404).json({ message: 'Resource not found at ' + req.originalUrl });
    });
    
    // Centralized Error Handler Middleware (Logs errors using Winston, sends generic response)
    // This catches errors passed via next(err) or uncaught synchronous errors in route handlers
    // Note: Asynchronous errors need to be caught and passed to next() or handled in controllers/services
    app.use((err, req, res, next) => {
        // Log the error using Winston
        logger.error('Unhandled error caught by central handler:', {
            message: err.message,
            stack: err.stack,
            url: req.originalUrl,
            method: req.method,
            ip: req.ip,
        });
    
        // Avoid sending stack traces or detailed errors to the client in production
        const statusCode = err.status || 500; // Use error status or default to 500
        const responseMessage = process.env.NODE_ENV === 'production'
            ? 'Internal Server Error'
            : err.message; // Show more detail in dev
    
        res.status(statusCode).json({
             message: 'An unexpected error occurred.',
             error: responseMessage // Provide minimal info
             });
    });
    
    
    // Start the server
    const PORT = process.env.PORT || 3000; // Default to 3000 if PORT not set
    app.listen(PORT, () => {
        logger.info(`Server listening on port ${PORT}`);
        logger.info(`Current environment: ${process.env.NODE_ENV || 'development'}`);
        // Verify essential environment variables are loaded
        if (!process.env.INFOBIP_API_KEY || !process.env.INFOBIP_BASE_URL) {
             logger.warn('Infobip API Key or Base URL not found in environment variables. API calls will fail.');
        }
         if (!process.env.INTERNAL_API_KEY && process.env.NODE_ENV === 'production') {
             logger.warn('INTERNAL_API_KEY is not set. The API endpoint security is compromised in production!');
         }
    });
    
    // Handle unhandled promise rejections
    process.on('unhandledRejection', (reason, promise) => {
        logger.error('Unhandled Rejection at:', { promise, reason: reason.stack || reason });
        // Optionally exit process: process.exit(1);
    });
    
    // Handle uncaught exceptions
    process.on('uncaughtException', (error) => {
        logger.error('Uncaught Exception:', { message: error.message, stack: error.stack });
        // Graceful shutdown logic might go here
        process.exit(1); // Mandatory exit after uncaught exception
    });
    • dotenv.config(): Still first.
    • Logging: Integrated Winston for request logging and centralized error handling.
    • Security: Added helmet middleware.
    • Error Handling: Enhanced the final error handler to use Winston and provide less detail in production. Added global handlers for unhandledRejection and uncaughtException.
    • Startup Checks: Added warnings if essential keys (Infobip or internal API key) are missing.
  4. Update package.json (Optional but Recommended): Add scripts to easily start the server.

    json
    // package.json (add inside "scripts")
    "scripts": {
      "start": "node src/server.js",
      "dev": "nodemon src/server.js", // If you install nodemon: npm install --save-dev nodemon
      "lint": "eslint .", // Example if using ESLint
      "test": "echo \"Error: no test specified\" && exit 1" // Keep or replace
    },

4. Infobip API Authentication and Configuration

This section details how to get your Infobip credentials and configure the application.

  1. Obtain Infobip API Key and Base URL:

    • Log in to your Infobip account portal.
    • Navigate to the Developers section or API Keys management area.
    • Base URL: Find your unique API Base URL (e.g., [your-unique-id].api.infobip.com). Copy this value.
    • API Key: Generate a new API key. Give it a descriptive name (e.g., nodejs-campaign-app). Copy the generated key value immediately.
    • Security Note: Treat your API key like a password. Use environment variables; do not commit it to version control.
  2. Update .env File: Replace the placeholder values in your .env file with the actual Base URL, API Key, and a secure internal API key.

    dotenv
    # .env (Example values - Replace with your actual credentials!)
    
    PORT=3000
    NODE_ENV=development
    
    # Infobip API Credentials
    INFOBIP_API_KEY=abcdef1234567890abcdef1234567890-abcdef12-abcd-1234-abcd-abcdef123456
    INFOBIP_BASE_URL=z9x8y7.api.infobip.com
    
    # Internal API Security (Generate a strong random string for this)
    INTERNAL_API_KEY=Str0ngS3cr3tK3yF0rYourAPI!
  3. Environment Variable Purpose:

    • PORT: Network port the Express server listens on.
    • NODE_ENV: Set to production in deployed environments.
    • INFOBIP_API_KEY: Authenticates requests to the Infobip API (Authorization: App <key>).
    • INFOBIP_BASE_URL: Correct domain for Infobip API endpoints (e.g., yoursubdomain.api.infobip.com). Does not include https://.
    • INTERNAL_API_KEY: Used to secure your application's API endpoint (see Section 7 - Note: Section 7 is not present in the provided text).
  4. Campaign Registration (Crucial Reminder):

    • As highlighted in the prerequisites, simply having an API key is often insufficient, especially for A2P 10DLC in the US. You must have a registered and approved campaign linked to your sender number(s).
    • Use the Infobip portal or the Number Registration API to register your Brand and Campaign. This involves detailing your use case, providing message samples, and explaining opt-in procedures.
    • Allow time for Infobip Compliance review and approval.
    • Only after approval can you reliably send messages using the associated sender ID/number. You might need to include a campaignId in the API request body (src/services/infobipService.js). Verify the requirements for your specific approved campaign. Failure to comply is a primary reason for message delivery issues.

5. Production Error Handling with Winston Logging

Our implementation now includes structured logging with Winston and improved error handling. Let's discuss retries.

  • Consistent Error Strategy: Errors are caught at the lowest level possible (e.g., infobipService). Detailed technical info is logged there using Winston. A curated error is thrown upwards. The controller catches this, logs the context, and sends a generic, safe error response to the client. Centralized handlers catch uncaught exceptions/rejections.
  • Logging:
    • We now use Winston configured in src/config/logger.js. It provides structured JSON logging, different levels based on NODE_ENV, and timestamping.
    • Logs include context like service name, request details, and error stacks.
    • In production, configure transports to write logs to files or stream them to external aggregation services (ELK, Datadog, Splunk).
  • Retry Mechanisms:
    • Network glitches or temporary Infobip issues (like 503 Service Unavailable) might cause transient failures. Implementing retries can improve reliability for these cases.
    • Use libraries like async-retry or axios-retry. axios-retry integrates directly with Axios, while async-retry is a more general-purpose promise retry library.
    • Caution: Only retry on specific, recoverable error types (network errors, 5xx server errors). Do not retry on client errors (4xx) or non-recoverable errors.

Frequently Asked Questions

Why is input validation important for sending SMS via API?

Input validation prevents sending messages to invalid numbers, exceeding character limits, or passing malicious data to the Infobip API. It improves security and reliability.

How to send SMS messages with Infobip API?

Use the Infobip SMS API's `/sms/2/text/advanced` endpoint. Make a POST request to this endpoint with the necessary authorization and request body containing recipient number, message text, and optionally, the sender ID.

What is the role of Express.js in the Infobip SMS app?

Express.js acts as the web framework for the Node.js application. It handles routing, middleware (like authentication), and HTTP request/response management. It simplifies building robust and scalable web APIs for sending SMS.

Why does Infobip campaign registration matter for US SMS?

US regulations require Application-to-Person (A2P) 10DLC messages to be sent under registered campaigns. Registering a brand and campaign with Infobip ensures compliance, preventing message filtering and failure.

When should I use a custom sender ID with Infobip?

Use a custom sender ID only if it's registered and compliant with regulations for your target region. If unsure, rely on Infobip's default configured sender ID to avoid delivery issues.

How to integrate Winston logger in Node.js Express app?

Install Winston (`npm install winston`) and configure a logger instance. Use `logger.*` (e.g., `logger.info`, `logger.error`) to replace `console.*`. Set up transports for different environments (console, file, external services).

What is the purpose of the .env file in the Infobip integration?

The `.env` file stores sensitive configuration data, such as your Infobip API key, base URL, and any internal API keys for your application. It is crucial for security to never commit the `.env` file to version control.

How to handle errors when sending SMS with Infobip?

Implement comprehensive error handling at each level: service, controller, and global handlers. Log detailed errors server-side using Winston but provide generic error messages to clients. Use Axios interceptors or dedicated retry libraries for handling transient network issues.

What is the technology stack for the Infobip SMS app?

The project uses Node.js with Express, Axios for making API requests, dotenv for environment variables, Winston for logging, and the Infobip API for sending SMS messages.

How to set up a Node.js Express project for Infobip SMS?

Create a project directory, initialize npm (`npm init -y`), install required packages (`npm install express axios dotenv winston`), structure your project (routes, services, controllers), and configure your `.env` file with credentials.

When should I implement retry mechanisms for Infobip API calls?

Implement retries for transient errors like network issues or 5xx server errors from Infobip. Use libraries like `async-retry` or `axios-retry`. Never retry on client errors (4xx) like validation failures.

How to structure a Node.js Express application for maintainability?

Organize code into modules with distinct responsibilities: routes for defining API endpoints, controllers for handling requests, services for interacting with external APIs (like Infobip), and middleware for cross-cutting concerns.

What is the project structure for the Node.js Infobip SMS app?

The recommended structure includes `src/` for source code, containing folders for routes, services, controllers, config, and middleware. The `server.js` file serves as the main entry point.

Can I use a free trial Infobip account for sending SMS?

Yes, but free trials usually limit sending to the phone number verified during signup. Check Infobip's free trial terms for limitations.