code examples

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

Send SMS with Node.js and Infobip API

Learn how to send SMS messages using Node.js and the Infobip API. Complete guide covering manual HTTP requests, official SDK integration, API layer setup, error handling, and production deployment.

Send SMS with Node.js and Infobip API

Send SMS messages programmatically using Node.js and the Infobip API. This complete guide shows you how to integrate Infobip SMS functionality into your Node.js application using either manual HTTP requests with Axios or the official Infobip SDK.

You'll learn to set up your Node.js project, authenticate with the Infobip API, send SMS messages using two different approaches, create an Express API endpoint for SMS delivery, implement production-ready error handling, and deploy your SMS service securely. Whether you're building a notification system, two-factor authentication, or marketing campaigns, this guide covers everything you need to send SMS with Node.js and Infobip.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications.
  • npm (or yarn): Package managers for Node.js.
  • Infobip API: The communication platform service for sending SMS messages.
  • Axios (Optional): A promise-based HTTP client for making requests manually.
  • @infobip-api/sdk (Optional): The official Infobip Node.js SDK for simplified API interaction.
  • Express (Optional): A minimal web framework for Node.js to create API endpoints.
  • dotenv: A module to load environment variables from a .env file.

You need basic understanding of JavaScript, Node.js, and REST APIs.

Prerequisites

Ensure you have the following before you begin:

  1. Node.js and npm (or yarn): Install Node.js v20 (Iron - Maintenance LTS) or v22 (Jod - Active LTS) on your development machine. Download from nodejs.org. Production applications should use Active LTS or Maintenance LTS versions.
  2. Infobip Account: Sign up for a free trial or use your existing account at infobip.com. Free trial accounts can send SMS only to your verified phone number.
  3. Infobip API Key and Base URL: Get these credentials to authenticate your API requests.

Why Use Infobip for SMS Integration?

Infobip provides a robust SMS API platform for sending transactional and marketing messages at scale. Developers choose Infobip for:

Common Use Cases:

  • Two-Factor Authentication (2FA): Send verification codes for secure login
  • Transactional Notifications: Order confirmations, shipping updates, appointment reminders
  • Marketing Campaigns: Promotional messages and customer engagement
  • Alert Systems: Critical notifications and system alerts

Key Benefits:

  • Global Reach: Send SMS to 190+ countries with carrier connections worldwide
  • High Deliverability: Direct carrier partnerships ensure reliable message delivery
  • Developer-Friendly: RESTful API with official SDKs for Node.js, Python, Java, PHP, and more
  • Scalability: Handle from a few messages to millions per day
  • Delivery Reports: Real-time status updates via webhooks for message tracking

This guide focuses on Node.js integration, but the concepts apply to other programming languages.

Getting Your Infobip API Credentials

Get two key pieces of information from your Infobip account:

  1. API Key: Authenticates your requests.
  2. Base URL: The specific API endpoint domain assigned to your account.

Steps to find your credentials:

  1. Log in to your Infobip account.
  2. Navigate to the homepage dashboard after login.
  3. Find your Base URL prominently displayed (e.g., xxxxx.api.infobip.com).
  4. Click your username or profile icon in the top-right corner.
  5. Look for API Keys or navigate to the Developers section.
  6. View existing API keys or generate a new one. Copy the API Key value securely. Treat this key like a password – never share it publicly or commit it to version control.

Keep these two values handy – you'll use them shortly.

1. Setting up the Project

Create a new Node.js project.

  1. Create Project Directory: Open your terminal and create a new directory for your project, then navigate into it.

    bash
    mkdir node-infobip-sms
    cd node-infobip-sms
  2. Initialize Node.js Project: Run npm init and follow the prompts. Accept the defaults by pressing Enter repeatedly, or customize the details. This creates a package.json file.

    bash
    npm init -y

    (The -y flag accepts all defaults)

  3. Install Core Dependencies: Install dotenv to manage your API credentials securely.

    bash
    npm install dotenv

    (If using yarn: yarn add dotenv)

  4. Create .gitignore: Prevent committing sensitive information and unnecessary files. Create a file named .gitignore in your project root:

    text
    # .gitignore
    
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Log files
    *.log
    
    # OS generated files
    .DS_Store
    Thumbs.db
  5. Create .env File: Create a file named .env in the project root to store your sensitive Infobip credentials. Add your API Key and Base URL:

    dotenv
    # .env
    INFOBIP_API_KEY=YOUR_API_KEY_HERE
    INFOBIP_BASE_URL=YOUR_BASE_URL_HERE
    # Optional: Your phone number for testing (must be verified for free trial accounts)
    YOUR_VERIFIED_PHONE_NUMBER=YOUR_PHONE_NUMBER_HERE

    Important: Replace the placeholder values with your actual Infobip API Key, Base URL, and optionally your verified phone number (include the country code, e.g., +15551234567).

