code examples

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

Send MMS with Fastify and Node.js Using Infobip API: Complete Guide

Learn how to send MMS messages using Fastify, Node.js, and the Infobip API. Complete tutorial with code examples, error handling, and deployment best practices for multimedia messaging.

Send MMS with Fastify and Node.js Using Infobip API: Complete Guide

This comprehensive guide shows you how to build a production-ready Node.js application using the Fastify framework to send Multimedia Messaging Service (MMS) messages via the Infobip API. Whether you're adding multimedia messaging capabilities to an existing application or building a new messaging service, this tutorial covers everything from initial project setup and API configuration to error handling, security best practices, and deployment strategies.

By the end of this tutorial, you'll have a fully functional Fastify API endpoint capable of accepting MMS details (recipient, sender, media URL, caption) and relaying them through Infobip's messaging infrastructure for reliable delivery. This implementation solves the common need to programmatically integrate rich media messaging into applications without manual intervention.

Project Overview and Goals

  • Goal: Build a simple but robust API endpoint using Fastify to send MMS messages via Infobip.
  • Problem Solved: Programmatically send images and captions as MMS messages without needing manual intervention through a web interface.
  • Technologies:
    • Node.js: The runtime environment for our JavaScript backend.
    • Fastify: A high-performance, low-overhead web framework for Node.js, chosen for its speed, extensibility, and developer experience. This guide uses Fastify v4.x; Fastify v5 is also available and follows similar patterns.
    • Infobip API: The third-party service used for sending MMS messages.
    • @infobip-api/sdk: The official Infobip Node.js SDK, simplifying interaction with their API.
    • dotenv: For managing environment variables securely.
  • Outcome: A Fastify server with a POST /send-mms endpoint that takes JSON payload and sends an MMS message using credentials stored in environment variables.
  • Prerequisites:
    • Node.js v18, v20, or v22 (LTS versions) and npm (or yarn) installed. Note: Node.js v14 reached end-of-life on April 30, 2023.
    • An active Infobip account. You can create a free trial account.
      • Note: Free trial accounts often have limitations, such as only being able to send messages to the phone number used during registration.
    • Your Infobip API Key and Base URL. Find these in your Infobip account dashboard.
    • A publicly accessible URL for the media file (e.g., image) you want to send. Infobip needs to fetch the media from this URL. Supported formats typically include JPEG, PNG, and GIF; verify current supported formats in the Infobip MMS API documentation.

System Architecture:

+-------------+ +-----------------+ +-------------------+ +-------------+ | |--(1)-->| |----->| |----->| | | User/Client | | Fastify API | (2) | Infobip Service | (3) | Infobip API | | (e.g. curl) |<--(5)--| (/send-mms) |<-----| (using SDK) |<-----| | | | | | (4) | | | | +-------------+ +-----------------+ +-------------------+ +-------------+ 1. Client sends POST request with MMS details (to, from, mediaUrl, caption) to Fastify. 2. Fastify route handler validates input and calls the Infobip service module. 3. Infobip service module uses the SDK to format the request and sends it to the Infobip API endpoint. 4. Infobip API processes the request and returns a response (success/failure) to the SDK/service. 5. Fastify API relays the success or error response back to the client.

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 fastify-infobip-mms
    cd fastify-infobip-mms
  2. Initialize Node.js Project: Initialize the project using npm. The -y flag accepts the default settings.

    bash
    npm init -y

    This creates a package.json file.

  3. Install Dependencies: We need Fastify for the web server, the Infobip SDK to interact with their API, and dotenv to manage environment variables.

    bash
    npm install fastify @infobip-api/sdk dotenv
  4. Create Project Structure: Set up a basic directory structure for organization:

    text
    fastify-infobip-mms/
    ├── node_modules/
    ├── services/
    │   └── infobipService.js   # Logic for interacting with Infobip
    ├── .env                    # Local environment variables (DO NOT COMMIT)
    ├── .env.example            # Example environment variables (Commit this)
    ├── .gitignore              # Specify files/folders to ignore in Git
    ├── package.json
    ├── package-lock.json
    └── server.js               # Main Fastify application file
  5. Configure .gitignore: Create a .gitignore file in the project root and add node_modules and .env to prevent committing them to version control.

    text
    # .gitignore
    node_modules
    .env
  6. Set up Environment Variables: Create two files in the root directory: .env (for your actual secrets) and .env.example (as a template).

    • .env.example:

      dotenv
      # .env.example
      # Infobip Credentials
      INFOBIP_API_KEY=YOUR_INFOBIP_API_KEY_HERE
      INFOBIP_BASE_URL=YOUR_INFOBIP_BASE_URL_HERE
      
      # Application Settings
      PORT=3000
      LOG_LEVEL=info
    • .env: Copy .env.example to .env and replace the placeholder values with your actual Infobip API Key and Base URL obtained from your Infobip dashboard. Set the desired port.

      dotenv
      # .env
      # Infobip Credentials
      INFOBIP_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      INFOBIP_BASE_URL=xxxxx.api.infobip.com
      
      # Application Settings
      PORT=3000
      LOG_LEVEL=info
      • Purpose: Using environment variables keeps sensitive credentials out of your codebase and enables different configurations for development, staging, and production environments. dotenv loads these variables into process.env for easy access in your Node.js application during development.

