code examples

Sent logo
Sent TeamMar 8, 2026 / code examples / Node.js

Send MMS Messages with Node.js, Express, and Infobip API

Complete guide to building a production-ready Node.js application using Express framework to send MMS messages via Infobip API. Covers setup, authentication, error handling, 10DLC registration, security, and deployment.

This guide provides a complete walkthrough for building a production-ready Node.js application using the Express framework to send Multimedia Messaging Service (MMS) messages via the Infobip API. We'll cover everything from project setup and core MMS sending logic to error handling, security considerations, and deployment.

By the end of this tutorial, you'll have a functional Express API endpoint capable of accepting requests to send MMS messages containing text and media (like images) to specified recipients through Infobip.

Project Goals:

  • Create a simple REST API endpoint using Node.js and Express.
  • Integrate the Infobip Node.js SDK to send MMS messages.
  • Securely manage API credentials.
  • Implement basic error handling and logging.
  • Provide clear steps for setup, implementation, testing, and deployment.

Technology Stack:

  • Node.js: A JavaScript runtime environment for building server-side applications. Minimum version 14 required for the Infobip SDK.
  • Express: A minimal and flexible Node.js web application framework.
  • Infobip Node.js SDK (@infobip-api/sdk): The official library for interacting with the Infobip API, simplifying MMS sending. Version 0.3.1 (released September 17, 2023) is the latest stable release.
  • dotenv: A module to load environment variables from a .env file.

System Architecture:

The basic flow involves a client (like curl, Postman, or a frontend application) making an HTTP POST request to our Express API. The Express app validates the request, uses the Infobip SDK (configured with your API key and base URL) to send the MMS message via the Infobip platform, and then returns a response to the client indicating success or failure.

mermaid
graph LR
    A[Client Application / curl] -- HTTP POST Request --> B(Node.js / Express API);
    B -- Send MMS Request (SDK) --> C(Infobip API);
    C -- MMS Sent --> D(Recipient Handset);
    C -- API Response (Success/Error) --> B;
    B -- API Response --> A;

Prerequisites:

  • Infobip Account: You need an active Infobip account. Sign up if you don't have one.
    • API Key: Generated from your Infobip account security settings.
    • Base URL: Your unique API domain provided by Infobip (e.g., xxxxx.api.infobip.com).
    • MMS-enabled Sender Number: You need a phone number provisioned on Infobip that is capable of sending MMS messages.
      • US 10DLC Registration (Required for A2P MMS): If sending MMS in the United States for Application-to-Person (A2P) messaging, you must complete 10DLC registration through The Campaign Registry (TCR). As of September 1, 2023, all SMS and MMS messages sent to US phone numbers from unregistered 10DLC phone numbers are blocked by carriers. Registration requires:
        • Brand Registration: Business identification with valid Tax ID (EIN) matching your business name
        • Campaign Registration: Description of message campaign objectives, use case, and opt-in collection methods
        • Processing Time: Allow several business days for approval
      • You can request a number via the Infobip portal under "Channels and Numbers".
    • Important: Free trial accounts often have limitations, typically restricting sending only to the phone number used during registration. Attempts to send to other numbers will likely fail.
  • Node.js and npm (or yarn): Installed on your development machine. Node.js 14 or higher is required for the Infobip SDK. Download Node.js from https://nodejs.org/.
  • Basic JavaScript/Node.js knowledge: Familiarity with asynchronous programming (async/await) is helpful.
  • Terminal/Command Line access.

1. Setting Up the Project