Now your basic project structure is ready.

2. Send SMS Messages with Node.js: Two Approaches

Send SMS using two approaches:

  • Approach 1: Manually construct and send HTTP requests using axios. This provides deeper understanding of the API interaction.
  • Approach 2: Use the official @infobip-api/sdk. This simplifies the process and is recommended for production use.

Approach 1: Send SMS with Axios HTTP Client

Use the axios HTTP client to directly interact with the Infobip Send SMS API endpoint (/sms/2/text/advanced).

  1. Install Axios:

    bash
    npm install axios

    (If using yarn: yarn add axios)

  2. Create sendManual.js: Create a new file named sendManual.js containing the logic for sending SMS.

    javascript
    // sendManual.js
    const axios = require('axios');
    
    // Helper function to build the API URL
    const buildUrl = (domain) => {
      if (!domain) {
        throw new Error('Infobip domain (Base URL) is mandatory in config.');
      }
      // Ensure the domain doesn't include 'https://' as we add it here
      const cleanDomain = domain.replace(/^https?:\/\//, '');
      return `https://${cleanDomain}/sms/2/text/advanced`;
    };
    
    // Helper function to build request headers
    const buildHeaders = (apiKey) => {
      if (!apiKey) {
        throw new Error('Infobip API Key is mandatory in config.');
      }
      return {
        'Content-Type': 'application/json',
        'Accept': 'application/json', // Good practice to accept JSON responses
        'Authorization': `App ${apiKey}`
      };
    };
    
    // Helper function to construct the request body
    const buildRequestBody = (destinationNumber, message, sender = 'InfoSMS') => {
      // Basic validation: optional '+' followed by 10-15 digits.
      if (!/^\+?\d{10,15}$/.test(destinationNumber)) {
         console.warn(`Destination number ""${destinationNumber}"" may not be in the correct international format (e.g., +15551234567).`);
         // Depending on requirements, you might throw an error here instead.
      }
       if (!message || typeof message !== 'string' || message.trim() === '') {
        throw new Error('Message content cannot be empty.');
       }
    
      const destinationObject = {
        to: destinationNumber
      };
      const messageObject = {
        destinations: [destinationObject],
        text: message,
        // Optional: Sender ID ('from' field). Using defaults like 'InfoSMS'.
        // Note: Alphanumeric Sender IDs often require registration and may not work reliably everywhere without it. See 'Special Cases' section.
        from: sender
      };
      return {
        messages: [messageObject]
      };
    };
    
    // Helper function to parse successful responses
    const parseSuccessResponse = (axiosResponse) => {
      const responseBody = axiosResponse.data;
      // Handle cases where the response might not contain messages (unlikely for success, but defensive)
      if (!responseBody || !responseBody.messages || responseBody.messages.length === 0) {
          return { success: true, data: responseBody, warning: ""Successful response but no message details found."" };
      }
      const singleMessageResponse = responseBody.messages[0];
      return {
        success: true,
        messageId: singleMessageResponse.messageId,
        status: singleMessageResponse.status.name,
        groupName: singleMessageResponse.status.groupName, // More descriptive status group
        description: singleMessageResponse.status.description,
        bulkId: responseBody.bulkId // Include bulkId if present
      };
    };
    
    // Helper function to parse error responses
    const parseFailedResponse = (axiosError) => {
      let errorInfo = { success: false, errorMessage: axiosError.message, errorDetails: null, statusCode: null };
    
      if (axiosError.response) {
        // The request was made and the server responded with a status code
        // that falls out of the range of 2xx
        const responseBody = axiosError.response.data;
        errorInfo.statusCode = axiosError.response.status;
        if (responseBody && responseBody.requestError && responseBody.requestError.serviceException) {
          errorInfo.errorMessage = responseBody.requestError.serviceException.text || 'Unknown Infobip service exception';
          errorInfo.errorDetails = responseBody; // Include the full error body
        } else {
          errorInfo.errorMessage = `Infobip API Error (Status ${errorInfo.statusCode}): ${axiosError.response.statusText}`;
          errorInfo.errorDetails = responseBody || axiosError.response.statusText;
        }
      } else if (axiosError.request) {
        // The request was made but no response was received
        errorInfo.errorMessage = 'No response received from Infobip API. Check network connectivity or Base URL.';
      } else {
        // Something happened in setting up the request that triggered an Error
        errorInfo.errorMessage = `Axios request setup error: ${axiosError.message}`;
      }
      return errorInfo;
    };
    
    // Main function to send SMS
    const sendSmsManually = async (config, destinationNumber, message) => {
      try {
        // Validate input configuration
        if (!config || !config.domain || !config.apiKey) {
            throw new Error('Configuration object with domain and apiKey is required.');
        }
        if (!destinationNumber) throw new Error('destinationNumber parameter is mandatory');
        if (!message) throw new Error('message parameter is mandatory');
    
        const url = buildUrl(config.domain);
        const requestBody = buildRequestBody(destinationNumber, message);
        const headers = buildHeaders(config.apiKey);
    
        console.log(`Sending SMS manually to ${destinationNumber} via ${url}`);
    
        const response = await axios.post(url, requestBody, { headers: headers });
        return parseSuccessResponse(response);
    
      } catch (error) {
        const parsedError = parseFailedResponse(error);
        console.error('Error sending SMS manually:', parsedError);
        return parsedError; // Return the structured error object
      }
    };
    
    module.exports = { sendSmsManually };

    Why this structure? Breaking down the logic into smaller, focused functions (URL building, header construction, body construction, response parsing) makes the code cleaner, easier to test, and more maintainable. The main sendSmsManually function orchestrates these steps and handles the overall success/error flow using async/await for better readability. We added basic input validation and more robust error parsing.

  3. Create index.js for Testing: Create a file named index.js in the project root to test the sendManual.js module.

    javascript
    // index.js
    require('dotenv').config(); // Load environment variables from .env file
    
    const { sendSmsManually } = require('./sendManual'); // Import the manual function
    
    // --- Configuration ---
    const infobipConfig = {
      domain: process.env.INFOBIP_BASE_URL,
      apiKey: process.env.INFOBIP_API_KEY,
    };
    
    const recipientPhoneNumber = process.env.YOUR_VERIFIED_PHONE_NUMBER; // Use the number from .env
    const messageText = `Hello from Node.js (Manual Axios)! Time: ${new Date().toLocaleTimeString()}`;
    
    // --- Validate Configuration ---
    if (!infobipConfig.domain || !infobipConfig.apiKey) {
      console.error('ERROR: INFOBIP_BASE_URL and INFOBIP_API_KEY must be set in the .env file.');
      process.exit(1); // Exit if configuration is missing
    }
    if (!recipientPhoneNumber) {
        console.error('ERROR: YOUR_VERIFIED_PHONE_NUMBER must be set in the .env file for testing.');
        process.exit(1);
    }
    
    // --- Send SMS ---
    console.log(`Attempting to send SMS to: ${recipientPhoneNumber}`);
    
    sendSmsManually(infobipConfig, recipientPhoneNumber, messageText)
      .then(result => {
        console.log('Manual Send Result:', JSON.stringify(result, null, 2));
        if(result.success) {
            console.log(`SMS submitted successfully! Message ID: ${result.messageId}, Status: ${result.status}`);
        } else {
            console.error(`SMS sending failed. Error: ${result.errorMessage}`);
        }
      })
      .catch(error => {
        // This catch is for unexpected errors not handled within sendSmsManually
        console.error('Unexpected error in index.js:', error);
      });
  4. Run the Test: Execute the index.js file from your terminal:

    bash
    node index.js

    You should see output indicating the attempt and then either a success message with details or an error message. Check your phone for the SMS! Remember, free trial accounts can typically only send to the phone number used during signup.

Approach 2: Send SMS with Infobip Official Node.js SDK

The official SDK (@infobip-api/sdk) abstracts away the direct HTTP calls, providing a cleaner interface.

  1. Install the SDK:

    bash
    npm install @infobip-api/sdk

    (If using yarn: yarn add @infobip-api/sdk)

  2. Create sendSdk.js: Create a new file named sendSdk.js.

    javascript
    // sendSdk.js
    const { Infobip, AuthType } = require('@infobip-api/sdk');
    
    let infobipClient; // Keep client instance reusable
    
    // Function to initialize the client (can be called once)
    const initializeInfobipClient = (config) => {
        if (!config || !config.domain || !config.apiKey) {
            throw new Error('Configuration object with domain and apiKey is required for SDK initialization.');
        }
         if (!infobipClient) {
             console.log(""Initializing Infobip SDK client..."");
             infobipClient = new Infobip({
                 baseUrl: config.domain, // SDK expects the full base URL
                 apiKey: config.apiKey,
                 authType: AuthType.ApiKey, // Specify API Key authentication
             });
         }
         return infobipClient;
    };
    
    // Main function to send SMS using the SDK
    const sendSmsWithSdk = async (config, destinationNumber, message, sender = 'InfoSDK') => {
       try {
           const client = initializeInfobipClient(config); // Get or initialize client
    
           if (!destinationNumber) throw new Error('destinationNumber parameter is mandatory');
           if (!message) throw new Error('message parameter is mandatory');
           // Basic validation: optional '+' followed by 10-15 digits.
           if (!/^\+?\d{10,15}$/.test(destinationNumber)) {
                console.warn(`Destination number ""${destinationNumber}"" may not be in the correct international format (e.g., +15551234567).`);
           }
    
           console.log(`Sending SMS via SDK to ${destinationNumber}`);
    
           // Construct the payload using the SDK's structure
           const sendSmsPayload = {
               messages: [{
                   destinations: [{ to: destinationNumber }],
                   // Optional: Sender ID ('from' field). Using defaults like 'InfoSDK'.
                   // Note: Alphanumeric Sender IDs often require registration and may not work reliably everywhere without it. See 'Special Cases' section.
                   from: sender,
                   text: message,
               }],
               // You can add more options here like bulkId, notifyUrl etc.
               // See SDK documentation for details.
           };
    
           // Use the client instance to send the SMS
           const infobipResponse = await client.channels.sms.send(sendSmsPayload);
    
           // Parse the SDK response (structure is slightly different from manual)
           const responseData = infobipResponse.data; // SDK wraps response in 'data'
            if (!responseData || !responseData.messages || responseData.messages.length === 0) {
               return { success: true, data: responseData, warning: ""Successful response but no message details found."" };
           }
           const singleMessageResponse = responseData.messages[0];
           return {
               success: true,
               messageId: singleMessageResponse.messageId,
               status: singleMessageResponse.status.name,
               groupName: singleMessageResponse.status.groupName,
               description: singleMessageResponse.status.description,
               bulkId: responseData.bulkId
           };
    
       } catch (error) {
           console.error('Error sending SMS with SDK:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message);
           // Parse SDK error structure (often nested in error.response.data)
           let errorMessage = error.message;
           let errorDetails = null;
           let statusCode = error.response ? error.response.status : null;
    
            if (error.response && error.response.data && error.response.data.requestError && error.response.data.requestError.serviceException) {
               errorMessage = error.response.data.requestError.serviceException.text || 'Unknown Infobip SDK service exception';
               errorDetails = error.response.data;
           } else if (error.response && error.response.data) {
                errorMessage = `Infobip SDK Error (Status ${statusCode})`;
                errorDetails = error.response.data;
           }
    
           return {
               success: false,
               errorMessage: errorMessage,
               errorDetails: errorDetails,
               statusCode: statusCode
           };
       }
    };
    
    module.exports = { sendSmsWithSdk, initializeInfobipClient }; // Export both

    Why this structure? The SDK simplifies interaction significantly. We initialize the client once (or check if it exists) for efficiency. The sendSmsWithSdk function focuses on building the correct payload structure expected by the SDK method (client.channels.sms.send) and handling its specific response and error formats.

  3. Update index.js for SDK Testing: Modify index.js to use the SDK function.

    javascript
    // index.js
    require('dotenv').config();
    
    // Choose one implementation to test:
    // const { sendSmsManually } = require('./sendManual');
    const { sendSmsWithSdk } = require('./sendSdk'); // Import the SDK function
    
    // --- Configuration ---
    const infobipConfig = {
      domain: process.env.INFOBIP_BASE_URL,
      apiKey: process.env.INFOBIP_API_KEY,
    };
    
    const recipientPhoneNumber = process.env.YOUR_VERIFIED_PHONE_NUMBER;
    // Change message slightly to distinguish SDK test
    const messageText = `Hello from Node.js (Official SDK)! Time: ${new Date().toLocaleTimeString()}`;
    
    // --- Validate Configuration ---
    if (!infobipConfig.domain || !infobipConfig.apiKey) {
      console.error('ERROR: INFOBIP_BASE_URL and INFOBIP_API_KEY must be set in the .env file.');
      process.exit(1);
    }
     if (!recipientPhoneNumber) {
        console.error('ERROR: YOUR_VERIFIED_PHONE_NUMBER must be set in the .env file for testing.');
        process.exit(1);
    }
    
    // --- Send SMS ---
    console.log(`Attempting to send SMS to: ${recipientPhoneNumber} using the SDK`);
    
    // Call the SDK function
    sendSmsWithSdk(infobipConfig, recipientPhoneNumber, messageText)
      .then(result => {
        console.log('SDK Send Result:', JSON.stringify(result, null, 2));
         if(result.success) {
            console.log(`SDK: SMS submitted successfully! Message ID: ${result.messageId}, Status: ${result.status}`);
        } else {
            console.error(`SDK: SMS sending failed. Error: ${result.errorMessage}`);
        }
      })
      .catch(error => {
        console.error('Unexpected error in index.js:', error);
      });
  4. Run the Test:

    bash
    node index.js

    Again, check your console output and your phone.

Choosing an Approach

  • Manual (axios): Good for learning exactly how the API works, offers maximum control over the request, fewer dependencies if you already use axios. However, it requires more boilerplate code and manual maintenance if the API changes.
  • Official SDK (@infobip-api/sdk): Recommended for most production scenarios. It's easier to use, reduces code volume, handles authentication and request structure internally, and is maintained by Infobip, ensuring compatibility with API updates.

For robustness and ease of maintenance, using the official SDK is generally the preferred approach.

3. Create a REST API Endpoint for SMS Sending (Optional)

Expose SMS functionality via a simple REST API using Express.

  1. Install Express:

    bash
    npm install express

    (If using yarn: yarn add express)

  2. Create server.js: Create a file named server.js. We'll use the SDK implementation here for brevity.

    javascript
    // server.js
    require('dotenv').config();
    const express = require('express');
    const { sendSmsWithSdk, initializeInfobipClient } = require('./sendSdk'); // Use SDK
    
    const app = express();
    const port = process.env.PORT || 3000; // Use environment variable or default
    
    // --- Middleware ---
    app.use(express.json()); // Enable parsing JSON request bodies
    
    // --- Configuration ---
    const infobipConfig = {
        domain: process.env.INFOBIP_BASE_URL,
        apiKey: process.env.INFOBIP_API_KEY,
    };
    
    // --- Validate Configuration on Startup ---
    if (!infobipConfig.domain || !infobipConfig.apiKey) {
      console.error('FATAL ERROR: INFOBIP_BASE_URL and INFOBIP_API_KEY must be set in the environment variables.');
      process.exit(1);
    }
    
    // --- Initialize Infobip Client ---
    // Initialize client once when server starts
    try {
        initializeInfobipClient(infobipConfig);
        console.log(""Infobip client initialized successfully."");
    } catch (error) {
        console.error(""FATAL ERROR: Failed to initialize Infobip client:"", error.message);
        process.exit(1);
    }
    
    
    // --- API Routes ---
    app.get('/', (req, res) => {
        res.send('SMS Service is running. Use POST /send to send an SMS.');
    });
    
    app.post('/send', async (req, res) => {
        const { to, message } = req.body; // Get recipient number and message from request body
    
        // --- Basic Input Validation ---
        if (!to || !message) {
            // Use backticks for field names
            return res.status(400).json({ success: false, error: 'Missing required fields: `to` (phone number) and `message`.' });
        }
         // Add more robust validation for phone number format if needed
         if (typeof to !== 'string' || typeof message !== 'string' || message.trim() === '') {
             return res.status(400).json({ success: false, error: 'Invalid input format. `to` and `message` must be non-empty strings.' });
         }
    
    
        console.log(`Received request to send SMS to: ${to}`);
    
        try {
            // Assuming infobipConfig is correctly populated from environment variables
            const result = await sendSmsWithSdk(infobipConfig, to, message);
    
            if (result.success) {
                console.log(`API: SMS submitted successfully for ${to}. Message ID: ${result.messageId}`);
                // Return only relevant success info to the client
                res.status(200).json({
                    success: true,
                    messageId: result.messageId,
                    status: result.status,
                    description: result.description
                });
            } else {
                console.error(`API: Failed to send SMS to ${to}. Error: ${result.errorMessage}`, result.errorDetails || '');
                // Return a generic server error or specific error if safe
                res.status(result.statusCode || 500).json({
                    success: false,
                    error: result.errorMessage || 'Failed to send SMS due to an internal error.',
                    // Optionally include errorDetails in non-production environments for debugging
                    details: process.env.NODE_ENV !== 'production' ? result.errorDetails : undefined
                });
            }
        } catch (error) {
            // Catch unexpected errors during the API call processing
            console.error(`API: Unexpected error processing /send request for ${to}:`, error);
            res.status(500).json({ success: false, error: 'An unexpected server error occurred.' });
        }
    });
    
    // --- Start Server ---
    app.listen(port, () => {
        console.log(`SMS API server listening on port ${port}`);
        console.log(`Infobip Base URL configured: ${infobipConfig.domain}`);
    });
  3. Run the Server:

    bash
    node server.js

    The server will start, typically on port 3000.

  4. Test the API Endpoint: You can use curl or a tool like Postman to send a POST request.

    Using curl: Replace YOUR_PHONE_NUMBER with the recipient's number (must be verified for free trial).

    bash
    curl -X POST http://localhost:3000/send \
         -H ""Content-Type: application/json"" \
         -d '{
               ""to"": ""YOUR_PHONE_NUMBER"",
               ""message"": ""Hello from the Express API!""
             }'

    Expected Response (Success):

    json
    {
      ""success"": true,
      ""messageId"": ""SOME_MESSAGE_ID_FROM_INFOBIP"",
      ""status"": ""PENDING_ACCEPTED"",
      ""description"": ""Message accepted and pending processing.""
    }

    Expected Response (Failure - e.g., missing field):

    json
    {
      ""success"": false,
      ""error"": ""Missing required fields: `to` (phone number) and `message`.""
    }

4. Infobip API Authentication and Configuration

The core integration is complete:

  • Configuration: API Key (INFOBIP_API_KEY) and Base URL (INFOBIP_BASE_URL) are stored securely in the .env file and loaded using dotenv.

  • Authentication: Handled either via the Authorization: App YOUR_API_KEY header (manual approach) or automatically by the SDK when configured with AuthType.ApiKey.

  • Dashboard Navigation: As detailed in the ""Getting Your Infobip API Credentials"" section.

  • Environment Variables:

    • INFOBIP_API_KEY: Your secret key for API authentication. Obtain from Infobip portal API Keys section. Format: String.
    • INFOBIP_BASE_URL: Your account-specific API domain. Obtain from Infobip portal dashboard. Format: String (e.g., xxxxx.api.infobip.com).
    • YOUR_VERIFIED_PHONE_NUMBER: (Optional, for testing) The phone number linked to your Infobip trial account. Format: String (e.g., +15551234567).
    • PORT: (Optional, for server) The port number for the Express server to listen on. Format: Number.
  • Fallback Mechanisms: For simple SMS sending, implement basic fallback by retrying requests (see Error Handling below). For critical systems, consider alternative providers or channels.

5. Production-Ready Error Handling and Logging

Implement solid error handling for robust applications.

  • Error Handling Strategy:

    • Both sendManual.js and sendSdk.js include try...catch blocks.
    • Specific Infobip errors (usually returned in the response body on non-2xx status codes) are parsed and returned in a consistent { success: false, errorMessage: '...', errorDetails: '...', statusCode: ... } format.
    • Network errors or request setup issues are also caught.
    • The API layer (server.js) catches errors during request processing and returns appropriate HTTP status codes (400 for bad input, 500 for server errors, or the status code from Infobip if available).
  • Logging:

    • We are using basic console.log and console.error.
    • For production, replace these with a structured logging library like pino or winston. This enables:
      • Different log levels (debug, info, warn, error).
      • Outputting logs in JSON format for easier parsing by log management systems.
      • Writing logs to files or external services.
    • Conceptual Example (using Pino): Note: This requires installing pino (npm install pino) and initializing the logger.
      bash
      npm install pino pino-pretty # pino-pretty for development logging
      javascript
      // --- Conceptual Pino Integration ---
      // At the top of server.js (replace console with logger)
      const pino = require('pino');
      // Initialize Pino Logger
      const logger = pino({
          level: process.env.LOG_LEVEL || 'info',
          // Use pino-pretty for human-readable logs in development, JSON in production
          transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
      });
      
      // Example Usage (replace console.* calls):
      // logger.info(`SMS API server listening on port ${port}`);
      // logger.error({ err: error, to }, `API: Unexpected error processing /send request.`);
      // --- End Conceptual Pino Integration ---
      
      // --- Example usage within server.js ---
      // Replace console.log(...) with logger.info(...)
      // Replace console.error(...) with logger.error(...)
      
      // Inside /send route error handling:
      // logger.error({ err: result.errorDetails, to }, `API: Failed to send SMS. Error: ${result.errorMessage}`);
      // ...
      // logger.error({ err: error, to }, `API: Unexpected error processing /send request.`);
      
      // Start server (using logger)
       app.listen(port, () => {
          logger.info(`SMS API server listening on port ${port}`);
          logger.info(`Infobip Base URL configured: ${infobipConfig.domain}`);
       });
  • Retry Mechanisms:

    • Sending an SMS is often idempotent (sending the same message twice might be acceptable or undesirable depending on context).
    • For transient network errors or specific temporary Infobip issues (e.g., 5xx status codes), you might implement retries.
    • Use libraries like async-retry or implement a manual loop with exponential backoff (wait longer between each retry).
    • Caution: Be careful not to retry on permanent errors (like invalid API keys or invalid phone numbers - 4xx errors) or you'll waste resources.
    • Conceptual Example (using async-retry): Note: This requires installing async-retry (npm install async-retry) and assumes a logger instance is available.
      bash
      npm install async-retry
      javascript
      // --- Conceptual Async-Retry Integration (within sendSdk.js) ---
      const retry = require('async-retry');
      // Assumes a 'logger' instance (like Pino from above) is available in this scope
      
      // Inside sendSmsWithSdk function, wrap the API call:
      // try {
           // ... setup before call ...
      
           const sendOperation = async (bail, attemptNumber) => {
               // bail(error) is used to stop retrying on permanent errors
               const client = initializeInfobipClient(config); // Ensure client is ready
               try {
                  logger.info(`Attempt ${attemptNumber}: Sending SMS via SDK to ${destinationNumber}`);
                  const infobipResponse = await client.channels.sms.send(sendSmsPayload);
                  return infobipResponse; // Success! Return the response
               } catch (error) {
                  // Decide if the error is permanent (e.g., 4xx client errors)
                  if (error.response && error.response.status >= 400 && error.response.status < 500) {
                      logger.error(`Permanent error detected (Status ${error.response.status}). Bailing out.`, error);
                      bail(error); // Don't retry 4xx errors
                  }
                  // Log other errors and allow retry
                  throw error; // Important: re-throw the error so retry() catches it
               }
           };
      
           // Retry logic
           const response = await retry(sendOperation, {
               retries: 3, // Number of retries
               factor: 2, // Exponential backoff factor
               minTimeout: 1000, // Initial delay ms
               maxTimeout: 5000, // Max delay ms
               onRetry: (error, attemptNumber) => {
                   logger.warn({ err: error }, `Attempt ${attemptNumber} failed. Retrying...`);
               }
           });
      
           // Parse success response (same as before)
           const responseData = response.data;
           // ... rest of success parsing ...
           if (!responseData || !responseData.messages || responseData.messages.length === 0) {
               return { success: true, data: responseData, warning: ""Successful response but no message details found."" };
           }
           const singleMessageResponse = responseData.messages[0];
           return {
               success: true,
               messageId: singleMessageResponse.messageId,
               status: singleMessageResponse.status.name,
               groupName: singleMessageResponse.status.groupName,
               description: singleMessageResponse.status.description,
               bulkId: responseData.bulkId
           };
      
      } catch (error) {
           // This catch block now handles errors after all retries failed
           logger.error({ err: error }, 'Error sending SMS with SDK after retries');
           // Parse SDK error structure (same as before)
           let errorMessage = error.message;
           let errorDetails = null;
           let statusCode = error.response ? error.response.status : null;
      
           if (error.response && error.response.data && error.response.data.requestError && error.response.data.requestError.serviceException) {
               errorMessage = error.response.data.requestError.serviceException.text || 'Unknown Infobip SDK service exception';
               errorDetails = error.response.data;
           } else if (error.response && error.response.data) {
               errorMessage = `Infobip SDK Error (Status ${statusCode})`;
               errorDetails = error.response.data;
           }
      
           return {
               success: false,
               errorMessage: errorMessage,
               errorDetails: errorDetails,
               statusCode: statusCode
           };
      }
      // }; // End of sendSmsWithSdk function
      
      // module.exports = { sendSmsWithSdk, initializeInfobipClient };
      // --- End Conceptual Async-Retry Integration ---