2. Implementing Core Functionality (Infobip Service)

Now, let's create the service module responsible for handling communication with the Infobip API using their SDK.

  1. Create infobipService.js: Inside the services directory, create the infobipService.js file.

  2. Implement the Service: Add the following code to services/infobipService.js.

    javascript
    // services/infobipService.js
    'use strict';
    
    const { Infobip, AuthType } = require('@infobip-api/sdk');
    
    // Check for essential environment variables during module load.
    // In a production application, this check is often better placed in the main
    // application entry point (server.js) before server initialization,
    // allowing for a more controlled shutdown or error reporting.
    if (!process.env.INFOBIP_API_KEY || !process.env.INFOBIP_BASE_URL) {
      console.error('Error: Missing Infobip API Key or Base URL in environment variables.');
      // Throwing an error here will typically stop the application startup process.
      throw new Error('Missing Infobip API Key or Base URL in environment variables.');
    }
    
    // Initialize the Infobip client
    const infobip = new Infobip({
      baseUrl: process.env.INFOBIP_BASE_URL,
      apiKey: process.env.INFOBIP_API_KEY,
      authType: AuthType.ApiKey, // Specify API Key authentication
    });
    
    /**
     * Sends an MMS message using the Infobip API.
     * @param {string} to - The recipient's phone number in E.164 format (e.g., +14155552671).
     * @param {string} from - Your Infobip MMS-enabled sender number in E.164 format.
     * @param {string} mediaUrl - The publicly accessible URL of the media file (image).
     * @param {string} [caption] - Optional caption text for the media.
     * @returns {Promise<object>} - The response object from the Infobip API.
     * @throws {Error} - Throws an error if the API call fails.
     */
    async function sendMms(to, from, mediaUrl, caption) {
      console.log(`Attempting to send MMS: to=${to}, from=${from}, mediaUrl=${mediaUrl}`); // Basic logging
    
      // Construct the MMS payload according to Infobip SDK/API requirements
      const mmsPayload = {
        from: from,
        to: to,
        content: {
          mediaUrl: mediaUrl,
          // caption: caption || '', // Include caption if provided
          // mediaType: 'IMAGE' // You might need to specify this depending on SDK version/API behavior
        },
        // Optional: Add other parameters like delivery reports callback URL etc.
        // notifyUrl: 'YOUR_DELIVERY_REPORT_WEBHOOK_URL'
      };
    
      // Add caption only if it exists
      if (caption) {
        mmsPayload.content.caption = caption;
      }
    
      try {
        // Use the Infobip SDK to send the MMS message via the 'mms' channel
        // The specific method might vary slightly based on SDK updates, check SDK docs.
        // Assuming a method like `infobip.channels.mms.send(payload)` exists.
        // Referencing potential structure based on SDK patterns. The MMS API
        // might expect messages in an array even for single sends. Verify this structure.
        const response = await infobip.channels.mms.send({
            messages: [mmsPayload]
        });
    
        console.log('Infobip API Response:', JSON.stringify(response, null, 2));
        return response; // Return the successful response body
    
      } catch (error) {
        console.error('Error sending MMS via Infobip:', error.message);
        // Log the full error if available (might contain more details from Infobip)
        if (error.response && error.response.data) {
          console.error('Infobip Error Details:', JSON.stringify(error.response.data, null, 2));
        } else {
          console.error('Full Error Object:', error);
        }
        // Re-throw the error to be handled by the calling function (API route)
        throw new Error(`Failed to send MMS: ${error.message}`);
      }
    }
    
    module.exports = {
      sendMms,
    };
    • Explanation:
      • We import the Infobip client and AuthType enum from the SDK.
      • We check for required environment variables. Throwing an error here will prevent the application from starting if they are missing.
      • We initialize the infobip client instance using the API Key and Base URL loaded from process.env. We explicitly state we're using AuthType.ApiKey.
      • The sendMms function takes the necessary parameters (to, from, mediaUrl, caption).
      • It constructs the mmsPayload object matching the structure potentially expected by the Infobip MMS API (e.g., /mms/1/single or similar, often requiring a messages array). Consult the latest Infobip MMS API documentation for the exact payload fields and structure.
      • We use a try...catch block to handle potential errors during the API call.
      • infobip.channels.mms.send(...) is the presumed SDK method to send the message. Note: The exact SDK method name, import path, and payload structure should be verified against the current version of @infobip-api/sdk documentation. SDK structures may change between major versions.
      • Errors are logged with details, and the error is re-thrown to be caught by the API route handler.
      • The function is exported for use in our Fastify server.

