sms compliance

Sent logo
Sent TeamMar 8, 2026 / sms compliance / Node.js

How to Build Two-Way SMS Messaging with Node.js, Express, and Infobip

Learn how to build two-way SMS messaging with Node.js, Express, and Infobip API. Complete tutorial covering webhooks, error handling, security, and production deployment.

<!-- meta_description: "Learn how to build two-way SMS messaging with Node.js, Express, and Infobip API. Complete tutorial covering webhooks, error handling, security, and production deployment." primary_keyword: "two-way SMS Node.js" secondary_keywords: "Infobip SMS API, Express webhook handling, SMS automation Node.js" -->

How to Build Two-Way SMS Messaging with Node.js, Express, and Infobip

This comprehensive guide walks you through building a production-ready Node.js application using Express to handle two-way SMS messaging powered by the Infobip SMS API. You'll learn how to send and receive SMS messages, configure webhooks for inbound messages, implement error handling, secure your application, and deploy it to production.

We'll build a simple "echo bot" – an SMS automation application that automatically replies to any incoming text message with the same content. This serves as a solid foundation for more complex conversational SMS workflows, chatbots, and automated messaging systems.

Project Goals:

  • Send outbound SMS messages using the Infobip Node.js SDK.
  • Receive inbound SMS messages from Infobip via webhooks.
  • Implement basic two-way SMS logic (echoing the message).
  • Securely manage API credentials and environment variables.
  • Set up essential error handling and logging.
  • Prepare the application for production deployment.

Technologies Used:

  • Node.js: A JavaScript runtime environment for server-side development (minimum version 14 required by Infobip SDK).
  • Express: A minimal and flexible Node.js web application framework for building REST APIs.
  • Infobip API & Node.js SDK (@infobip-api/sdk): Official SDK for interacting with Infobip's SMS services (uses /sms/2/text/advanced endpoint).
  • dotenv: To manage environment variables securely.

System Architecture:

User's Phone <-----> Mobile Carrier <-----> Infobip Platform <-----> Your Node.js/Express App | (SMS) | (Webhook POST & API Call) +-------------------------------------------+ (SMS Delivery)
  1. A user sends an SMS to your provisioned Infobip phone number.
  2. Infobip receives the SMS (Mobile Originated - MO).
  3. Infobip sends an HTTP POST request (webhook) containing the message details to your configured endpoint in the Node.js/Express application.
  4. Your application processes the webhook payload (extracts sender number and message text).
  5. Your application uses the Infobip SDK to send a reply SMS (Mobile Terminated - MT) back to the original sender.
  6. Infobip delivers the reply SMS to the user's phone.

Prerequisites:

  • An active Infobip account (Sign up here).
  • A provisioned phone number within your Infobip account capable of sending and receiving SMS.
  • Node.js (version 14 or higher) and npm (or yarn) installed on your development machine. (Download Node.js).
  • Basic understanding of JavaScript, Node.js, REST APIs, and the command line.
  • A tool for testing API endpoints (like curl or Postman).
  • A way to expose your local development server to the internet (e.g., ngrok) for testing inbound webhooks.

Final Outcome:

By the end of this guide, you will have a running Node.js/Express application that can receive SMS messages sent to your Infobip number and automatically reply. You will also have a solid understanding of integrating Infobip for two-way SMS communication and best practices for building such applications.


1. Setting up the Project