Frequently Asked Questions

How to send SMS with Node.js and Infobip?

You can send SMS messages with Node.js and Infobip using either manual HTTP requests with Axios or the official Infobip Node.js SDK. The SDK simplifies the process and is recommended for production, while the manual approach is good for learning. Both methods require your Infobip API Key and Base URL, stored securely in a .env file.

What is the Infobip Node.js SDK?

The Infobip Node.js SDK (@infobip-api/sdk) streamlines interaction with the Infobip API. It handles authentication and request structure internally, reducing boilerplate code compared to manual HTTP requests. This approach is generally preferred for production environments.

Why does Infobip need an API key?

Infobip uses API keys for authentication and security, ensuring only authorized applications can access and use the SMS service. Treat your API key like a password and never expose it publicly or in version control.

When should I use the manual Axios approach?

The manual Axios approach is best when you need deep control over HTTP requests or are learning how the Infobip API works. However, for production, the official SDK is generally recommended for easier maintenance and API compatibility.

Can I create an API endpoint for sending SMS?

Yes, you can easily create a simple API endpoint using Express.js to expose your SMS sending functionality. This allows other applications to trigger SMS messages through your Node.js service.

How to get Infobip API key and base URL?

Log in to your Infobip account, navigate to the dashboard for your Base URL, and find your API Key under your profile (or the 'Developers' section). Keep these credentials secure.