Let's initialize our Node.js project and install the necessary dependencies.

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

    bash
    mkdir infobip-mms-sender
    cd infobip-mms-sender
  2. Initialize npm Project: This creates a package.json file to manage dependencies and project metadata.

    bash
    npm init -y

    (The -y flag accepts the default settings).

  3. Install Dependencies: We need Express for the web server, the Infobip SDK, and dotenv for managing environment variables.

    bash
    npm install express @infobip-api/sdk dotenv express-validator

    (If you prefer yarn: yarn add express @infobip-api/sdk dotenv express-validator) (Note: Added express-validator here as it's used later).

  4. Create Project Structure: Set up a basic directory structure for better organization.

    bash
    mkdir src
    mkdir src/routes
    mkdir src/controllers
    mkdir src/services
    touch src/index.js
    touch src/routes/mmsRoutes.js
    touch src/controllers/mmsController.js
    touch src/services/infobipService.js
    touch .env
    touch .gitignore
  5. Configure .gitignore: Prevent sensitive information and unnecessary files from being committed to version control. Add the following lines to your .gitignore file:

    plaintext
    # .gitignore
    node_modules/
    .env
    npm-debug.log
    *.log
  6. Set Up Environment Variables: Open the .env file and add your Infobip credentials and sender number. Never commit this file to Git.

    bash
    # .env
    INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY
    INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL
    INFOBIP_SENDER_NUMBER=YOUR_MMS_ENABLED_SENDER_NUMBER
    PORT=3000
    • Replace YOUR_INFOBIP_API_KEY with the key from your Infobip account.
    • Replace YOUR_INFOBIP_BASE_URL with your specific Infobip API domain.
    • Replace YOUR_MMS_ENABLED_SENDER_NUMBER with the number you'll send MMS from (must be MMS capable and in international format, e.g., 14155552671).
    • PORT defines the port your Express server will listen on.
  7. Update package.json Scripts: Add start and dev scripts to package.json for easily running the application. Replace the existing scripts block or add this one:

    json
    {
      "scripts": {
        "start": "node src/index.js",
        "dev": "node --watch src/index.js",
        "test": "echo \"Error: no test specified\" && exit 1"
      }
    }
    • (Ensure this JSON snippet is placed correctly within your package.json file, replacing the existing scripts object if necessary).
    • npm start will run the application.
    • npm run dev will run the application and automatically restart it when file changes are detected (requires Node.js v18.11.0+ for --watch).

2. Implementing Core Functionality (MMS Sending Service)

We'll encapsulate the Infobip SDK interaction within a dedicated service file.

  1. Configure Infobip SDK Client: In src/services/infobipService.js, import the SDK and initialize the client using the environment variables.

    javascript
    // src/services/infobipService.js
    import { Infobip, AuthType } from '@infobip-api/sdk';
    import dotenv from 'dotenv';
    
    dotenv.config(); // Load environment variables from .env file
    
    const apiKey = process.env.INFOBIP_API_KEY;
    const baseUrl = process.env.INFOBIP_BASE_URL;
    
    if (!apiKey || !baseUrl) {
      console.error(
        'Error: INFOBIP_API_KEY and INFOBIP_BASE_URL must be set in the .env file.'
      );
      process.exit(1); // Exit if essential config is missing
    }
    
    // Initialize Infobip client
    const infobipClient = new Infobip({
      baseUrl: baseUrl,
      apiKey: apiKey,
      authType: AuthType.ApiKey, // Use API Key authentication
    });
    
    /**
     * Sends an MMS message using the Infobip SDK.
     * @param {string} recipientNumber - The recipient's phone number in international format.
     * @param {string} senderNumber - The MMS-enabled sender number.
     * @param {string} subject - The subject of the MMS message.
     * @param {string} textContent - The text part of the message.
     * @param {string} mediaUrl - The publicly accessible URL of the media file (e.g., image).
     * @returns {Promise<object>} - The response data from the Infobip API.
     * @throws {Error} - Throws an error if the MMS sending fails.
     */
    export const sendMms = async (
      recipientNumber,
      senderNumber,
      subject,
      textContent,
      mediaUrl
    ) => {
      console.log(
        `Attempting to send MMS to ${recipientNumber} from ${senderNumber}`
      );
    
      // Construct the MMS payload.
      // IMPORTANT: The payload structure and SDK method (`channels.mms.send`) shown here
      // are based on common patterns. Always consult the *official* Infobip API
      // documentation and Node.js SDK reference for the most current and accurate details,
      // as these can change over time.
      const payload = {
        from: senderNumber,
        to: recipientNumber,
        subject: subject,
        content: [
          // First content part: Text
          {
            type: 'TEXT',
            text: textContent,
          },
          // Second content part: Media (Image in this case)
          {
            type: 'IMAGE', // Can be 'VIDEO', etc. Adjust content type as needed.
            url: mediaUrl,
            // filename: 'image.jpg' // Optional: can specify a filename
          },
        ],
        // smil: '<smil>...</smil>' // Optional: SMIL structure - Generally avoid due to iOS incompatibility
      };
    
      try {
        // Use the Infobip SDK's MMS channel to send the message.
        // Verify this method call against current Infobip SDK documentation.
        const response = await infobipClient.channels.mms.send(payload);
    
        console.log('Infobip API Response:', JSON.stringify(response.data, null, 2));
        return response.data; // Return the successful response body
      } catch (error) {
        // Log the detailed error from Infobip if available
        const errorDetails = error.response ? JSON.stringify(error.response.data, null, 2) : error.message;
        console.error(`Error sending MMS via Infobip: ${errorDetails}`);
    
        // Extract a user-friendly message if possible from the Infobip error response
        const errorMessage = error.response?.data?.requestError?.serviceException?.text
                          || error.message
                          || 'Unknown error occurred while sending MMS via Infobip.';
        throw new Error(`Failed to send MMS: ${errorMessage}`);
      }
    };
    
    // Export the client only if needed elsewhere (unlikely for this example)
    // export { infobipClient };
    • Explanation:
      • We load environment variables using dotenv.
      • We initialize the Infobip client with the baseUrl, apiKey, and specify AuthType.ApiKey.
      • The sendMms function takes recipient, sender, subject, text, and a media URL as parameters.
      • Payload Structure & SDK Method: The code constructs a payload and uses infobipClient.channels.mms.send(payload). Crucially, verify the exact payload structure and method path in the official Infobip SDK/API documentation as this is a common point of failure if outdated.
      • Error Handling: A try...catch block wraps the API call. It logs detailed errors and throws a new, potentially more user-friendly error message extracted from the Infobip response if possible.

3. Building the API Layer (Express Route and Controller)

Now, let's create the Express endpoint that will receive requests and use our infobipService.

  1. Create MMS Controller: This file handles the request logic for MMS sending.

    javascript
    // src/controllers/mmsController.js
    import { sendMms } from '../services/infobipService.js';
    
    const senderNumber = process.env.INFOBIP_SENDER_NUMBER;
    
    if (!senderNumber) {
      console.error('Error: INFOBIP_SENDER_NUMBER must be set in the .env file.');
      process.exit(1);
    }
    
    export const handleSendMms = async (req, res) => {
      // Input validation should have been performed by middleware (see routes)
      const { recipientNumber, subject, textContent, mediaUrl } = req.body;
    
      try {
        const result = await sendMms(
          recipientNumber,
          senderNumber, // Use the sender number from .env
          subject,
          textContent,
          mediaUrl
        );
    
        // Respond with the success details from Infobip
        res.status(200).json({
          message: 'MMS submitted successfully to Infobip.',
          details: result,
        });
      } catch (error) {
        console.error('API Error handling MMS request:', error.message);
        // Respond with a generic server error message
        // Avoid leaking detailed internal errors to the client in production
        res.status(500).json({
          message: 'Failed to send MMS.',
          // Use the simplified error message thrown by the service layer
          error: error.message || 'Internal Server Error',
        });
      }
    };
    • Explanation:
      • Imports the sendMms function.
      • Retrieves the INFOBIP_SENDER_NUMBER from environment variables.
      • Assumes input validation is handled by middleware (like express-validator).
      • Calls the sendMms service function.
      • Responds with 200 OK and the Infobip result on success.
      • Responds with 500 Internal Server Error on failure, logging the detailed error server-side but returning the simplified error message from the service layer.
  2. Create MMS Routes: Define the API endpoint and include validation middleware.

    javascript
    // src/routes/mmsRoutes.js
    import express from 'express';
    import { handleSendMms } from '../controllers/mmsController.js';
    import { body, validationResult } from 'express-validator'; // Import validation functions
    
    const router = express.Router();
    
    // Define the POST endpoint for sending MMS with validation middleware
    // POST /api/mms/send
    router.post('/send',
      // Validation rules:
      body('recipientNumber')
        .isMobilePhone('any', { strictMode: false }) // 'any' allows various formats, strictMode: false is more permissive
        .withMessage('Invalid recipient phone number format. Use international format ideally (e.g., +14155552671).')
        .trim(), // Sanitize
      body('subject')
        .trim()
        .notEmpty().withMessage('Subject is required.'),
      body('textContent')
        .trim()
        .notEmpty().withMessage('Text content is required.'),
      body('mediaUrl')
        .trim()
        .isURL({ protocols: ['http','https'], require_protocol: true })
        .withMessage('Invalid or insecure media URL format. Must be a valid HTTP/HTTPS URL.'),
    
      // Middleware to handle validation results:
      (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          // If validation errors exist, return 400 Bad Request
          return res.status(400).json({ errors: errors.array() });
        }
        // If validation passes, proceed to the controller
        next();
      },
      // Controller function to handle the request after validation:
      handleSendMms
    );
    
    export default router;
    • Explanation:
      • Imports Express, the controller, and express-validator functions.
      • Defines the POST /api/mms/send route.
      • Uses express-validator middleware (body(...)) to define validation and basic sanitization rules (trim()) before the controller runs.
      • Includes a check for validationResult. If errors occur, it sends a 400 Bad Request response. Otherwise, next() passes control to handleSendMms.
      • The isMobilePhone validation uses strictMode: false for broader acceptance but notes the ideal format.
      • The isURL validation ensures a valid HTTP/HTTPS URL.
  3. Set Up Express Server: Tie everything together in src/index.js.

    javascript
    // src/index.js
    import express from 'express';
    import dotenv from 'dotenv';
    import mmsRoutes from './routes/mmsRoutes.js'; // Import the MMS routes
    
    // Load environment variables
    dotenv.config();
    
    const app = express();
    const port = process.env.PORT || 3000; // Use port from .env or default to 3000
    
    // Middleware
    app.use(express.json()); // Enable parsing JSON request bodies
    app.use(express.urlencoded({ extended: true })); // Enable parsing URL-encoded bodies
    
    // Basic Logging Middleware (optional)
    app.use((req, res, next) => {
      console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
      next(); // Pass control to the next middleware/route handler
    });
    
    // API Routes
    app.use('/api/mms', mmsRoutes); // Mount the MMS routes under /api/mms
    
    // Basic Health Check Endpoint
    app.get('/health', (req, res) => {
        res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
    });
    
    // Global Error Handler (Basic Example)
    // Catches errors not handled in specific routes or middleware before it
    app.use((err, req, res, next) => {
        console.error('Unhandled Error:', err.stack || err); // Log the stack trace
        // Avoid sending stack trace in production response
        const errorMessage = process.env.NODE_ENV === 'production'
            ? 'Something went wrong!'
            : err.message || 'Internal Server Error';
        res.status(err.status || 500).json({
             message: errorMessage,
             // Optionally include error code or type in production if safe
        });
    });
    
    // Start the server
    app.listen(port, () => {
      console.log(`Server running on http://localhost:${port}`);
    });
    • Explanation:
      • Initializes Express.
      • Loads environment variables.
      • Uses express.json() and express.urlencoded() middleware.
      • Mounts the mmsRoutes under /api/mms.
      • Includes a /health endpoint and a basic global error handler that avoids leaking stack traces in production.
      • Starts the server.