3. Building the API Layer (Fastify Server and Route)

Now, let's set up the Fastify server and create the API endpoint that will use our infobipService.

  1. Create server.js: In the project root, create the server.js file.

  2. Implement the Fastify Server: Add the following code to server.js:

    javascript
    // server.js
    'use strict';
    
    // Load environment variables from .env file
    require('dotenv').config();
    
    // Check required environment variables before proceeding
    if (!process.env.INFOBIP_API_KEY || !process.env.INFOBIP_BASE_URL) {
        console.error('Startup Error: Missing Infobip API Key or Base URL. Check .env file.');
        process.exit(1); // Exit if critical config is missing
    }
    
    const fastify = require('fastify')({
      logger: {
        level: process.env.LOG_LEVEL || 'info', // Use log level from env or default to info
        transport: process.env.NODE_ENV !== 'production' ? { // Use pino-pretty only in non-production
          target: 'pino-pretty',
          options: {
            translateTime: 'HH:MM:ss Z',
            ignore: 'pid,hostname',
          },
        } : undefined, // Use default JSON logger in production
      },
    });
    
    // Import the Infobip service *after* checking environment variables
    const infobipService = require('./services/infobipService');
    
    // --- API Routes ---
    
    // Basic health check route
    fastify.get('/', async (request, reply) => {
      return { status: 'ok', timestamp: new Date().toISOString() };
    });
    
    // Define schema for request body validation
    const sendMmsSchema = {
      body: {
        type: 'object',
        required: ['to', 'from', 'mediaUrl'],
        properties: {
          to: { type: 'string', description: 'Recipient phone number (E.164 format)', pattern: '^\\+[1-9]\\d{1,14}$' }, // E.164 pattern
          from: { type: 'string', description: 'Sender phone number (E.164 format)', pattern: '^\\+[1-9]\\d{1,14}$' }, // E.164 pattern
          mediaUrl: { type: 'string', format: 'url', description: 'Publicly accessible URL for the media' },
          caption: { type: 'string', description: 'Optional caption for the media' },
        },
        additionalProperties: false // Disallow properties not defined in the schema
      },
      response: { // Optional: Define expected response schemas
        200: {
          type: 'object',
          properties: {
            messageId: { type: 'string' },
            status: { type: 'string' },
            details: { type: 'object' } // Include the full Infobip response
          }
        },
        // Add schemas for error responses (400, 500) if desired
      }
    };
    
    // MMS Sending Route
    fastify.post('/send-mms', { schema: sendMmsSchema }, async (request, reply) => {
      const { to, from, mediaUrl, caption } = request.body;
      request.log.info(`Received request to send MMS to ${to} from ${from}`);
    
      try {
        const result = await infobipService.sendMms(to, from, mediaUrl, caption);
    
        // Extract relevant info for the client response.
        // **Important:** Adjust this parsing based on the *actual* Infobip API/SDK response structure.
        // The example below assumes the primary message status is within `result.messages[0]`.
        const firstMessageResult = result?.messages?.[0]; // Use optional chaining for safety
        const responsePayload = {
          messageId: firstMessageResult?.messageId || 'N/A',
          status: firstMessageResult?.status?.groupName || 'UNKNOWN',
          details: result, // Send back the full Infobip response for context
        };
    
        reply.code(200).send(responsePayload);
    
      } catch (error) {
        request.log.error(`Error in /send-mms route: ${error.message}`);
        // Determine appropriate status code (e.g., 400 for validation issues caught by service, 500 for API/internal errors)
        // For simplicity here, we send 500 for any error originating from the service layer call.
        reply.code(500).send({ error: 'Failed to send MMS', details: error.message });
      }
    });
    
    // --- Start Server ---
    
    const start = async () => {
      try {
        const port = process.env.PORT || 3000;
        await fastify.listen({ port: parseInt(port, 10), host: '0.0.0.0' }); // Listen on all available network interfaces
        // fastify.log is available after listen resolves successfully
        fastify.log.info(`Server listening on port ${port}`);
      } catch (err) {
        // Use console.error for startup errors as logger might not be ready
        console.error('Error starting server:', err);
        process.exit(1);
      }
    };
    
    start();
    • Explanation:
      • We load environment variables using require('dotenv').config(); at the very top.
      • A critical check for INFOBIP_API_KEY and INFOBIP_BASE_URL is added before initializing Fastify or requiring the service, causing the app to exit if they are missing.
      • A Fastify instance is created with logging enabled. We conditionally use pino-pretty for development logging readability (install it with npm install --save-dev pino-pretty). Production uses standard JSON logging.
      • The infobipService is imported after the environment variable check.
      • A basic GET / route is added for health checks.
      • A sendMmsSchema is defined using Fastify's schema validation. This ensures incoming requests have the required fields (to, from, mediaUrl), checks their types and formats (URL, E.164 phone number pattern: `^\\+[1-9]\\d{1,14}$`), and disallows extra properties. This provides input validation and security.
      • The POST /send-mms route is defined:
        • It uses the sendMmsSchema for automatic request validation. If validation fails, Fastify automatically sends a 400 Bad Request response.
        • It extracts the validated data from request.body.
        • It calls infobipService.sendMms within a try...catch block.
        • On success, it constructs a response payload (attempting to parse the Infobip response; verify this parsing against actual API responses) and sends a 200 OK status.
        • On failure (error caught from the service), it logs the error and sends a 500 Internal Server Error response.
      • The start function listens on the configured port (from .env or default 3000) and handles server startup errors. Note the use of parseInt(port, 10) for the port number.
  3. Add Start Script: Modify your package.json to include start scripts.

    json
    // package.json
    {
      ""name"": ""fastify-infobip-mms"",
      ""version"": ""1.0.0"",
      ""description"": """",
      ""main"": ""server.js"",
      ""scripts"": {
        ""start"": ""node server.js"",
        ""dev"": ""node --watch server.js | pino-pretty"",
        ""test"": ""echo \""Error: no test specified\"" && exit 1""
      },
      ""keywords"": [],
      ""author"": """",
      ""license"": ""ISC"",
      ""dependencies"": {
        ""@infobip-api/sdk"": ""^X.X.X"", // Replace with actual installed version
        ""dotenv"": ""^X.X.X"",         // Replace with actual installed version
        ""fastify"": ""^X.X.X""         // Replace with actual installed version
      },
      ""devDependencies"": {
        ""pino-pretty"": ""^X.X.X""     // Replace with actual installed version
      }
    }
    • npm start: Runs the server normally (intended for production, uses JSON logging).
    • npm run dev: Runs the server using Node's watch mode (restarts on file changes) and pipes logs through pino-pretty for better readability during development. Requires npm install --save-dev pino-pretty.

