code examples

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

Sending MMS with Node.js, Express, and Infobip

A step-by-step guide to building a Node.js/Express application for sending MMS messages using the Infobip API, covering setup, implementation, error handling, and deployment.

Developer Guide: Sending MMS with Node.js, Express, and Infobip

This guide provides a step-by-step 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 will cover project setup, core implementation, API creation, error handling, security, deployment, and verification.

Note: Some sections refer to external links or specific user credentials (like GitHub repository links, API keys, Docker Hub usernames). Where the actual link or value was not provided, placeholders remain. Please replace these placeholders with your actual values.

Project Overview and Goals

What We're Building:

We will create a simple REST API endpoint using Node.js and Express. This endpoint will accept requests containing recipient information, a subject, text content, and a URL pointing to media (like an image). It will then use the Infobip API to construct and send an MMS message including this text and media to the specified recipient.

Problem Solved:

This guide enables developers to programmatically send rich media messages (MMS) through a reliable provider like Infobip, integrating this capability into their applications for use cases such as marketing campaigns, notifications with images, or customer support involving visual aids.

Technologies Used:

  • Node.js: A JavaScript runtime environment for building server-side applications.
  • Express: A minimal and flexible Node.js web application framework used to build the API layer.
  • Infobip API: The third-party service used for sending the MMS messages.
  • Axios: A promise-based HTTP client for making requests to the Infobip API.
  • form-data: A library to create multipart/form-data streams, necessary for the Infobip MMS API.
  • dotenv: A module to load environment variables from a .env file.

System Architecture:

text
+----------+       +-----------------------+       +--------------+       +-----------+       +-----------+
|  Client  | ----> | Node.js/Express API | ----> | Infobip API  | ----> | Carrier   | ----> | Handset   |
| (e.g. curl|       |  (Our Application)    |       | (MMS Gateway)|       | Network   |       | (Recipient)|
| Postman) |       +-----------------------+       +--------------+       +-----------+       +-----------+
+----------+              |                                ^
                          | loads secrets from             | reads media from
                          v                                |
                      +-------+                      +-----------+
                      | .env  |                      | Media URL |
                      +-------+                      +-----------+

Prerequisites:

  • A free or paid Infobip account (Sign up here).
  • Your Infobip API Key and Base URL (found in your Infobip dashboard).
  • An MMS-capable phone number purchased or verified within your Infobip account (often a 10DLC number for US A2P traffic).
  • Node.js v14.x or later (for ES Module support used in this guide) and npm (or yarn) installed on your development machine.
  • Basic understanding of JavaScript, Node.js, REST APIs, and asynchronous programming.

Expected Outcome:

By the end of this guide, you will have a running Express application with a single API endpoint (POST /send-mms) that can successfully send an MMS message containing text and an image (fetched from a URL) via Infobip.

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.

    bash
    mkdir infobip-mms-express
    cd infobip-mms-express
  2. Initialize npm Project: Initialize the project using npm. The -y flag accepts the default settings.

    bash
    npm init -y

    This creates a package.json file. Make sure it includes ""type"": ""module"" to enable ES Module syntax (see Section 3).

  3. Install Dependencies: We need Express for the server, Axios for HTTP requests, form-data to construct the multipart request for Infobip's MMS API, and dotenv to manage environment variables securely.

    bash
    npm install express axios form-data dotenv
  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/server.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 files and node modules from being committed to version control. Add the following to your .gitignore file:

    text
    # .gitignore
    node_modules/
    .env
    npm-debug.log
  6. Set up Environment Variables: Open the .env file and add your Infobip credentials and other configuration. Replace the placeholder values below with your actual credentials. Never commit this file to Git.

    dotenv
    # .env
    
    # Infobip Credentials (Get from your Infobip Dashboard - REPLACE PLACEHOLDERS)
    INFOBIP_BASE_URL=YOUR_ACCOUNT_SPECIFIC_URL.api.infobip.com
    INFOBIP_API_KEY=YOUR_API_KEY_HERE
    
    # Your MMS-capable sender number from Infobip (REPLACE PLACEHOLDER)
    SENDER_NUMBER=+1_YOUR_INFOBIP_NUMBER
    
    # Server Port
    PORT=3000
    • INFOBIP_BASE_URL: Replace with the Base URL from your Infobip account's homepage or API documentation section (e.g., xxxxx.api.infobip.com).
    • INFOBIP_API_KEY: Replace with the API key generated in the API key management section of your Infobip dashboard. Treat it like a password.
    • SENDER_NUMBER: Replace with the MMS-enabled phone number associated with your Infobip account that will appear as the sender. Ensure it's in E.164 format (e.g., +12223334444).
    • PORT: The port number on which your Express server will listen.