4. Integrating with Infobip (Configuration Recap)

  • API Key and Base URL: Securely stored in .env and loaded via dotenv. Ensure .env is in .gitignore.
  • Infobip SDK Initialization: Handled in src/services/infobipService.js.
  • MMS-Enabled Sender Number: Stored in .env and used as the from address. Verify this number is provisioned for MMS in your Infobip account (""Channels and Numbers"" section).

5. Error Handling, Logging, and Retries

  • Error Handling:

    • infobipService.js catches SDK errors, logs details, and throws simplified errors.
    • mmsController.js catches service errors, logs them, and returns 500 Internal Server Error with the simplified message.
    • express-validator in mmsRoutes.js handles input validation errors, returning 400 Bad Request.
    • index.js has a global error handler for unexpected errors.
    • Production: Consider using dedicated error tracking services (e.g., Sentry, Datadog).
  • Logging:

    • Basic console.log/console.error is used.
    • Production: Implement structured logging (e.g., using libraries like winston or pino) for better analysis and integration with log management systems.
  • Retry Mechanisms:

    • Consider implementing retries for transient network errors or specific HTTP status codes like 429 Too Many Requests from Infobip. Libraries like p-retry can simplify this. Be cautious not to retry permanent failures (e.g., invalid credentials, bad requests 4xx other than 429).
    • Conceptual Example (requires npm install p-retry): The following shows how you might integrate p-retry into the sendMms function in infobipService.js. Adapt it carefully based on which errors are deemed retryable according to Infobip's API behavior.
    javascript
    // Conceptual example using p-retry inside infobipService.js sendMms function:
    /*
    import pRetry from 'p-retry'; // Make sure to install and import
    
    // Inside the sendMms function, replace the try...catch block with this:
    try {
      const response = await pRetry(
        async () => {
          // The operation to retry - ensure infobipClient and payload are accessible here
          console.log('Attempting Infobip API call...');
          // Note: If infobipClient itself throws an error that shouldn't be retried,
          // you might need more sophisticated error checking within this async function
          // or check the error type in onFailedAttempt.
          return await infobipClient.channels.mms.send(payload);
        },
        {
          retries: 3, // Number of retries (e.g., 3 attempts after the initial one)
          minTimeout: 1000, // Initial delay in milliseconds (1 second)
          factor: 2, // Exponential backoff factor (delay doubles each retry)
          onFailedAttempt: error => {
            // Log retry attempts and check if the error is retryable
            console.warn(
              `MMS send attempt ${error.attemptNumber} failed. Retrying in ${error.retryAfter} ms. Error: ${error.message}`
            );
            // Example: Don't retry on client errors (4xx) other than 429 (Too Many Requests)
            // Adjust status code checks based on Infobip's actual responses for transient vs permanent errors.
            const statusCode = error.response?.status;
            if (statusCode && statusCode >= 400 && statusCode !== 429 && statusCode < 500) {
               console.error(`Non-retryable client error (${statusCode}), aborting retries.`);
               throw error; // Abort retrying for non-retryable client errors
            }
            // Optionally add checks for specific retryable 5xx errors if needed
          },
        }
      );
    
      // If pRetry succeeds:
      console.log('Infobip API Response after retries:', JSON.stringify(response.data, null, 2));
      return response.data;
    
    } catch (error) {
       // This catch block executes if all retries failed or if a non-retryable error was thrown
      const errorDetails = error.response ? JSON.stringify(error.response.data, null, 2) : error.message;
      console.error(`Error sending MMS via Infobip after retries: ${errorDetails}`);
    
      const errorMessage = error.response?.data?.requestError?.serviceException?.text
                          || error.message
                          || 'Unknown error occurred after retries.';
      throw new Error(`Failed to send MMS: ${errorMessage}`);
    }
    */