What is the purpose of dotenv in this project?

The dotenv module loads environment variables from a .env file, enabling you to store sensitive credentials like API keys securely outside of your codebase.

How to install the Infobip Node.js SDK?

Use your preferred Node.js package manager: npm install @infobip-api/sdk (npm) or yarn add @infobip-api/sdk (yarn). This adds the SDK to your project dependencies.

What are the prerequisites for sending SMS with Infobip?

You need Node.js and npm (or yarn) installed, an active Infobip account (a free trial is sufficient for testing), your Infobip API Key, and Base URL.

Why use Express.js for the API layer?

Express.js simplifies creating web servers and API endpoints in Node.js. It's lightweight, flexible, and widely used, making it a convenient choice for exposing SMS functionality.

How to handle errors when sending SMS?

Implement try...catch blocks in your SMS sending logic to handle potential errors, including network issues, invalid credentials, or problems with Infobip's service. Log errors using a structured logging library and consider retry mechanisms for transient errors.

How to structure a Node.js project for sending SMS?

Create separate modules for manual sending (sendManual.js), SDK sending (sendSdk.js), and an API server (server.js). Use dotenv for environment variables, and consider a structured logger for production.

Where do I find my Infobip base URL?

Your Infobip Base URL is displayed prominently on your account dashboard after you log in to the Infobip portal. It's a domain specific to your account for accessing the API.

What are the benefits of using an SDK?

SDKs like Infobip's Node.js SDK simplify API interaction by handling authentication, request formatting, and response parsing. They reduce boilerplate code and are maintained for API compatibility.