2. Implementing Core Functionality (Infobip Service)

This service module will encapsulate the logic for interacting with the Infobip MMS API. The key difference from sending SMS is that MMS requires a multipart/form-data request structure.

Open src/services/infobipService.js and add the following code:

javascript
// src/services/infobipService.js
import axios from 'axios';
import FormData from 'form-data';

// Load environment variables at the top level
const INFOBIP_BASE_URL = process.env.INFOBIP_BASE_URL;
const INFOBIP_API_KEY = process.env.INFOBIP_API_KEY;
const SENDER_NUMBER = process.env.SENDER_NUMBER;

/**
 * Sends an MMS message using the Infobip API.
 * @param {string} recipient - The recipient's phone number in E.164 format.
 * @param {string} subject - The subject line for the MMS.
 * @param {string} textContent - The text body of the MMS.
 * @param {string} mediaUrl - The URL of the media file (e.g., image) to include.
 * @returns {Promise<object>} - A promise that resolves with the Infobip API response.
 * @throws {Error} - Throws an error if the request fails.
 */
async function sendMms(recipient, subject, textContent, mediaUrl) {
    if (!INFOBIP_BASE_URL || !INFOBIP_API_KEY || !SENDER_NUMBER || INFOBIP_BASE_URL === 'YOUR_ACCOUNT_SPECIFIC_URL.api.infobip.com') {
        throw new Error('Infobip credentials or sender number are missing or are placeholders in environment variables.');
    }
    if (!recipient || !textContent || !mediaUrl) {
        throw new Error('Recipient, text content, and media URL are required.');
    }

    const mmsApiUrl = `https://${INFOBIP_BASE_URL}/mms/1/single`; // MMS Single message endpoint

    const form = new FormData();

    // 1. Head Part (Required by Infobip MMS API structure)
    const head = {
        from: SENDER_NUMBER,
        to: recipient,
        subject: subject || 'MMS Message', // Provide a default subject if none given
    };
    form.append('head', JSON.stringify(head), { contentType: 'application/json' });

    // 2. Text Part (Optional, but usually included)
    if (textContent) {
        form.append('text', textContent, {
            contentType: 'text/plain',
            filename: 'text.txt', // Filename is arbitrary but helps structure
        });
    }

    // 3. Media Part (Fetch from URL and append)
    let mediaResponse;
    try {
        mediaResponse = await axios.get(mediaUrl, {
            responseType: 'stream', // Fetch as a readable stream
        });
    } catch (error) {
        console.error(`Error fetching media from URL: ${mediaUrl}`, error.message);
        throw new Error(`Failed to fetch media from URL: ${mediaUrl}. Status: ${error.response?.status}`);
    }

    const contentType = mediaResponse.headers['content-type'] || 'application/octet-stream'; // Get content type from response
    const filename = mediaUrl.substring(mediaUrl.lastIndexOf('/') + 1) || 'media_file'; // Extract filename

    // Append the stream directly to the form-data
    form.append('file', mediaResponse.data, {
        filename: filename,
        contentType: contentType,
    });

    // --- Axios Request Configuration ---
    const axiosConfig = {
        headers: {
            ...form.getHeaders(), // Includes 'Content-Type: multipart/form-data; boundary=...'
            'Authorization': `App ${INFOBIP_API_KEY}`, // Use 'App' prefix for API Key auth
        },
        maxContentLength: Infinity, // Allow large file uploads if necessary
        maxBodyLength: Infinity,
    };

    // --- Send the Request ---
    console.log(`Sending MMS to ${recipient} via Infobip...`);
    try {
        const response = await axios.post(mmsApiUrl, form, axiosConfig);
        console.log('Infobip API Response Status:', response.status);
        console.log('Infobip API Response Data:', JSON.stringify(response.data, null, 2));
        return response.data; // Return the successful response body
    } catch (error) {
        console.error('Error sending MMS via Infobip:');
        if (error.response) {
            // The request was made and the server responded with a status code
            // that falls out of the range of 2xx
            console.error('Status:', error.response.status);
            console.error('Headers:', JSON.stringify(error.response.headers, null, 2));
            console.error('Data:', JSON.stringify(error.response.data, null, 2));
            // Re-throw a more specific error using response data
            throw new Error(`Infobip API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
        } else if (error.request) {
            // The request was made but no response was received
            console.error('Request Error:', error.request);
            throw new Error('Infobip API Error: No response received from server.');
        } else {
            // Something happened in setting up the request that triggered an Error
            console.error('Setup Error:', error.message);
            throw new Error(`Infobip API Error: ${error.message}`);
        }
    }
}

export { sendMms };

Why this approach?

  • multipart/form-data: Infobip's MMS API requires this format, unlike the JSON typically used for SMS. The form-data library correctly structures the request.
  • Streaming Media: Fetching the media URL as a stream (responseType: 'stream') and passing it directly into the form data is memory-efficient, especially for larger files. It avoids loading the entire media file into memory.
  • Content Parts: The code constructs the distinct head, text, and file parts as required by the Infobip MMS specification found in their documentation.
  • Headers: form-data generates the correct Content-Type header with the boundary. We add the Authorization header using the App scheme as specified by Infobip.
  • Error Handling: Specific error handling for fetching media and for the Infobip API call provides better debugging information, including extracting details from Infobip's error response when available.

3. Building the API Layer (Express)

Now, let's create the Express route and controller to handle incoming requests and use our infobipService.

Controller (src/controllers/mmsController.js):

javascript
// src/controllers/mmsController.js
import { sendMms } from '../services/infobipService.js';

/**
 * Handles incoming requests to send an MMS.
 * Expects body: { recipient: string, subject?: string, text: string, mediaUrl: string }
 */
async function handleSendMmsRequest(req, res, next) {
    const { recipient, subject, text, mediaUrl } = req.body;

    // Basic Input Validation
    if (!recipient || !text || !mediaUrl) {
        return res.status(400).json({
            error: 'Bad Request',
            message: 'Missing required fields: recipient, text, mediaUrl.',
        });
    }

    // Simple E.164 format check (can be enhanced with a library like libphonenumber-js)
    if (!/^\+?[1-9]\d{1,14}$/.test(recipient)) {
         return res.status(400).json({
            error: 'Bad Request',
            message: 'Invalid recipient phone number format. Use E.164 format (e.g., +12223334444).',
        });
    }

     // Basic URL validation
    try {
        new URL(mediaUrl);
    } catch (_) {
        return res.status(400).json({
            error: 'Bad Request',
            message: 'Invalid mediaUrl format.',
        });
    }

    try {
        console.log(`Received request to send MMS to ${recipient}`);
        const result = await sendMms(recipient, subject, text, mediaUrl);
        console.log(`Successfully sent MMS request for ${recipient}. Result:`, result);

        // Respond with the result from Infobip
        // Note: The result contains bulkId and messageId(s) which are crucial
        // for tracking message status later (e.g., via webhooks).
        res.status(200).json({
            message: 'MMS send request accepted by Infobip.',
            infobipResponse: result,
        });
    } catch (error) {
         console.error(`Failed to process MMS request for ${recipient}:`, error.message);
         // Pass error to the central error handler
         next(error);
    }
}

export { handleSendMmsRequest };

Routes (src/routes/mmsRoutes.js):

javascript
// src/routes/mmsRoutes.js
import express from 'express';
import { handleSendMmsRequest } from '../controllers/mmsController.js';

const router = express.Router();

// Define the POST endpoint for sending MMS
router.post('/send-mms', handleSendMmsRequest);

export default router;

Server Setup (src/server.js):

javascript
// src/server.js
import express from 'express';
import dotenv from 'dotenv';
import mmsRoutes from './routes/mmsRoutes.js';

// Load environment variables from .env file
dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

// --- Middlewares ---
// Enable parsing JSON request bodies
app.use(express.json());

// --- Routes ---
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
}); // Basic health check endpoint

// Mount the MMS routes
app.use('/api', mmsRoutes); // Prefix routes with /api

// --- Central Error Handler ---
// This middleware catches errors passed via next(error)
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, next) => {
  console.error('Unhandled Error:', err.stack || err);

  // Default error response
  let statusCode = 500;
  let errorMessage = 'Internal Server Error';
  let errorDetails = {};

  // Check if it's an Axios error with response from Infobip
  if (err.message.startsWith('Infobip API Error:') && err.message.includes(' - ')) {
    const parts = err.message.split(' - ');
    if (parts.length >= 2) {
        const statusMatch = parts[0].match(/(\d{3})/);
        if (statusMatch) {
            statusCode = parseInt(statusMatch[1], 10);
        }
        errorMessage = `Infobip API Error: ${statusCode}`;
        try {
            // Attempt to parse the JSON details part
            errorDetails = JSON.parse(parts.slice(1).join(' - '));
        } catch (parseError) {
            console.error(""Could not parse Infobip error details:"", parseError);
            // Fallback: provide the raw string details if JSON parsing fails
            errorDetails = { rawError: parts.slice(1).join(' - ') };
        }
    } else {
        // Fallback if parsing the specific format fails
        errorMessage = err.message;
    }

  } else if (err.message.includes('Failed to fetch media') || err.message.includes('Invalid recipient') || err.message.includes('Missing required fields') || err.message.includes('credentials or sender number are missing')) {
      statusCode = 400; // Bad Request for input/media/config issues
      errorMessage = err.message;
  } else if (err.message.includes('No response received from server')) {
      statusCode = 503; // Service Unavailable (maybe Infobip down or network issue)
      errorMessage = err.message;
  }
  // Add more specific error checks if needed

  res.status(statusCode).json({
    error: errorMessage,
    details: errorDetails, // Include parsed Infobip details or raw error string
    message: err.message // Keep original message for wider context if needed
  });
});

// --- Start Server ---
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
  console.log(`API Endpoint available at POST http://localhost:${PORT}/api/send-mms`);
});

Add Start Script and Ensure type: module in package.json:

Modify your package.json to include a start script and ensure ""type"": ""module"" is present to enable ES Module syntax (import/export).

json
{
  // ... other package.json contents (name, version, etc.)
  ""type"": ""module"",
  ""main"": ""src/server.js"",
  ""scripts"": {
    ""start"": ""node src/server.js"",
    ""test"": ""echo \""Error: no test specified\"" && exit 1""
  },
  // ... dependencies, devDependencies, etc.
}

Testing the API Endpoint:

  1. Start the server:

    bash
    npm start
  2. Send a POST request using curl (or Postman/Insomnia): Remember to replace +1_REPLACE_WITH_RECIPIENT_NUMBER with an actual E.164 formatted recipient phone number.

    bash
    curl -X POST http://localhost:3000/api/send-mms \
    -H ""Content-Type: application/json"" \
    -d '{
      ""recipient"": ""+1_REPLACE_WITH_RECIPIENT_NUMBER"",
      ""subject"": ""Hello from Express!"",
      ""text"": ""Here is a picture sent via Infobip MMS API."",
      ""mediaUrl"": ""https://www.infobip.com/wp-content/uploads/2021/09/01-infobip-logo-scaled.jpg""
    }'

Expected Response (Success):

json
{
  ""message"": ""MMS send request accepted by Infobip."",
  ""infobipResponse"": {
    ""bulkId"": ""some-unique-bulk-id"",
    ""messages"": [
      {
        ""to"": ""+1_REPLACE_WITH_RECIPIENT_NUMBER"",
        ""status"": {
          ""groupId"": 1,
          ""groupName"": ""PENDING"",
          ""id"": 7,
          ""name"": ""PENDING_ENROUTE"",
          ""description"": ""Message sent to next instance""
        },
        ""messageId"": ""some-unique-message-id""
      }
    ]
  }
}

Expected Response (Error - e.g., Invalid API Key):

json
{
    ""error"": ""Infobip API Error: 401"",
    ""details"": {
        ""requestError"": {
            ""serviceException"": {
                ""messageId"": ""UNAUTHORIZED"",
                ""text"": ""Invalid login details""
            }
        }
    },
    ""message"": ""Infobip API Error: 401 - {\""requestError\"":{\""serviceException\"":{\""messageId\"":\""UNAUTHORIZED\"",\""text\"":\""Invalid login details\""}}}""
}

4. Integrating with Infobip (Credentials & Setup)

  • Obtaining Credentials:
    1. Log in to your Infobip Portal.
    2. Base URL: Your unique Base URL is often displayed prominently on the dashboard/home page after login. It looks like xxxxx.api.infobip.com.
    3. API Key: Navigate to the ""Developers"" or ""API Keys"" section (exact naming might vary). Generate a new API key if you don't have one. Copy this key securely.
    4. Sender Number: Go to ""Channels and Numbers"" > ""Numbers"". Ensure you have an MMS-capable number acquired here. Copy the number in E.164 format.
  • Secure Storage: We are using a .env file loaded by dotenv. This file is listed in .gitignore to prevent accidental commits of your secret credentials. In production environments, use your hosting provider's mechanism for managing environment variables securely (e.g., AWS Secrets Manager, Heroku Config Vars, Docker Secrets).
  • .env Variables (Recap):
    • INFOBIP_BASE_URL: (String) Replace placeholder. Base domain for Infobip API calls.
    • INFOBIP_API_KEY: (String) Replace placeholder. Your secret API key.
    • SENDER_NUMBER: (String) Replace placeholder. Your MMS-capable number from Infobip (E.164 format).
    • PORT: (Number) Local port for the Express server.

5. Error Handling, Logging, and Retry Mechanisms

  • Error Handling Strategy:
    • Service layer (infobipService.js) throws errors for config issues, input validation, media fetching failures, and Infobip API errors (extracting status and data).
    • Controller layer (mmsController.js) performs input validation and catches errors, passing them via next(error).
    • Central error handler (server.js) catches errors, logs them, attempts to parse Infobip details from the error message (using error.response data if available via the service layer), and sends a structured JSON response.
  • Logging:
    • Uses basic console.log and console.error.
    • Logs server start, requests, success, Infobip responses/errors, media fetch errors, unhandled errors.
    • Production Enhancement: Use libraries like Winston or Pino for structured, leveled logging and configurable outputs (files, external services).
  • Retry Mechanisms:
    • Not implemented by default.
    • Consideration: For transient issues (network errors, 503/504 from Infobip), implement retries using libraries like async-retry around the axios.post call in infobipService.js. Use exponential backoff.
    • Caution: Be mindful of idempotency. Sending messages is often safe to retry on failure, but use messageId from Infobip's response (if a partial failure occurred) or implement your own idempotency keys if needed.

6. Database Schema and Data Layer (Optional)

While not required for basic sending, tracking message status often involves a database.

  • Potential Schema (e.g., PostgreSQL):
    sql
    CREATE TABLE mms_log (
        id SERIAL PRIMARY KEY,
        infobip_message_id VARCHAR(255) UNIQUE, -- Store Infobip's ID
        infobip_bulk_id VARCHAR(255),
        recipient VARCHAR(20) NOT NULL,
        sender VARCHAR(20) NOT NULL,
        subject TEXT,
        text_content TEXT,
        media_url VARCHAR(2048),
        sent_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
        infobip_status_group_name VARCHAR(50), -- Track status (e.g., PENDING, DELIVERED)
        infobip_status_description TEXT,
        last_status_update_at TIMESTAMP WITH TIME ZONE,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
    );
    CREATE INDEX idx_mms_log_status ON mms_log(infobip_status_group_name);
    CREATE INDEX idx_mms_log_recipient ON mms_log(recipient);
  • Data Access: Use an ORM (Prisma, Sequelize, TypeORM) or query builder (Knex.js) to insert logs after sending and update status via webhooks (Section 11).
  • Migrations: Use tools provided by the ORM/builder for schema management.

7. Security Features

  • Input Validation and Sanitization:
    • Basic validation in the controller.
    • Enhancement: Use libraries like joi or express-validator for more robust schema validation and sanitization in routes/controllers.
  • API Key Security: Managed via .env (local) and secure environment variables (production). Never hardcode keys.
  • Rate Limiting: Use middleware like express-rate-limit to prevent abuse. Apply it before your API routes in server.js.
  • HTTPS: Essential in production. Use a reverse proxy (Nginx, Caddy) or platform service (Heroku, AWS ELB) for TLS termination.
  • Helmet: Use helmet middleware in server.js (app.use(helmet());) to set security-related HTTP headers.

8. Handling Special Cases

  • Character/Size Limits (Infobip Recommendations):
    • Subject: < 50 chars recommended.
    • Text: Max 1600 chars possible, but < 500 recommended. UTF-8.
    • Media: JPEG, GIF, PNG, MP4 common. Total MMS size ideally < 300KB (up to 1MB possible but less reliable). Ensure mediaUrl points to suitable files.
  • International Number Formatting: E.164 format (+1...) expected. Use libphonenumber-js for robust validation/formatting if needed.
  • Media URL Accessibility: Service handles fetch errors. Ensure URLs are public. Consider adding checks for allowed domains or protocols if necessary.
  • Infobip Free Trial Limitations: Sending usually restricted to your verified number.
  • 10DLC/Sender Requirements (US A2P): Ensure your SENDER_NUMBER is correctly registered and provisioned for A2P MMS traffic if applicable.

9. Implementing Performance Optimizations

  • Media Handling: Streaming media via axios (responseType: 'stream') is key for memory efficiency.
  • Asynchronous Operations: async/await prevents blocking the Node.js event loop.
  • Connection Pooling: Axios/Node.js handle TCP connection reuse automatically.
  • Caching: Caching fetched media content (if mediaUrl is reused often) is possible but adds complexity. Use in-memory (node-cache) or external (Redis) caches.
  • Load Testing: Use tools like k6, artillery, autocannon to test throughput and identify bottlenecks under load.
  • Profiling: Use Node.js profiler or clinic.js to analyze CPU/memory usage.

10. Adding Monitoring, Observability, and Analytics

  • Health Checks: /health endpoint for basic checks. Enhance for production (check dependencies). Integrate with uptime monitors.
  • Performance Metrics: Use prom-client for Prometheus metrics (request latency, rate, errors) or integrate with APM tools. Monitor Infobip API call latency specifically. Track system metrics (CPU, memory).
  • Error Tracking: Integrate with Sentry, Bugsnag, Datadog APM for real-time error capture and alerting.
    bash
    # Example Sentry setup
    npm install @sentry/node @sentry/tracing
    javascript
    // src/server.js
    import * as Sentry from ""@sentry/node""; // Corrected import
    import * as Tracing from ""@sentry/tracing""; // Corrected import
    import express from 'express';
    import dotenv from 'dotenv';
    import mmsRoutes from './routes/mmsRoutes.js';
    
    dotenv.config();
    
    Sentry.init({
      dsn: process.env.SENTRY_DSN, // Get DSN from Sentry project settings (set in .env or env vars)
      integrations: [
        new Sentry.Integrations.Http({ tracing: true }),
        // Initialize Express integration later, after 'app' is defined
      ],
      tracesSampleRate: 1.0, // Adjust in production
      environment: process.env.NODE_ENV || 'development',
    });
    
    const app = express(); // Define app first
    
    // Add Express integration now that 'app' exists
    Sentry.addIntegration(new Tracing.Integrations.Express({ app }));
    
    // Sentry handlers MUST be registered BEFORE any other error handler
    // and AFTER all controllers/routers
    app.use(Sentry.Handlers.requestHandler());
    app.use(Sentry.Handlers.tracingHandler());
    
    // --- Middlewares --- (Your existing ones)
    app.use(express.json());
    
    // --- Routes --- (Your existing ones)
    app.get('/health', (req, res) => {
      res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
    });
    app.use('/api', mmsRoutes);
    
    // The Sentry error handler must be before other error middleware
    app.use(Sentry.Handlers.errorHandler());
    
    // Your custom error handler (must be AFTER Sentry's)
    // eslint-disable-next-line no-unused-vars
    app.use((err, req, res, next) => {
        // Your existing error handler logic from Section 3
        console.error('Unhandled Error:', err.stack || err);
        let statusCode = 500;
        let errorMessage = 'Internal Server Error';
        let errorDetails = {};
        // ... (rest of your error parsing logic) ...
        res.status(statusCode).json({
          error: errorMessage,
          details: errorDetails,
          message: err.message
        });
    });
    
    // --- Start Server ---
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Server running on http://localhost:${PORT}`);
      console.log(`API Endpoint available at POST http://localhost:${PORT}/api/send-mms`);
    });
  • Dashboards: Visualize metrics using Grafana, Datadog, Kibana, etc. Track MMS sent rate, error rate, latency.
  • Alerting: Set up alerts in your monitoring system for high error rates, latency spikes, health check failures.