6. Database Schema and Data Layer (Optional Logging)

Logging message attempts and delivery statuses to a database provides valuable tracking and auditing capabilities.

Simple Logging Approach (No DB): Console logging is implemented as shown. File logging or external log aggregation services are alternatives for persistence.

Database Approach (Example Schema): If using a database (e.g., PostgreSQL, MongoDB) potentially with an ORM (e.g., Prisma, Sequelize):

  • Entity Relationship Diagram (Conceptual):
    mermaid
    erDiagram
        MMS_LOG ||--o{ MMS_RECIPIENT : contains
        MMS_LOG {
            string messageId PK ""Infobip Message ID / Internal UUID""
            string bulkId NULL ""Infobip Bulk ID (if applicable)""
            string senderNumber
            string status ""SUBMITTED, PENDING, SENT, DELIVERED, FAILED, REJECTED""
            string errorCode NULL
            string errorDescription NULL
            datetime createdAt
            datetime updatedAt
        }
        MMS_RECIPIENT {
            string logMessageId FK
            string recipientNumber
        }
  • Data Access Layer: Implement functions (e.g., using your chosen ORM or database driver) to create, read, and update records based on this schema.
  • Migrations: Use ORM tools (e.g., prisma migrate dev, sequelize db:migrate) for managing database schema changes.

Implementation (Conceptual - adding log entry): This shows where you might add a logging call in the mmsController.js after a successful submission to Infobip. It assumes you have created a dbLogService module with the necessary database interaction logic (e.g., createMmsLog).

javascript
// Conceptual code inside mmsController.js, within the `try` block after `await sendMms(...)`
/*
try {
  const result = await sendMms(
    recipientNumber,
    senderNumber,
    subject,
    textContent,
    mediaUrl
  );

  // Log successful submission attempt to database (fire-and-forget or await)
  try {
      // Assumes dbLogService.createMmsLog exists and handles DB interaction.
      // Use Infobip message ID if available, otherwise generate a local one.
      const messageId = result.messages?.[0]?.messageId || `local-${Date.now()}`;
      await dbLogService.createMmsLog({
        messageId: messageId,
        bulkId: result.bulkId,
        senderNumber: senderNumber,
        recipientNumber: recipientNumber, // Or store in related MMS_RECIPIENT table
        status: 'SUBMITTED', // Initial status upon successful API call
        createdAt: new Date(),
        updatedAt: new Date(),
      });
      console.log(`MMS submission logged to DB with ID: ${messageId}`);
  } catch (dbError) {
      console.error(""Failed to log MMS submission to database:"", dbError);
      // Decide if failure to log should affect the API response (usually not critical)
  }

  // Respond with the success details from Infobip
  res.status(200).json({
    message: 'MMS submitted successfully to Infobip.',
    details: result,
  });

} catch (error) {
  // ... existing error handling ...
}
*/

7. Adding Security Features

  • Input Validation and Sanitization: Use express-validator (as shown in mmsRoutes.js) for schema validation (checking required fields, types), sanitization (e.g., trim()), and format checking (isMobilePhone, isURL). The example uses isMobilePhone('any', { strictMode: false }) which is permissive; adjust parameters based on required phone number strictness. isURL() ensures valid, secure HTTP/HTTPS URLs.
    bash
    # Ensure express-validator is installed
    npm install express-validator
  • API Key Security: Keep keys in .env, ensure .env is in .gitignore, manage access permissions within the Infobip platform, and consider rotating keys periodically.
  • Rate Limiting: Protect against brute-force attacks and API abuse using middleware like express-rate-limit.
    bash
    npm install express-rate-limit
    javascript
    // In src/index.js
    import rateLimit from 'express-rate-limit';
    
    // ... Express app setup ...
    
    const apiLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // Limit each IP to 100 requests per windowMs
      standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
      legacyHeaders: false, // Disable the `X-RateLimit-*` headers
      message: 'Too many requests from this IP, please try again after 15 minutes.',
      // keyGenerator: (req, res) => req.ip // Default key generator (IP address)
    });
    
    // Apply the rate limiting middleware to API routes
    app.use('/api/', apiLimiter);
    
    // Mount your API routes *after* the limiter
    app.use('/api/mms', mmsRoutes);
    
    // ... rest of server setup ...
  • HTTPS: Essential for production environments to encrypt data in transit. Use a reverse proxy (like Nginx or Caddy) or rely on Platform-as-a-Service (PaaS) or load balancer features for SSL/TLS termination.
  • Content Security: Validate the mediaUrl strictly. Ensure the media host is trusted and secure. Be cautious about Server-Side Request Forgery (SSRF) vulnerabilities if the mediaUrl can be influenced by external users; ensure the URL points to expected locations.