Let's start by creating our project structure and installing the necessary dependencies.

  1. Create Project Directory: Open your terminal or command prompt and create a new directory for your project. Navigate into it:

    bash
    mkdir infobip-two-way-sms
    cd infobip-two-way-sms
  2. Initialize Node.js Project: Initialize the project using npm. This creates a package.json file to manage dependencies and project metadata. You can accept the defaults or customize them.

    bash
    npm init -y
    • Why -y? This flag automatically accepts the default settings for npm init, speeding up the process. You can omit it to configure settings manually.
  3. Install Dependencies: We need Express for our web server, the Infobip SDK to interact with their API, and dotenv to manage environment variables.

    bash
    npm install express @infobip-api/sdk dotenv
    • express: The web framework.
    • @infobip-api/sdk: The official Infobip Node.js SDK simplifies API interactions.
    • dotenv: Loads environment variables from a .env file into process.env.
  4. Create Project Structure: Let's organize our code logically.

    bash
    mkdir src
    touch src/server.js src/infobipService.js src/webhookHandler.js
    touch .env
    touch .gitignore
    • src/: Contains our main application source code.
    • src/server.js: The main entry point for our Express application.
    • src/infobipService.js: A dedicated module for Infobip API interactions (sending SMS).
    • src/webhookHandler.js: A module to handle incoming Infobip webhooks.
    • .env: Stores sensitive configuration like API keys (DO NOT commit this file).
    • .gitignore: Specifies intentionally untracked files that Git should ignore (like node_modules and .env).
  5. Configure .gitignore: Add the following lines to your .gitignore file to prevent committing sensitive information and unnecessary files:

    text
    # Dependencies
    node_modules/
    
    # Environment variables
    .env
    
    # Logs
    logs
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    
    # Optional Editor directories
    .idea
    .vscode
    *.suo
    *.ntvs*
    *.njsproj
    *.sln
    *.sw?
  6. Set up Basic Express Server (src/server.js): Open src/server.js and add the following initial setup:

    javascript
    // src/server.js
    require('dotenv').config(); // Load environment variables from .env file
    const express = require('express');
    const webhookHandler = require('./webhookHandler'); // We'll create this next
    
    const app = express();
    const PORT = process.env.PORT || 3000; // Use port from env or default to 3000
    
    // Middleware to parse JSON bodies. Crucial for reading webhook payloads.
    app.use(express.json());
    
    // Define a simple root route for health checks or basic info
    app.get('/', (req, res) => {
      res.status(200).send('Infobip Two-Way SMS Server Running!');
    });
    
    // Define the route for Infobip inbound SMS webhooks
    // We use POST as Infobip sends data via POST requests
    app.post('/webhook/infobip/sms', webhookHandler.handleIncomingSms);
    
    // Start the server
    app.listen(PORT, () => {
      console.log(`Server listening on port ${PORT}`);
    });
    • require('dotenv').config(): Must be called early to load variables before they are used.
    • express.json(): This middleware is essential. Infobip sends webhook data as JSON in the request body. This middleware parses it and makes it available as req.body.
    • /webhook/infobip/sms: This is the specific path where our application will listen for incoming SMS notifications from Infobip. You'll configure this URL in the Infobip portal later.
  7. Add Start Script to package.json: Open your package.json file and add a start script within the "scripts" section. This provides a standard way to run your application.

    json
    // package.json (partial view)
    {
      // ... other configurations
      "scripts": {
        "start": "node src/server.js", // Add this line
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      // ... rest of the configurations
    }

    Now you can start your server (though it won't do much yet) by running npm start.


2. Implementing Core Functionality (Sending SMS)

We'll create a dedicated service to encapsulate the logic for sending SMS messages using the Infobip SDK.

  1. Configure Environment Variables (.env): Open the .env file and add your Infobip API Key and Base URL.

    dotenv
    # .env
    INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
    INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL
    INFOBIP_SENDER_ID=YOUR_INFOBIP_NUMBER_OR_SENDER_ID # Your Infobip number or registered Alphanumeric Sender ID
    • INFOBIP_API_KEY:
      • Purpose: Authenticates your requests to the Infobip API.
      • How to obtain: Log in to your Infobip account. Navigate to the "API Keys" section (often found under account settings or developer tools). Generate a new API key if you don't have one. Copy the full key.
      • Format: A long alphanumeric string (e.g., a1b2c3d4e5f67890a1b2c3d4e5f67890-a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890).
    • INFOBIP_BASE_URL:
      • Purpose: Specifies the regional API endpoint you should connect to.
      • How to obtain: This is provided in your Infobip account dashboard, often on the homepage or API documentation landing page after you log in. It's specific to your account region.
      • Format: A URL like xxxxx.api.infobip.com. Do not include https:// here; the SDK handles the protocol.
    • INFOBIP_SENDER_ID:
      • Purpose: The "from" address for your outbound SMS. This must be a number you've provisioned in Infobip or a pre-registered Alphanumeric Sender ID. Using your provisioned number is crucial for two-way messaging so users can reply.
      • How to obtain: Find your purchased/provisioned numbers in the "Numbers" section of your Infobip portal.
      • Format: A phone number in E.164 international format (e.g., 447123456789 – no spaces, country code included) or a registered alphanumeric string (e.g., InfoSMS).

    Security: Never commit your .env file to version control. Ensure .env has restrictive file permissions (chmod 600 .env) and is never committed.

  2. Create Infobip Service (src/infobipService.js): This module will initialize the Infobip client and provide a function to send messages.

    javascript
    // src/infobipService.js
    const { Infobip, AuthType } = require('@infobip-api/sdk');
    
    // Ensure required environment variables are set
    if (!process.env.INFOBIP_BASE_URL || !process.env.INFOBIP_API_KEY) {
      console.error(""INFOBIP_BASE_URL and INFOBIP_API_KEY must be set in the .env file"");
      process.exit(1); // Exit if configuration is missing
    }
    
    // Initialize the Infobip client
    const infobipClient = new Infobip({
      baseUrl: process.env.INFOBIP_BASE_URL,
      apiKey: process.env.INFOBIP_API_KEY,
      authType: AuthType.ApiKey, // Specify API Key authentication
    });
    
    /**
     * Sends an SMS message using the Infobip API.
     * @param {string} recipientNumber - The destination phone number in E.164 format (e.g., 447123456789).
     * @param {string} messageText - The text content of the SMS.
     * @param {string} [senderId=process.env.INFOBIP_SENDER_ID] - The sender ID (your Infobip number or registered alphanumeric). Defaults to env var.
     * @returns {Promise<object>} - A promise that resolves with the Infobip API response or rejects with an error.
     */
    const sendSms = async (recipientNumber, messageText, senderId = process.env.INFOBIP_SENDER_ID) => {
      if (!recipientNumber || !messageText) {
        throw new Error('Recipient number and message text are required.');
      }
      if (!senderId) {
        console.warn('INFOBIP_SENDER_ID is not set. Using default sender.');
        // Infobip might assign a shared short code if no sender is provided,
        // which might affect reply capabilities and deliverability.
        // For two-way, always use your provisioned number.
      }
    
      const payload = {
        messages: [
          {
            destinations: [{ to: recipientNumber }],
            from: senderId, // Use the configured sender ID or number
            text: messageText,
          },
        ],
      };
    
      console.log(`Attempting to send SMS to ${recipientNumber} from ${senderId || 'default'}`);
    
      try {
        // Use the SDK's send method
        const response = await infobipClient.channels.sms.send(payload);
        console.log('Infobip SMS Send API Response:', JSON.stringify(response.data, null, 2));
        return response.data; // Return the body of the response
      } catch (error) {
        console.error('Error sending SMS via Infobip:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message);
        // Re-throw the error or handle it as needed
        throw error;
      }
    };
    
    module.exports = {
      sendSms,
    };
    • Client Initialization: We create a single instance of the Infobip client using the credentials from .env. AuthType.ApiKey is specified.
    • sendSms Function: Takes the recipient, message text, and optionally a sender ID. It constructs the payload according to the Infobip API specification (specifically for the /sms/2/text/advanced endpoint structure, which the SDK uses).
    • Error Handling: Includes a try...catch block to handle potential API errors during the send request and logs relevant information.
    • Sender ID: Emphasizes the importance of INFOBIP_SENDER_ID for two-way communication. If not set, Infobip might use a default sender, which likely won't work for replies.

3. Building the API Layer (Handling Inbound SMS)

Now, let's implement the logic to receive and process the incoming SMS messages sent by Infobip to our webhook endpoint.

  1. Understand the Infobip Inbound SMS Payload: When Infobip receives an SMS directed to your number, it will make a POST request to your configured webhook URL (/webhook/infobip/sms in our case). The request body contains JSON data similar to this structure (refer to Infobip SMS API documentation for the complete, up-to-date schema):

    json
    {
      "results": [
        {
          "messageId": "abc123xyz456",
          "from": "447123456789", // Sender's phone number
          "to": "447987654321",   // Your Infobip number
          "text": "Hello from user!", // The message content
          "cleanText": "Hello from user!",
          "keyword": "HELLO", // Often the first word, capitalized
          "receivedAt": "2025-04-20T10:30:00.123Z",
          "smsCount": 1,
          "price": {
            "pricePerMessage": 0,
            "currency": "EUR"
          },
          "callbackData": null // Data you might have sent with the outbound message
        }
        // Potentially more messages if received in quick succession
      ],
      "messageCount": 1,
      "pendingMessageCount": 0
    }
    • Key Fields: results[0].from (who sent the message) and results[0].text (what they sent) are crucial for our echo bot.
  2. Implement the Webhook Handler (src/webhookHandler.js): This module takes the incoming request, extracts the necessary information, and uses the infobipService to send a reply.

    javascript
    // src/webhookHandler.js
    const infobipService = require('./infobipService');
    
    /**
     * Handles incoming SMS messages from Infobip webhooks.
     * Parses the message, logs it, and triggers a reply.
     * @param {import('express').Request} req - The Express request object.
     * @param {import('express').Response} res - The Express response object.
     */
    const handleIncomingSms = async (req, res) => {
      console.log("Received Infobip Webhook:", JSON.stringify(req.body, null, 2));
    
      // Basic validation: Check if the payload looks like an Infobip SMS notification
      if (!req.body || !req.body.results || !Array.isArray(req.body.results) || req.body.results.length === 0) {
        console.error("Invalid or empty payload received.");
        // Respond quickly to Infobip to acknowledge receipt, even if invalid
        return res.status(400).send({ message: "Invalid payload structure" });
      }
    
      // Infobip recommends responding quickly to webhooks.
      // Acknowledge receipt immediately before processing.
      res.status(200).send({ message: "Webhook received successfully" });
    
      // Process each message in the payload (usually just one)
      for (const message of req.body.results) {
        const sender = message.from;
        const receivedText = message.text;
        const messageId = message.messageId; // Useful for logging/tracking
    
        if (!sender || !receivedText) {
          console.warn(`Skipping message ${messageId}: Missing sender or text.`);
          continue; // Skip this message and proceed to the next if any
        }
    
        console.log(`Processing message ${messageId} from ${sender}: "${receivedText}"`);
    
        // --- Business Logic: Echo the message back ---
        const replyText = `You sent: "${receivedText}"`;
    
        try {
          // Use the Infobip service to send the reply
          // The 'sender' of the incoming message is the 'recipient' of our reply
          await infobipService.sendSms(sender, replyText);
          console.log(`Successfully sent reply to ${sender} for message ${messageId}`);
        } catch (error) {
          console.error(`Failed to send reply to ${sender} for message ${messageId}:`, error.message);
          // Implement retry logic or further error handling here if needed
        }
        // --- End Business Logic ---
      }
    };
    
    module.exports = {
      handleIncomingSms,
    };
    • Logging: The first step is to log the entire incoming payload. This is invaluable for debugging.
    • Validation: Basic checks ensure the payload structure is roughly what we expect.
    • Quick Response: It's crucial to send a 200 OK response back to Infobip before doing potentially long-running processing (like calling the API to send the reply). Webhooks often have short timeouts.
    • Message Iteration: The code iterates through the results array, although typically it contains only one message per webhook request.
    • Data Extraction: It pulls out the sender (from) and receivedText (text).
    • Business Logic: This is where you'd implement your application's specific response logic. Here, we simply formulate an echo reply.
    • Sending Reply: It calls infobipService.sendSms, passing the original sender's number as the recipient for the reply.
    • Error Handling: Includes a try...catch around the sendSms call to handle failures specific to sending the reply.

4. Integrating with Infobip (Webhook Configuration)

Your code is ready to send and receive, but you need to tell Infobip where to send the incoming SMS notifications.

  1. Expose Your Local Server: Infobip's servers need to be able to reach your running application. During development, your machine is usually behind a firewall/NAT. Tools like ngrok create a secure tunnel and provide a public URL.

    • Install ngrok (Download ngrok).

    • Run your Node.js app: npm start (it should log ""Server listening on port 3000"").

    • In a new terminal window, start ngrok, telling it to forward to your application's port (e.g., 3000):

      bash
      ngrok http 3000
    • ngrok will display output similar to this:

      Session Status online Account Your Name (Plan: Free) Version 3.x.x Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding https://<unique-subdomain>.ngrok-free.app -> http://localhost:3000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
    • Copy the https://<unique-subdomain>.ngrok-free.app URL. This is your public webhook URL for now.

  2. Configure Webhook in Infobip Portal:

    • Log in to your Infobip account.
    • Navigate to the ""Numbers"" section (or equivalent where you manage your purchased phone numbers).
    • Find the specific phone number you are using for this application and go to its settings/configuration.
    • Look for a section related to ""Forwarding,"" ""Webhooks,"" ""Messaging Settings,"" or ""Inbound Messages.""
    • There should be an option to configure a URL for Incoming SMS Messages (or similar wording).
    • Paste your ngrok URL, followed by the specific path you defined in server.js: https://<unique-subdomain>.ngrok-free.app/webhook/infobip/sms
    • Important: Ensure the method is set to POST.
    • Save the configuration changes in the Infobip portal.

    (Dashboard navigation paths can change. Refer to the current Infobip documentation if you can't find the exact location.)


5. Error Handling, Logging, and Retries

Production applications need robust error handling and logging.

  • Consistent Error Handling:

    • Our current code uses try...catch blocks in key areas (infobipService.sendSms, webhookHandler.handleIncomingSms).
    • Always log errors with sufficient context (e.g., message ID, sender number, error details).
    • Distinguish between errors that should stop processing (like invalid config) and errors that affect a single message (like failed API call to send reply).
    • Consider creating custom error classes for better error categorization.
  • Logging:

    • console.log and console.error are used for basic logging.
    • For production, use a dedicated logging library like Winston or Pino. They offer:
      • Different log levels (debug, info, warn, error).
      • Structured logging (JSON format is common for easier parsing).
      • Multiple transports (log to console, files, external services).
    • Example (Conceptual with Winston):
      javascript
      // Example logging setup (e.g., in a logger.js module)
      const winston = require('winston');
      const logger = winston.createLogger({
        level: process.env.LOG_LEVEL || 'info',
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json()
        ),
        transports: [
          new winston.transports.Console(),
          // Add file transport for production
          // new winston.transports.File({ filename: 'error.log', level: 'error' }),
          // new winston.transports.File({ filename: 'combined.log' }),
        ],
      });
      // Use logger.info(), logger.warn(), logger.error() instead of console.*
  • Retry Mechanisms:

    • Network issues or temporary Infobip API glitches can cause requests to fail. Implement retries for transient errors.
    • Use libraries like async-retry or implement a simple loop with exponential backoff (wait longer between each retry).
    • Example (Conceptual async-retry for sending):
      javascript
      // Inside infobipService.sendSms, wrap the API call
      const retry = require('async-retry');
      // ...
      try {
        await retry(
          async bail => { // bail is a function to call for non-retryable errors
            console.log(`Attempting to send SMS (retry logic)...`);
            const response = await infobipClient.channels.sms.send(payload);
            console.log('Infobip SMS Send API Response:', JSON.stringify(response.data, null, 2));
            return response.data;
          },
          {
            retries: 3, // Number of retries
            factor: 2, // Exponential backoff factor
            minTimeout: 1000, // Initial wait time (ms)
            onRetry: (error, attempt) => {
              console.warn(`Retrying SMS send (attempt ${attempt}) due to error: ${error.message}`);
            }
          }
        );
      } catch (error) {
        console.error('Error sending SMS via Infobip after retries:', error.response ? JSON.stringify(error.response.data, null, 2) : error.message);
        throw error;
      }
    • Caution: Be careful not to retry errors that are clearly permanent (e.g., invalid API key, invalid recipient number format - Infobip error codes can help identify these).

6. Database Schema and Data Layer (Optional for Stateful Apps)

Our current echo bot is stateless – it doesn't remember past interactions. For most real-world applications (support chats, surveys, multi-step workflows), you'll need a database.

  • Why a Database?

    • Store conversation history.
    • Manage user state (e.g., which step of a survey they are on).
    • Link SMS conversations to user accounts in your system.
    • Store user preferences or data collected via SMS.
  • Example Schema (Conceptual - PostgreSQL/Prisma): You might have tables like:

    • User: Stores user information, potentially linked by phone number.
    • Conversation: Groups related messages.
    • Message: Stores individual inbound/outbound messages with details (sender, recipient, text, timestamp, Infobip message ID, status, direction - inbound/outbound).
  • Data Access:

    • Use an Object-Relational Mapper (ORM) like Prisma or Sequelize to interact with your database in a structured way.
    • Implement functions to:
      • Find or create a user based on phone number.
      • Create/retrieve conversation records.
      • Save inbound and outbound messages, linking them to conversations/users.
      • Query conversation history.
    • Example (webhookHandler.js modified conceptually):
      javascript
      // Inside handleIncomingSms, after extracting sender/text
      // const user = await db.findOrCreateUserByPhone(sender);
      // const conversation = await db.findOrCreateActiveConversation(user.id);
      // await db.saveMessage({
      //   conversationId: conversation.id,
      //   direction: 'inbound',
      //   infobipMessageId: messageId,
      //   sender: sender,
      //   recipient: message.to, // Your Infobip number
      //   text: receivedText,
      // });
      
      // --- Business Logic (now potentially stateful) ---
      // const currentState = conversation.state;
      // const replyText = determineReplyBasedOnState(currentState, receivedText);
      // conversation.state = determineNextState(...);
      // await db.updateConversationState(conversation.id, conversation.state);
      // --- End Business Logic ---
      
      // await infobipService.sendSms(sender, replyText);
      // await db.saveMessage({ ... direction: 'outbound', ... });
  • Migrations: Use the ORM's migration tools (e.g., prisma migrate dev) to manage database schema changes safely.


7. Adding Security Features

Security is paramount, especially when handling user data and API keys.

  • Environment Variables: We're already using .env to keep API keys out of the codebase. Ensure the .env file has restrictive permissions (chmod 600 .env) and is never committed.
  • Input Validation/Sanitization:
    • Webhook Payload: While we have basic structure validation, you could add more specific checks (e.g., ensure from looks like a phone number, limit text length). Libraries like Joi or Zod are excellent for schema validation.
    • Database Inputs: Sanitize any user-provided text (receivedText) before storing it in the database or using it in replies to prevent potential Cross-Site Scripting (XSS) issues if this data is ever displayed in a web interface. Libraries like dompurify (if rendering as HTML) or simply escaping special characters might be needed depending on usage.
  • Webhook Security:
    • Problem: Anyone could potentially send a POST request to your /webhook/infobip/sms endpoint, pretending to be Infobip.
    • Solutions:
      1. Signature Validation (Recommended): Check if Infobip offers webhook signature validation (often using HMAC-SHA signatures with a shared secret). This cryptographically verifies the request originated from Infobip. Consult Infobip's documentation for details on enabling and verifying signatures. If they offer it, implement the validation middleware.
      2. IP Filtering: Configure your firewall or infrastructure (e.g., AWS Security Group, Cloudflare) to allow requests to the webhook endpoint only from Infobip's published IP address ranges. This is less secure than signature validation but better than nothing.
      3. Secret in URL (Less Secure): Include a hard-to-guess secret token in the webhook URL path (/webhook/infobip/sms/YOUR_SECRET_TOKEN). This provides minimal obscurity but is vulnerable if the URL leaks.
  • Rate Limiting:
    • Protect your application from abuse (accidental or malicious) by limiting the number of requests from a single IP address or for a specific user.
    • Use middleware like express-rate-limit.
    • Example (server.js):
      javascript
      const rateLimit = require('express-rate-limit');
      
      const webhookLimiter = rateLimit({
        windowMs: 15 * 60 * 1000, // 15 minutes
        max: 100, // Limit each IP to 100 requests per windowMs
        message: 'Too many requests from this IP, please try again after 15 minutes',
        standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
        legacyHeaders: false, // Disable the `X-RateLimit-*` headers
      });
      
      // Apply to the webhook route
      app.use('/webhook/infobip/sms', webhookLimiter);
      app.post('/webhook/infobip/sms', webhookHandler.handleIncomingSms);
  • Helmet Middleware:
    • Install and use Helmet to set security-related HTTP headers automatically.
    • Installation: npm install helmet
    • Usage in server.js:
      javascript
      const helmet = require('helmet');
      
      // Apply Helmet early in middleware chain
      app.use(helmet());
    • Helmet sets headers like Content-Security-Policy, Strict-Transport-Security, and removes the X-Powered-By header to reduce attack surface.
  • Regular Security Audits: Run npm audit regularly to check for known vulnerabilities in dependencies. Consider using tools like Snyk for continuous security monitoring.

8. Handling Special Cases

Real-world SMS involves nuances:

  • Phone Number Formatting: Infobip expects and provides numbers in E.164 format (e.g., +14155552671, 447123456789 – note the plus sign is optional in API requests but the format is required). Ensure your application consistently handles this format when sending replies.
    • Recommended: Use libphonenumber-js to parse, validate, and format numbers.
    • Installation: npm install libphonenumber-js
    • Example:
      javascript
      import parsePhoneNumber from 'libphonenumber-js';
      
      const phoneNumber = parsePhoneNumber(sender);
      if (phoneNumber && phoneNumber.isValid()) {
        const e164Number = phoneNumber.format('E.164'); // '+14155552671'
        await infobipService.sendSms(e164Number, replyText);
      }
  • Character Limits & Concatenation: A standard SMS segment has 160 GSM-7 characters or 70 UCS-2 characters (for non-Latin alphabets or emojis). Longer messages are split into multiple segments (concatenated SMS).
    • Be mindful of reply length to manage costs (Infobip charges per segment).
    • Infobip handles the concatenation, but your application logic might need to consider length if splitting messages manually or truncating. The smsCount field in the inbound webhook indicates how many segments the incoming message used.

9. Implementing Performance Optimizations

For high-volume applications:

  • Fast Webhook Responses: As mentioned, respond to Infobip webhooks immediately (res.status(200).send()) before performing slow operations.
  • Background Processing: Offload time-consuming tasks (complex logic, multiple external API calls, database operations) to a background job queue (e.g., using libraries like BullMQ with Redis, or services like AWS SQS). The webhook handler simply adds a job to the queue, and a separate worker process handles it.
  • Database Optimization: Use indexing on frequently queried columns (e.g., phone numbers, conversation IDs, timestamps). Optimize complex queries.
  • Caching: Cache frequently accessed, rarely changing data (e.g., user profiles, configuration settings) using tools like Redis or Memcached to reduce database load.
  • Load Testing: Use tools like k6, Artillery, or ApacheBench to simulate high traffic volumes and identify bottlenecks in your webhook handler and API response times.
  • Profiling: Use Node.js built-in profiler or tools like Clinic.js to analyze CPU usage and memory allocation to pinpoint performance issues in your code.

Frequently Asked Questions (FAQ)

What is two-way SMS messaging?

Two-way SMS messaging allows both sending and receiving text messages programmatically. Unlike one-way SMS (broadcast only), two-way SMS enables interactive conversations where users can reply to messages and your application can respond automatically, creating chatbot-like experiences.

How do I get an Infobip API key?

Log in to your Infobip account and navigate to the API Keys section (usually under account settings or developer tools). Generate a new API key and copy it to your .env file as INFOBIP_API_KEY. Keep this key secure and never commit it to version control.

What phone number format does Infobip require?

Infobip expects phone numbers in E.164 international format: country code + national number without spaces or special characters (e.g., 447123456789 for a UK number). The optional + prefix is accepted but not required. Use libphonenumber-js to validate and format numbers correctly.

How do Infobip webhooks work?

When Infobip receives an SMS to your provisioned number, it sends an HTTP POST request to your configured webhook URL with a JSON payload containing the sender's number, message text, and metadata. Your Express application processes this webhook, extracts the data, and can send a reply using the Infobip SDK.

How can I secure my SMS webhook endpoint?

Implement multiple security layers: (1) Use webhook signature validation if Infobip provides it to verify request authenticity, (2) Apply rate limiting with express-rate-limit to prevent abuse, (3) Filter requests by Infobip's IP ranges, (4) Use Helmet middleware to set security headers, and (5) Validate all incoming payload data before processing.

What's the difference between MO and MT SMS?

MO (Mobile Originated) refers to messages sent from a user's phone to your application. MT (Mobile Terminated) refers to messages sent from your application to a user's phone. In two-way messaging, you receive MO messages via webhooks and send MT messages via the Infobip API.

How do I test webhooks locally?

Use ngrok or a similar tunneling tool to expose your local development server to the internet. Run ngrok http 3000 to get a public HTTPS URL, then configure this URL in your Infobip portal as your webhook endpoint. This allows Infobip to reach your local machine during development.

How much does SMS messaging with Infobip cost?

Infobip pricing varies by destination country and message volume. SMS messages are charged per segment (160 GSM-7 characters or 70 UCS-2 characters for Unicode). Check your Infobip account dashboard for specific pricing details and consider message length when designing replies to manage costs.


Next Steps and Production Deployment