11. Troubleshooting and Caveats

  • Common Errors & Solutions:
    • 401 Unauthorized (Infobip): Check INFOBIP_API_KEY and Authorization: App <key> format.
    • 400 Bad Request (Infobip): Check recipient format (E.164), request structure, or Infobip's detailed error message.
    • 403 Forbidden (Infobip): Check MMS enablement, free trial limits, A2P/10DLC compliance. Contact Infobip support if needed.
    • Failed to fetch media...: Verify mediaUrl accessibility and correctness.
    • ECONNREFUSED/ENOTFOUND: Check INFOBIP_BASE_URL, DNS, network connectivity.
    • Large File Errors/Timeouts: Optimize media (<300KB recommended). Consider increasing Axios timeout (timeout: 60000) cautiously.
  • Platform Limitations:
    • Carrier/handset MMS support varies.
    • Infobip API rate limits apply.
    • SMIL Files: While Infobip supports SMIL for complex layouts, note that iPhones often ignore them. Avoid SMIL for simple text + media messages for broader compatibility.
  • Version Compatibility: Ensure Node.js version (>=14) is compatible with dependencies.
  • Delivery Status Updates (Webhooks): This guide focuses on sending. To track delivery, configure Infobip Delivery Reports to POST status updates to an endpoint you create in your application. Store these updates (e.g., in the mms_log table). The bulkId and messageId returned in the initial send response are key to correlating these updates.