8. Handling Special Cases Relevant to MMS

  • Content Size Limits:
    • Recommended Maximum: Keep total MMS size (text + media) below 300 KB for maximum carrier compatibility. This is the "smallest common denominator" across device capabilities and carrier configurations.
    • Infobip Recommendation: Infobip officially recommends messages under 300 KB, though some operators support up to 1 MB.
    • US Carrier-Specific Limits (as of 2024):
      • AT&T: Accepts media up to 1 MB
      • Verizon: Accepts media up to 1.7 MB (images up to 1.2 MB, videos up to 3.5 MB)
      • T-Mobile: Accepts media up to 3 MB for receiving, 1 MB for sending
    • Best Practice: Use attachments no larger than 600 KB for non-JPEG/PNG/GIF formats. Validate media file size before attempting to send.
  • Supported Content Types: Use common, widely supported formats. For images: JPEG, GIF, PNG. For text: TEXT/PLAIN (UTF-8 encoded). For video: MP4 (H.264 video codec, AAC audio codec). Ensure the type field in the payload (IMAGE, VIDEO, TEXT) matches the actual content.
  • URL Accessibility:
    • The mediaUrl must be publicly accessible via HTTP or HTTPS without requiring authentication cookies or headers. Infobip's servers need to fetch this content.
    • Minimum Availability: Keep media URLs available for at least 3 days after sending the MMS. Infobip may retry fetching content during delivery attempts.
    • Best Practice: Use unique URLs for new content to prevent caching issues.
  • Content IDs: When referencing content, use IDs that avoid angle brackets, contain only basic letters/numbers, and stay under 20 characters.
  • Character Encoding: Always use UTF-8 encoding for the textContent field to support a wide range of characters.
  • Subject Line Length: Keep subject lines under 500 characters (operators support up to 1,600 characters, but brevity improves compatibility).
  • International Formatting: Use the E.164 format (country code prefixed with +, no spaces or dashes, e.g., +14155552671) for both the to (recipient) and from (sender) phone numbers.
  • SMIL Files: Synchronized Multimedia Integration Language (SMIL) is used to control the layout and timing of MMS content presentation. However, support is inconsistent across devices, especially on iOS. It's generally recommended to avoid using explicit SMIL. Instead, rely on the order of elements in the content array in the payload for sequencing (e.g., text first, then image).
  • Group Messaging: The current code sends an MMS to a single recipient (to field accepts a single number). Sending to multiple recipients as a group MMS (3+ participants) requires an MMS-capable 10DLC number and potentially different payload structure. Consult the Infobip API documentation for group MMS specifics.