4. Integrating with Third-Party Services (Infobip Configuration Recap)

We've already integrated the core service, but let's recap the configuration points:

  1. Infobip Account: You need an active account.
  2. API Credentials:
    • INFOBIP_API_KEY: Obtain this from the Infobip Portal (usually under API Keys). It's used for authentication.
    • INFOBIP_BASE_URL: Your unique API endpoint URL provided by Infobip (e.g., xxxxx.api.infobip.com). This directs the SDK to the correct API server.
  3. Secure Storage: Store these credentials in the .env file locally and use environment variables in your deployment environment. Never commit your .env file or hardcode credentials directly in the source code.
  4. Sender Number (from): The from number used in the API call must be an MMS-enabled number registered in your Infobip account and authorized for sending.
  5. Media URL (mediaUrl): The URL must be publicly accessible without authentication for Infobip's servers to fetch the media file. Ensure the URL points directly to the image file (JPEG, PNG, etc.).

5. Error Handling, Logging, and Retries

  • Error Handling Strategy:
    • Startup: Critical configuration errors (missing API keys) prevent the server from starting.
    • Validation: Fastify's schema validation handles malformed requests (400 Bad Request).
    • Service Layer: The infobipService catches errors during the API call (e.g., network issues, invalid credentials, Infobip API errors). It logs detailed error information and throws a generic error upwards.
    • API Route: The POST /send-mms route catches errors from the service layer and returns a standardized error response to the client (e.g., 500 Internal Server Error).
  • Logging:
    • Fastify's built-in logger (request.log) is used for request-related logging. console.log/error is used for startup messages/errors.
    • Log level is configurable via the LOG_LEVEL environment variable. Use info for production, debug or trace for development/troubleshooting.
    • pino-pretty enhances log readability during development. In production, use structured JSON logging (Fastify's default without pino-pretty) for easier analysis by log aggregation tools.
    • Key events logged: Incoming request details, success responses from Infobip, errors encountered at service and route levels, Infobip error details.
  • Retry Mechanisms (Not Implemented, Considerations):
    • For transient network errors or specific Infobip rate-limit responses, implementing a retry strategy with exponential backoff can improve reliability.
    • Libraries like async-retry can simplify this. Wrap the infobipService.sendMms call or the SDK call within a retry function.
    • Be cautious not to retry non-recoverable errors (e.g., invalid API key, invalid destination number). Check Infobip's error codes to decide which are retryable.

6. Database Schema and Data Layer (Not Applicable)

This specific guide focuses solely on the API interaction and doesn't require a database. If you needed to store message history, track statuses via webhooks, or manage user data, you would introduce a database (e.g., PostgreSQL, MongoDB) and a data access layer (e.g., using an ORM like Prisma or Sequelize).

7. Security Features

  • Input Validation: Handled by Fastify's schema validation (sendMmsSchema), preventing malformed data and basic injection attempts. The E.164 pattern (`^\\+[1-9]\\d{1,14}$`) enforces a specific format. additionalProperties: false prevents unexpected data.

  • Secret Management: API keys and sensitive configuration are managed via environment variables (.env locally, platform environment variables in production). .gitignore prevents accidental commits.

  • Rate Limiting: Protect your API from abuse. Use a plugin like @fastify/rate-limit.

    bash
    npm install @fastify/rate-limit
    javascript
    // In server.js, register the plugin *before* your routes
    // Make sure to register plugins asynchronously if needed, e.g., inside the start function or using fastify-plugin
    await fastify.register(require('@fastify/rate-limit'), {
      max: 100, // Max requests per window per IP
      timeWindow: '1 minute'
    });
  • HTTPS: Ensure your deployed application runs over HTTPS to encrypt communication. This is typically handled by your hosting provider or load balancer (reverse proxy).

  • Helmet: Set various security-related HTTP headers using @fastify/helmet.

    bash
    npm install @fastify/helmet
    javascript
    // In server.js, register the plugin *before* your routes
    await fastify.register(require('@fastify/helmet'));

8. Handling Special Cases

  • Phone Number Formatting: The API expects E.164 format (e.g., +14155552671). The schema validates this format using the regex `^\\+[1-9]\\d{1,14}$`. More complex validation might be needed depending on specific regional requirements or if you need to normalize different input formats.

    ITU-T Recommendation E.164 (June 2020): The E.164 format supports up to 15 digits total, with 1-3 digit country codes. The format is + followed by the country code and the national significant number. The country code is a sequence of 1 to 3 digits, and the national significant number is a sequence of 1 to 14 digits. The regex pattern `^\\+[1-9]\\d{1,14}$` is used to validate this format.

  • Media URL Accessibility: Reiterate that the mediaUrl must be public and directly accessible. Internal or authenticated URLs will cause Infobip to fail fetching the media, resulting in an error.

  • Character Limits: Captions might have character limits imposed by carriers or Infobip. Check Infobip documentation for specific limits.

  • Infobip API Errors: Handle specific error codes returned by Infobip (e.g., insufficient funds, invalid number, blocked destination). The current implementation logs the details; a production system might parse error.response.data from the SDK error to map these to more user-friendly error messages or trigger specific alerts.

9. Performance Optimizations (Considerations)

  • Fastify: Chosen for its high performance.
  • SDK Efficiency: The official SDK is generally optimized for API interaction.
  • Asynchronous Operations: Node.js and Fastify are inherently asynchronous, handling I/O (like network calls to Infobip) efficiently without blocking the main thread.
  • Caching: Not directly applicable for sending unique messages, but if retrieving message statuses frequently or using templates, caching could be relevant.
  • Load Testing: Use tools like k6, autocannon, or wrk to test the endpoint's performance under load, especially if high throughput is expected. Identify bottlenecks (CPU, memory, network latency to Infobip).

10. Monitoring, Observability, and Analytics (Considerations)

  • Health Checks: The GET / route provides a basic health check. More sophisticated checks could verify connectivity to Infobip (e.g., by making a low-impact, authenticated API call like fetching account balance, if available).
  • Metrics: Integrate with monitoring tools (e.g., Prometheus via fastify-metrics, Datadog). Track request rates, error rates (HTTP 4xx/5xx), latency (overall API response time and specifically the latency of the infobipService.sendMms call).
  • Error Tracking: Use services like Sentry (@sentry/node) or Datadog APM to capture and aggregate errors in production, providing stack traces and context for easier debugging.
  • Logging Aggregation: In production, configure Fastify's logger to output JSON and ship these logs to a centralized logging platform (e.g., ELK stack, Splunk, Datadog Logs, Grafana Loki) for analysis, searching, and alerting.
  • Infobip Reports: Utilize Infobip's dashboard and potentially configure delivery report webhooks (using the notifyUrl parameter in the payload) to track message delivery status and analytics directly from Infobip.

11. Troubleshooting and Caveats

  • Invalid login details Error (from Infobip):
    • Cause: Incorrect INFOBIP_API_KEY or INFOBIP_BASE_URL.
    • Solution: Double-check the values in your .env file (or deployment environment variables) against the Infobip portal. Ensure the Base URL is correct for your account region and API key type.
  • Failed to fetch media Error (or similar):
    • Cause: The mediaUrl provided is not publicly accessible, points to a non-media file, is malformed, the hosting server blocked Infobip's request (e.g., firewall, User-Agent block), or the URL timed out.
    • Solution: Verify the URL works in a browser's incognito mode or using curl. Ensure it links directly to the image. Check your media server logs if you host the media. Ensure the media type is supported by Infobip MMS.
  • Invalid ""To"" address Error:
    • Cause: The recipient number (to) is not in valid E.164 format (+ followed by country code and number, e.g., +14155552671), is invalid for the destination country, or is on a blacklist.
    • Solution: Ensure the number strictly follows the E.164 format. Check Infobip documentation for any specific number validation rules or country restrictions.
  • Message sending not enabled for number Error:
    • Cause: The from number is not configured or approved for sending MMS in your Infobip account, or MMS sending is not enabled for the specific destination country/carrier.
    • Solution: Contact Infobip support or check your number configuration and capabilities in their portal.
  • Free Trial Limitations: Remember that free trial accounts typically restrict sending to the registered phone number only and may have other limitations.
  • SDK Version Compatibility: Ensure you are using a compatible version of the @infobip-api/sdk with your Node.js version. Check the SDK's documentation or package.json for requirements. Method calls (like infobip.channels.mms.send) and payload structures might change between major SDK versions. Always refer to the specific SDK version documentation you are using.
  • Rate Limits: Hitting Infobip's API rate limits will result in errors (often HTTP 429 Too Many Requests). Implement rate limiting on your API (Section 7) and consider implementing retries with exponential backoff for 429 errors in your service layer.

12. Deployment and CI/CD

  • Environment Configuration: Do not deploy your .env file. Configure INFOBIP_API_KEY, INFOBIP_BASE_URL, PORT, LOG_LEVEL, and NODE_ENV=production using your deployment platform's environment variable management system (e.g., Heroku Config Vars, AWS System Manager Parameter Store, Kubernetes Secrets, Docker environment variables).

  • Deployment Platforms:

    • PaaS (e.g., Heroku, Render): Simple deployment via Git push. Configure environment variables through their dashboard/CLI. Ensure your package.json specifies the correct Node.js engine.
    • Containers (Docker): Create a Dockerfile to package your application. Manage containers using Docker Compose (for simple setups), Kubernetes, or managed services like AWS ECS/Fargate, Google Cloud Run.
    • Serverless (e.g., AWS Lambda, Google Cloud Functions): Adapt the Fastify app using adapters like aws-lambda-fastify or @fastify/aws-lambda, or potentially restructure as individual functions triggered by API Gateway.
  • Dockerfile Example:

    dockerfile
    # Use an official Node.js runtime as a parent image (Choose LTS version like 18 or 20)
    FROM node:18-alpine
    
    # Set the working directory in the container
    WORKDIR /usr/src/app
    
    # Copy package.json and package-lock.json (or yarn.lock)
    # Copy only necessary files first to leverage Docker cache
    COPY package.json package-lock.json* ./
    # Or: COPY package.json yarn.lock ./
    
    # Install production dependencies
    # Using npm ci is generally recommended in CI/CD for reproducible builds
    RUN npm ci --only=production
    # Or: RUN yarn install --production --frozen-lockfile
    
    # Bundle app source code
    COPY . .
    
    # Expose the port the app runs on (should match PORT env var, default 3000)
    EXPOSE 3000
    
    # Define the command to run your app in production
    # Set NODE_ENV=production explicitly if not done elsewhere
    ENV NODE_ENV=production
    # Use array form for CMD
    CMD [ ""node"", ""server.js"" ]
  • CI/CD Pipeline (e.g., GitHub Actions, GitLab CI, Jenkins):

    1. Checkout Code: Get the source code.
    2. Setup Node.js: Specify the correct Node.js version.
    3. Install Dependencies: npm ci or yarn install --frozen-lockfile.
    4. Lint/Format Check: Run tools like ESLint/Prettier (npm run lint - requires adding a lint script).
    5. (Optional) Security Scan: Run vulnerability scans (e.g., npm audit, Snyk).
    6. Build Docker Image (if using containers). Tag the image appropriately.
    7. Push Image to Registry (if using containers): e.g., Docker Hub, AWS ECR, Google GAR.
    8. Deploy: Trigger deployment to your hosting platform (e.g., using platform CLI commands, Infrastructure as Code tools like Terraform, or built-in integrations).
  • Rollback Strategy: Ensure your deployment process allows for easy rollback to a previous stable version in case of deployment failures or runtime issues in the new version.

13. Verification and Testing

  • Manual Verification:
    1. Ensure your .env file has correct credentials and a valid public mediaUrl.

    2. Start the server: npm run dev (for pretty logs) or npm start.

    3. Use curl or a tool like Postman/Insomnia to send a POST request to http://localhost:3000/send-mms (or your configured port).

    4. curl Example:

      • Important: Replace +1xxxxxxxxxx with your actual recipient phone number (must be allowed by your Infobip account, e.g., your registered number on a free trial).
      • Replace +1yyyyyyyyyy with your valid, MMS-enabled Infobip sender number.
      • Replace the mediaUrl with a working, publicly accessible image URL (e.g., https://placehold.co/600x400.png).
      bash
      curl -X POST http://localhost:3000/send-mms \
      -H ""Content-Type: application/json"" \
      -d '{
        ""to"": ""+1xxxxxxxxxx"",
        ""from"": ""+1yyyyyyyyyy"",
        ""mediaUrl"": ""https://placehold.co/600x400.png"",
        ""caption"": ""Hello from Fastify!""
      }'

Frequently Asked Questions (FAQ)

What is MMS and how does it differ from SMS?

MMS (Multimedia Messaging Service) allows you to send messages that include multimedia content such as images, videos, and audio files, while SMS (Short Message Service) is limited to text only. MMS messages can be up to 1600 characters and support rich media attachments.

Which Node.js versions are compatible with Fastify and Infobip SDK?

This implementation works with Node.js LTS versions v18, v20, and v22. Node.js v14 reached end-of-life on April 30, 2023, and is no longer recommended. Always use an actively maintained LTS version for production deployments.

How do I get Infobip API credentials?

Sign up for an Infobip account, then navigate to your dashboard to find your API Key and Base URL. Free trial accounts have limitations, typically allowing messages only to your registered phone number.

What image formats are supported for MMS messages?

Infobip typically supports JPEG, PNG, and GIF formats for MMS media. Always verify current supported formats in the Infobip MMS API documentation as specifications may change.

Why am I getting "Failed to fetch media" errors?

This error occurs when the mediaUrl is not publicly accessible. Ensure the URL: (1) is publicly accessible without authentication, (2) points directly to an image file, (3) uses HTTPS, and (4) doesn't block Infobip's servers via firewall or User-Agent restrictions.

How do I validate phone numbers in E.164 format?

E.164 format requires a + prefix followed by 1-3 digit country code and the national number, with a maximum of 15 digits total. The regex pattern ^\\+[1-9]\\d{1,14}$ validates this format as specified in ITU-T Recommendation E.164 (June 2020).

Can I send MMS to any phone number with Infobip?

With free trial accounts, you can typically only send messages to the phone number used during registration. Production accounts can send to any valid phone number, subject to regional regulations and carrier support for MMS.

How do I implement retry logic for failed MMS sends?

Use libraries like async-retry to wrap the infobipService.sendMms call with exponential backoff. Only retry transient errors (network issues, rate limits) and avoid retrying non-recoverable errors (invalid credentials, blocked destinations).

Summary

You now have a complete, production-ready implementation for sending MMS messages using Fastify and the Infobip API. This guide covered project setup, Infobip SDK integration, request validation, error handling, security best practices, and deployment strategies. The implementation provides a solid foundation that you can extend with features like delivery tracking, message queuing, or integration with other communication channels.

Frequently Asked Questions

How to send MMS with Infobip and Fastify?

Create a Fastify server with a POST /send-mms route, use the Infobip SDK to format the MMS message, and send it via the Infobip API. The request body should contain recipient, sender, media URL, and an optional caption. Ensure your Infobip account and sender number are MMS-enabled.

What is the Infobip Node.js SDK?

The Infobip Node.js SDK (`@infobip-api/sdk`) simplifies interaction with the Infobip API, offering convenient methods for sending SMS, MMS, and other communication types. It handles authentication, request formatting, and response parsing, making integration smoother.

Why use Fastify for sending MMS messages?

Fastify is a high-performance Node.js web framework known for its speed and extensibility. Its schema validation feature helps ensure data integrity and security when handling incoming MMS requests. It's an excellent choice for building robust and efficient API endpoints.

When should I check Infobip API credentials?

Check your Infobip API key and base URL during server startup. This prevents the application from launching if critical credentials are missing. In server.js, verify these environment variables before initializing Fastify or requiring any services.

What is the role of dotenv in this setup?

Dotenv loads environment variables from a .env file into process.env during development. This keeps sensitive API credentials out of your codebase and allows easy configuration changes without modifying core application files. Never commit .env to version control.

How to handle Infobip API errors?

Implement a try-catch block in your service function and route handler. Catch errors originating from the Infobip API call, log detailed error messages including status codes, and return a user-friendly error response to the client. Check Infobip error codes for specific issues.

What data format does Infobip API expect for MMS?

The Infobip MMS API requires a specific JSON structure for sending MMS messages, typically including an array called "messages", even for single MMS sends. Each message object needs recipient ('to'), sender ('from'), content object with media URL ('mediaUrl'), and an optional caption ('caption').

How to format phone numbers for Infobip?

Phone numbers must be in E.164 format, e.g., +14155552671 (+ followed by country code and number). Validate phone numbers using the E.164 pattern (^\+[1-9]\d{1,14}$) during schema validation in your API endpoint. This prevents sending requests with invalid numbers.

Can I use a local image URL for Infobip MMS?

No, the media URL for MMS messages must be publicly accessible without authentication. Infobip servers must be able to fetch the media directly from the URL. Internal network or private URLs will cause errors.

How to structure a Node.js project with Fastify?

Create a directory for your project, initialize npm, install Fastify, the Infobip SDK, and dotenv. Create separate files for server logic (server.js), service functions (e.g., services/infobipService.js), and environment variables (.env, .env.example, .gitignore).

How to add rate limiting for sending Infobip MMS?

Use a plugin like fastify-rate-limit. Register the plugin in your server.js before your API routes. Configure limits like max requests per IP within a time window to prevent abuse and maintain a healthy system load.

How to deploy the Fastify Infobip MMS app?

Deploy your application to PaaS services like Heroku or Render via Git. For containerized deployments, build a Docker image using a Dockerfile. For serverless, adapt your app for platforms like AWS Lambda or Google Cloud Functions.

Why is input validation important with Fastify?

Input validation, through Fastify's JSON schema, prevents malformed data, potential injection attacks, and improves the robustness of your application. Using Fastify's schema, you can enforce required fields and specific data types, ensuring the validity of incoming requests.

What are best practices for security in a Node.js and Fastify app?

Use environment variables for API keys and secrets. Implement input validation with Fastify's schema validation. Use rate limiting (e.g., fastify-rate-limit) to prevent abuse. Set secure HTTP headers with fastify-helmet. Run the application over HTTPS in production.