12. Deployment and CI/CD

  • Environments: Development (local), Staging/Production (cloud/server).
  • Deployment Steps: Build (if needed), Package, Configure Environment Variables securely (NO .env file in production), Install Prod Dependencies (npm ci --only=production), Run with Process Manager (PM2), Setup Reverse Proxy (Nginx/Caddy for HTTPS, etc.).
  • Containerization (Docker):
    dockerfile
    # Dockerfile
    FROM node:18-alpine AS base
    
    WORKDIR /app
    
    # Install production dependencies based on package-lock.json
    COPY package*.json ./
    RUN npm ci --only=production
    
    # Copy application code
    COPY ./src ./src
    
    # Expose the application port
    EXPOSE 3000
    
    # Set Node environment to production
    ENV NODE_ENV=production
    # Set PORT (can be overridden at runtime)
    ENV PORT=3000
    
    # User security best practice
    USER node
    
    # Command to run the application
    CMD [""node"", ""src/server.js""]
    Build/Run:
    bash
    docker build -t infobip-mms-express .
    # Run passing env vars securely (e.g., via -e, --env-file, or orchestration secrets)
    docker run -p 3000:3000 \
      -e INFOBIP_BASE_URL='your_actual_base_url' \
      -e INFOBIP_API_KEY='your_actual_api_key' \
      -e SENDER_NUMBER='+1_your_actual_sender' \
      -e NODE_ENV='production' \
      --name mms-app infobip-mms-express
  • CI/CD Pipeline (e.g., GitHub Actions): Automate testing, building (Docker image), pushing (to registry like Docker Hub), and deploying (e.g., SSH to server, update service/container).
    yaml
    # .github/workflows/deploy.yml (Conceptual Example)
    name: Deploy MMS API
    
    on:
      push:
        branches: [ main ]
    
    jobs:
      build-and-deploy:
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Set up Node.js
          uses: actions/setup-node@v3
          with:
            node-version: '18' # Match Dockerfile version
    
        - name: Install Dependencies
          run: npm ci
    
        # Add steps for linting, testing here
    
        - name: Login to Docker Hub
          uses: docker/login-action@v2
          with:
            username: ${{ secrets.DOCKERHUB_USERNAME }}
            password: ${{ secrets.DOCKERHUB_TOKEN }}
    
        - name: Build and push Docker image
          uses: docker/build-push-action@v4
          with:
            context: .
            push: true
            tags: ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest
    
        # Add deployment steps here (e.g., SSH, kubectl apply, etc.)
        # Example: SSH and restart docker container
        # - name: Deploy to Server
        #   uses: appleboy/ssh-action@master
        #   with:
        #     host: ${{ secrets.SERVER_HOST }}
        #     username: ${{ secrets.SERVER_USER }}
        #     key: ${{ secrets.SSH_PRIVATE_KEY }}
        #     script: |
        #       docker pull ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest
        #       docker stop mms-app || true
        #       docker rm mms-app || true
        #       docker run -d --restart always -p 3000:3000 \
        #         -e INFOBIP_BASE_URL='${{ secrets.INFOBIP_BASE_URL }}' \
        #         -e INFOBIP_API_KEY='${{ secrets.INFOBIP_API_KEY }}' \
        #         -e SENDER_NUMBER='${{ secrets.SENDER_NUMBER }}' \
        #         -e NODE_ENV='production' \
        #         --name mms-app ${{ secrets.DOCKERHUB_USERNAME }}/infobip-mms-express:latest