9. Performance Optimizations (Considerations)

  • External Dependencies: The primary performance factor will likely be the latency of the Infobip API itself and the time it takes for Infobip to fetch the media from your mediaUrl.

  • Asynchronous Nature: Node.js is well-suited for I/O-bound tasks like making API calls, so the application itself should handle concurrent requests efficiently.

  • Media Hosting: Host your media files (mediaUrl) on a fast, reliable server or a Content Delivery Network (CDN) geographically close to Infobip's infrastructure if possible, to minimize fetch times.

  • Payload Size: Keep the JSON payload sent to your API and the response size reasonable. Avoid overly large request/response bodies.

  • Load Testing: Use tools like k6, artillery, or JMeter to simulate user traffic and identify potential bottlenecks in your application or infrastructure under load.

    Example k6 Load Test Script: (Install k6 globally: npm install -g k6 or use Docker/other installation methods) Save the following as mms-test.js:

    javascript
    import http from 'k6/http';
    import { sleep, check } from 'k6';
    
    export const options = {
      vus: 10, // Number of concurrent virtual users
      duration: '30s', // Duration of the test
    };
    
    export default function () {
      // Target your running API endpoint
      const url = 'http://localhost:3000/api/mms/send';
    
      // IMPORTANT: Replace with a valid recipient number allowed by your Infobip account
      // (e.g., your registered phone number during the free trial)
      const recipient = '+1YOUR_TEST_NUMBER';
    
      // Use a small, reliable, publicly accessible image URL for testing
      const mediaUrl = 'https://www.infobip.com/wp-content/uploads/2021/09/01-infobip-logo-menu.png';
    
      const payload = JSON.stringify({
          recipientNumber: recipient,
          subject: `K6 Load Test MMS VU=${__VU}`,
          textContent: `Testing MMS sending from k6. VU=${__VU}, Iteration=${__ITER}`,
          mediaUrl: mediaUrl
      });
    
      const params = {
        headers: {
          'Content-Type': 'application/json',
        },
      };
    
      // Send the POST request
      const res = http.post(url, payload, params);
    
      // Check if the request was successful (status 200) and response looks okay
      check(res, {
        'status is 200': (r) => r.status === 200,
        'response body contains message': (r) => r.body.includes('MMS submitted successfully'),
        // Add more checks as needed, e.g., response time
        'response time < 500ms': (r) => r.timings.duration < 500,
      });
    
      // Wait for 1 second before the next request (per VU)
      sleep(1);
    }

    Run the test from your terminal (ensure your Node.js app is running): k6 run mms-test.js (Remember to replace +1YOUR_TEST_NUMBER with a number you can actually send to via your Infobip account).