Frequently Asked Questions

How to send MMS messages with Node.js and Express?

Use the Infobip API along with Express and Node.js. Create a REST API endpoint that accepts recipient details, message content, and a media URL, then uses the Infobip API to send the MMS message. This setup enables programmatic sending of MMS messages for various applications.

What is the Infobip API used for in this Node.js application?

The Infobip API is the core service for sending MMS messages in this Node.js and Express application. It handles constructing the message with media and text and delivers it to the recipient's mobile carrier. The Node.js app acts as an intermediary to format requests and handle responses from the Infobip platform.

Why does MMS require multipart/form-data with Infobip?

Infobip's MMS API expects data in the multipart/form-data format, which handles combining different data types, such as text, images, and other media, within a single HTTP request. This format is distinct from the JSON typically used for SMS messages and is crucial for sending rich media messages via Infobip.

When should I use environment variables with dotenv?

Always use environment variables for sensitive information like API keys, and load them with dotenv during development. This keeps secrets out of your codebase and enhances security. In production, leverage your platform's secure mechanisms for managing environment variables.

Can I send large media files via MMS with Infobip?

While the Infobip API supports MMS messages up to 1MB, it's recommended to keep the media size under 300KB for reliable delivery. Large files might lead to delivery issues or timeouts. Optimize media for mobile devices to ensure efficient transmission.