10. Monitoring, Observability, and Analytics

  • Health Checks: Implement a basic /health endpoint (as shown in index.js) that monitoring systems can poll to check if the application is running.
  • Performance Metrics: Monitor key application and system metrics:
    • Request latency (average, p95, p99)
    • Request rate (requests per second/minute)
    • Error rates (HTTP 4xx, 5xx)
    • Node.js process metrics (CPU usage, memory usage, event loop lag).
    • Use libraries like prom-client for Prometheus metrics or integrate with Application Performance Monitoring (APM) tools (e.g., Datadog, Dynatrace, New Relic).
  • Error Tracking: Integrate dedicated error tracking services (e.g., Sentry, Bugsnag) to capture, aggregate, and alert on unhandled exceptions and errors in real-time.
  • Distributed Tracing: For more complex systems involving multiple microservices, consider implementing distributed tracing using standards like OpenTelemetry to track requests across service boundaries.
  • Infobip Delivery Reports (DLRs): Infobip can send asynchronous status updates about message delivery (e.g., DELIVERED_TO_HANDSET, FAILED_TO_DELIVER) via webhooks. Configure a webhook URL in your Infobip account settings pointing to an endpoint in your application. Create a new route and controller in your Express app to receive these POST requests, parse the DLR data, and update your logs or database accordingly. This provides crucial visibility into the final delivery status.

11. Troubleshooting and Caveats

  • Invalid Credentials: (401 Unauthorized response from Infobip)
    • Solution: Double-check that INFOBIP_API_KEY and INFOBIP_BASE_URL in your .env file are correct and match your Infobip account details. Ensure you are using AuthType.ApiKey during client initialization. Verify the API key is active and has MMS permissions.
  • Invalid Phone Number Format: (400 Bad Request from your API validator or an error from Infobip)
    • Solution: Ensure recipient and sender numbers are provided in the international E.164 format (e.g., +14155552671). Check the isMobilePhone validation rules in mmsRoutes.js.
  • Sender Number Not MMS Capable / Not Provisioned: (Infobip error message like "Sender not registered", "MMS not enabled for sender", or similar)
    • Solution: Confirm that the INFOBIP_SENDER_NUMBER specified in .env is correctly provisioned and activated for sending MMS messages within the Infobip portal ("Channels and Numbers"). This often requires specific registration processes (e.g., US 10DLC registration for A2P MMS in the US).
  • Free Trial Limitations:
    • Caveat: This is the most common issue for new users. Infobip free trial accounts typically restrict sending messages only to the phone number that was verified during the signup process. Attempts to send to other numbers will fail.
    • Solution: During the trial period, ensure your recipientNumber matches your verified phone number. Check the specific error message returned by Infobip, as it often indicates this limitation. Upgrade to a paid account for broader sending capabilities.
  • Media URL Issues: (Infobip error related to fetching content, often resulting in message failure)
    • Invalid/Insecure URL: Ensure the mediaUrl starts with http:// or https://. Check the isURL validation in mmsRoutes.js.
    • URL Not Publicly Reachable: Verify the URL is accessible from the public internet without requiring logins or specific headers. Check server uptime and firewalls. Infobip's servers must be able to fetch the content.
    • Unsupported Content Type: Make sure the media file at the URL is in a supported format (JPEG, PNG, GIF for images; MP4 H.264/AAC for video; plain text).
    • Content Too Large: Keep the media file size within recommended limits (ideally < 300-600KB). Large files may fail to download or be rejected by carriers.
  • Insufficient Funds: (Infobip error indicating lack of balance)
    • Solution: Ensure your Infobip account has sufficient balance to cover the cost of sending MMS messages.
  • Carrier Filtering/Blocking:
    • Caveat: Mobile carriers may filter or block MMS messages based on content, sender reputation, or compliance with regulations (like 10DLC in the US).
    • Solution: Adhere to content best practices (avoid spammy content), ensure proper sender registration if required (e.g., 10DLC campaign registration), and contact Infobip support if you suspect carrier filtering is causing delivery issues.
  • SDK Method or Payload Mismatch:
    • Caveat: The specific Infobip SDK method (e.g., infobipClient.channels.mms.send) or the required structure of the payload object might change between different versions of the SDK or due to updates in the Infobip API itself.
    • Solution: Always consult the current official Infobip Node.js SDK documentation and API reference for the definitive method signature and payload structure required for sending MMS messages. Do not rely solely on examples (like this one) without verifying against the official documentation.
  • SMIL Incompatibility:
    • Solution: As mentioned earlier, avoid using SMIL for controlling presentation due to poor cross-device compatibility, especially with iOS. Rely on the order of items in the content array.