How to set up an Infobip MMS project in Node.js?

Install necessary packages like Express, Axios, form-data, and dotenv. Set up project structure with appropriate routes, controllers, and services. Configure environment variables for credentials and server details. Implement Infobip service logic with error handling and response parsing.

What is the recommended format for phone numbers when sending MMS?

Use the E.164 format (e.g., +12223334444) for recipient phone numbers. It ensures compatibility and accuracy in international MMS delivery. Use a library like libphonenumber-js to validate or format numbers consistently.

Why are retries important for MMS message sending?

Retries handle transient failures like network issues or temporary Infobip API unavailability. The async-retry library, with exponential backoff, can be used for implementing retries in the axios.post call within the Infobip service logic.

How to fetch media from a URL for MMS with Axios?

Use Axios's `responseType: 'stream'` option to fetch media from the provided URL efficiently. This allows handling potentially large files without loading them fully into memory, leading to better performance, especially with larger files.

What security measures are essential for a Node.js MMS app?

Input validation, secure API key storage, and rate limiting are crucial security aspects. Use tools like Joi for validation, dotenv and secure environment variable storage in production, and express-rate-limit to prevent abuse and protect your application.

How to handle Infobip API errors in my Express app?

Implement a central error handler in your server.js file that logs details and parses Infobip's error messages, enabling informed debugging and user-friendly error responses. The error details often give insights into the issue's root cause.

What are the character limits for MMS subject and text content?

While Infobip technically supports larger lengths, for subject lines keep them under 50 characters and for text content under 500 characters is recommended. Use UTF-8 encoding for text to accommodate a wide range of characters.

How to monitor the performance of my MMS sending application?

Implement health checks, track metrics using Prometheus or APM tools, monitor Infobip API latency specifically, and integrate with error tracking solutions like Sentry or Bugsnag for real-time alerts and insights.

What is the purpose of a database schema for an MMS app?

A database schema allows storing message logs, including recipient details, content, delivery status, and timestamps. This data can be used to track message history, resend failed messages, and generate reports.

How to deploy a Node.js MMS application using Docker?

Create a Dockerfile, build the image, and run the container, ensuring environment variables are configured securely through mechanisms like -e, --env-file, or Docker secrets, and that a process manager like PM2 is used in production.