12. Deployment and CI/CD

Deployment Options: Choose a platform suitable for Node.js applications, such as Platform-as-a-Service (PaaS) providers (Heroku, Render, Vercel - check MMS backend support), container orchestration (Docker with Kubernetes), or traditional Virtual Machines (AWS EC2, Google Compute Engine).

Basic PaaS Deployment Steps (Conceptual for Heroku/Render):

  1. start script: Ensure your package.json includes a start script: ""start"": ""node src/index.js"".
  2. Procfile (Heroku Specific): Create a file named Procfile (no extension) in your project root with the line: web: npm start. Render often infers the start command.
  3. Node.js Version: Specify the Node.js engine version in package.json to match what you developed with and what the platform supports:
    json
    {
      ""engines"": {
        ""node"": "">=18.0.0""
      }
    }
    (Place this engines block at the top level of your package.json).
  4. Platform CLI/Dashboard: Install the platform's Command Line Interface (CLI) (e.g., heroku login, render login) or use their web dashboard.
  5. Create Application: Create a new application instance on the platform.
  6. Configure Environment Variables: Set the required environment variables (INFOBIP_API_KEY, INFOBIP_BASE_URL, INFOBIP_SENDER_NUMBER, PORT - often set by the platform, NODE_ENV=production) through the platform's settings interface or CLI. Crucially, do not commit your .env file to Git.
  7. Deploy: Deploy your code, typically by pushing your Git repository to the platform's remote (e.g., git push heroku main, git push render main).

CI/CD Pipeline (Conceptual using GitHub Actions):

Continuous Integration/Continuous Deployment (CI/CD) automates testing and deployment. Create a workflow file, for example, .github/workflows/deploy.yml:

yaml
# .github/workflows/deploy.yml
name: Deploy MMS Sender API

on:
  push:
    branches: [ main ] # Trigger deployment on pushes to the main branch

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production # Optional: Define environment for secrets/rules

    steps:
    - name: Checkout code
      uses: actions/checkout@v4 # Use the latest version

    - name: Set up Node.js
      uses: actions/setup-node@v4 # Use the latest version
      with:
        node-version: '18' # Match your package.json engines field
        cache: 'npm' # Enable caching of npm dependencies

    - name: Install Dependencies
      run: npm ci # Use 'ci' for clean, reproducible installs in CI/CD

    # Optional Steps: Add steps here for linting, testing, building, etc.
    # - name: Run Linter
    #   run: npm run lint
    # - name: Run Tests
    #   run: npm test
    # - name: Build Step (if needed)
    #   run: npm run build

    # Example Deployment Step for Heroku (replace with your platform's action/method)
    - name: Deploy to Heroku
      uses: akhileshns/heroku-deploy@v3.12.14 # Check for the latest version of this action
      with:
        heroku_api_key: ${{ secrets.HEROKU_API_KEY }} # Store API key in GitHub Secrets
        heroku_app_name: ""your-heroku-app-name"" # Replace with your actual Heroku app name
        heroku_email: ""your-heroku-email@example.com"" # Replace with your Heroku account email
        # Optional: Specify branch, Procfile path, healthcheck URL, etc.
      # Add steps/actions for other platforms (Render, Vercel, AWS, GCP etc.) as needed

    # Example Deployment Step for Render (using Render Deploy Hook)
    # - name: Trigger Render Deploy Hook
    #   run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK_URL }} # Store hook URL in GitHub Secrets
  • GitHub Secrets: Configure secrets like HEROKU_API_KEY or RENDER_DEPLOY_HOOK_URL in your GitHub repository settings (Settings > Secrets and variables > Actions) to securely provide credentials to the workflow.
  • Platform Actions: Use official or community-supported GitHub Actions specific to your deployment platform (Heroku, Render, AWS Elastic Beanstalk, etc.) for streamlined deployments.