code examples

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

How to Send MMS Messages with Sinch in Node.js Express: Complete Tutorial

Learn how to send MMS multimedia messages using Sinch API in Node.js Express. Step-by-step guide covering MMS setup, mt_media type, file size limits (1 MB), supported image/video formats, error handling, and production deployment.

Send MMS with Sinch in Node.js Express: Complete Implementation Guide

Learn how to build a production-ready Node.js application using Express to send MMS (Multimedia Messaging Service) messages via the Sinch API. This comprehensive tutorial covers project setup, MMS implementation, API creation, error handling, security best practices, and deployment.

By the end, you'll have a functional Express API endpoint that sends MMS messages with images, videos, or audio files using the Sinch XMS API and your Sinch account credentials.

What Will You Build with Sinch MMS?

Goal: Create a reliable backend service that sends MMS messages programmatically using Sinch.

Problem Solved: Automate sending rich media messages (images, audio, video) to users – essential for notifications, marketing campaigns, appointment reminders with QR codes, customer support with visual instructions, e-commerce order confirmations with product images, and event invitations with venue photos.

When to Use MMS vs. SMS:

Use CaseChoose MMSChoose SMS
Visual content required✓ Product images, QR codes, maps
Plain text sufficient✓ Maximum deliverability
Branding/marketing✓ Rich visual experience
Cost-sensitive✗ Higher per-message cost✓ Lower cost
Time-sensitive alerts✗ Slightly slower delivery✓ Faster delivery

Common Use Cases:

  • Appointment Reminders: Send calendar invites with location maps
  • Marketing Campaigns: Deliver product images with promotional offers
  • Customer Support: Provide visual troubleshooting guides
  • Order Confirmations: Include product images and shipping QR codes
  • Event Invitations: Share venue photos and ticket barcodes

Technologies:

  • Node.js v16+: JavaScript runtime for building scalable server-side applications
  • Express v4.18+: Minimal and flexible Node.js web application framework for building APIs
  • Axios v1.6+: Promise-based HTTP client for making requests to the Sinch API
  • dotenv v16+: Module to load environment variables from a .env file
  • Winston v3.11+: Versatile logging library
  • express-validator v7+: Middleware for request input validation
  • express-rate-limit v7+: Middleware for basic API rate limiting
  • helmet v7+: Middleware for setting security-related HTTP headers
  • axios-retry v4+: Plugin to automatically retry failed Axios requests
  • Sinch MMS JSON API: XMS API endpoint for sending MMS messages via the /batches endpoint with mt_media type

Compatibility Notes:

  • Requires Node.js 16 or higher for native ES modules support
  • Express 4.x and 5.x both supported
  • All dependencies support Node.js LTS versions

System Architecture:

mermaid
graph LR
    Client[Client Application / cURL] -- HTTP POST Request --> ExpressApp[Node.js/Express API]
    ExpressApp -- Validate Request --> ExpressApp
    ExpressApp -- Call Sinch Service --> SinchService[Sinch MMS Service Logic]
    SinchService -- Build JSON Payload --> SinchService
    SinchService -- POST Request (Axios w/ Retry) --> SinchAPI[Sinch XMS API (/batches)]
    SinchAPI -- Send MMS --> MobileNetwork[Mobile Network]
    MobileNetwork -- Deliver MMS --> UserDevice[User's Mobile Device]
    SinchAPI -- API Response (Success/Failure) --> SinchService
    SinchService -- Process Response --> ExpressApp
    ExpressApp -- HTTP Response (Success/Error) --> Client

Prerequisites:

  • Install Node.js v16+ and npm (or yarn)
  • Create a Sinch account and confirm your plan includes MMS API access. Note: In the US region, contact your Sinch account manager to enable MMS functionality for your Service Plan ID.
  • Provision a phone number (Short Code, Toll-Free, or 10DLC) within your Sinch account. Verify in the Sinch dashboard (usually under "Numbers" → "Your Numbers") that the chosen number has MMS sending capabilities enabled.
  • Obtain your Sinch Service Plan ID and API Token from your Sinch Customer Dashboard under SMS → APIs → REST API Configuration. (Dashboard layouts may vary – look for API credentials or REST API settings if this path differs.)
  • Host your media file (image, audio, video) at a publicly accessible URL. Sinch fetches content from this URL via HTTP GET. The hosting server must return a valid Content-Type header (e.g., image/jpeg, video/mp4) and a Content-Length header.

Media Hosting Solutions:

Choose a reliable hosting solution for your MMS media files:

  • CDN Providers: Cloudflare, AWS CloudFront, Fastly (recommended for production)
  • Cloud Storage: AWS S3, Google Cloud Storage, Azure Blob Storage with public read access
  • Media Services: Cloudinary, Imgix (automatic format optimization)
  • Requirements: HTTPS support, Content-Type headers, Content-Length headers, 99.9%+ uptime

Source: Sinch MMS Support Documentation

MMS File Size Limits and Best Practices:

  • Recommended limit: Keep media files under 1 MB – most MMS providers use this limit
  • Without transcoding: Keep video and image files under 500 KB, with a maximum of 740 KB
  • With Sinch transcoding: Keep files under 1 MB
  • Base64 encoding overhead: Sinch delivers MMS messages in Base64 encoding. To estimate final size, multiply the file size by 1.37 and add 814 bytes for headers
  • Content-Type requirement: Serve all media files with a valid Content-Type header (e.g., text/plain, image/gif, audio/mp3, video/mp4)

File Size Calculation Examples:

Original FileCalculationFinal SizeStatus
300 KB image(300 × 1.37) + 0.795 KB~411.8 KB✓ Safe
500 KB image(500 × 1.37) + 0.795 KB~685.8 KB✓ Safe
800 KB video(800 × 1.37) + 0.795 KB~1,096.8 KB✗ Exceeds 1 MB
730 KB video(730 × 1.37) + 0.795 KB~1,000.9 KB✗ Exceeds 1 MB

What Happens When Files Exceed Limits:

  • Carriers may reject the message entirely
  • Message may downgrade to SMS with a link instead of inline media
  • Delivery may fail silently without error notification
  • Some carriers compress media, reducing quality significantly

Source: Sinch MMS Best Practices

Supported Media Types:

Media TypeFormatsRecommended SizeMax DimensionNotes
ImagesGIF, JPG, PNG< 500 KB1024×768 pxUse JPEG for photos, PNG for graphics
AudioMP3, WAV< 500 KBN/AMP3 recommended for smaller size
Video3GP, MP4, MPEG, MPG, AVI, WMV< 500 KB640×480 pxMP4 H.264 codec recommended
TextTEXT/PLAIN< 100 KBN/APlain text files

Format-Specific Recommendations:

  • Images: Use JPEG with 80% quality for optimal size/quality balance
  • Videos: Use H.264 codec with 30 fps, 640×480 resolution
  • Audio: Use MP3 at 128 kbps bitrate
  • Avoid: BMP, TIFF, uncompressed formats

Source: Sinch MMS Sizes and Limitations

How Do You Set Up Your Node.js Project for Sinch MMS?

Initialize your Node.js project and install the necessary dependencies for sending MMS messages with Sinch.

  1. Create Project Directory: Open your terminal and create a new directory for the project.

    bash
    mkdir sinch-mms-sender
    cd sinch-mms-sender
  2. Initialize Node.js Project: Create a package.json file.

    bash
    npm init -y
  3. Enable ES Modules: Use import/export syntax by adding "type": "module" to your package.json. Alternatively, rename all .js files to .mjs. Open package.json and add:

    json
    {
      "name": "sinch-mms-sender",
      "version": "1.0.0",
      "description": "",
      "main": "src/server.js",
      "type": "module",
      "scripts": {
      },
    }
  4. Install Dependencies: Install Express, Axios, dotenv, validation, rate limiting, security headers, logging, and retry libraries with pinned versions for production stability.

    bash
    npm install express@^4.18.0 axios@^1.6.0 dotenv@^16.0.0 express-validator@^7.0.0 express-rate-limit@^7.0.0 helmet@^7.0.0 winston@^3.11.0 axios-retry@^4.0.0
  5. Install Development Dependencies: Install nodemon for automatic server restarts during development.

    bash
    npm install --save-dev nodemon
  6. Create Project Structure: Organize the code for better maintainability.

    bash
    mkdir src
    mkdir src/routes
    mkdir src/controllers
    mkdir src/services
    mkdir src/middleware
    mkdir src/config
    touch src/server.js
    touch src/routes/mmsRoutes.js
    touch src/controllers/mmsController.js
    touch src/services/sinchService.js
    touch src/middleware/authMiddleware.js
    touch src/config/logger.js
    touch .env
    touch .gitignore
  7. Configure .gitignore: Prevent sensitive files and unnecessary modules from version control.

    dotenv
    # .gitignore
    
    node_modules
    .env
    npm-debug.log
    logs/
    *.log
  8. Set Up Environment Variables (.env): Store your sensitive credentials and configuration here. Never commit this file.

    dotenv
    # .env
    
    # Server Configuration
    PORT=3000
    NODE_ENV=development # Use "production" in deployment
    
    # Sinch API Credentials & Config
    SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID_HERE
    SINCH_API_TOKEN=YOUR_API_TOKEN_HERE
    SINCH_MMS_NUMBER=+1YOURSINCHNUMBER # Use E.164 format (e.g., +12223334444)
    # Regional Endpoints: us.sms.api.sinch.com (US) or eu.sms.api.sinch.com (EU)
    SINCH_API_BASE_URL=https://us.sms.api.sinch.com # Choose based on your region
    
    # Basic API Key for Authentication
    # IMPORTANT: Generate a strong, random key for production!
    # Example generation: openssl rand -base64 32
    INTERNAL_API_KEY=REPLACE_WITH_A_SECURE_RANDOM_KEY
    
    # Logging Configuration
    LOG_LEVEL=debug # Use "info" or "warn" in production
    # SENTRY_DSN=YOUR_SENTRY_DSN_HERE # Optional: For Sentry integration
    • PORT: Port the Express server listens on
    • NODE_ENV: Environment type (development, production)
    • SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN: Your credentials from the Sinch Dashboard (see Prerequisites)
    • SINCH_MMS_NUMBER: The Sinch phone number you send MMS from (E.164 format)
    • SINCH_API_BASE_URL: Base URL for the Sinch API region. Regional endpoints: us.sms.api.sinch.com (US) or eu.sms.api.sinch.com (EU). Choose based on your location and data protection requirements.
    • INTERNAL_API_KEY: Crucial: Replace the placeholder with a cryptographically secure random string. This key protects your API endpoint.
    • LOG_LEVEL: Controls logging verbosity

Environment Variable Validation Strategy:

Add startup validation to catch configuration errors early. Create src/config/validateEnv.js:

javascript
// Validate required environment variables at startup
export function validateEnv() {
    const required = [
        'SINCH_SERVICE_PLAN_ID',
        'SINCH_API_TOKEN',
        'SINCH_MMS_NUMBER',
        'SINCH_API_BASE_URL',
        'INTERNAL_API_KEY'
    ];

    const missing = required.filter(key => !process.env[key]);

    if (missing.length > 0) {
        throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
    }

    // Validate E.164 format
    if (!process.env.SINCH_MMS_NUMBER.match(/^\+[1-9]\d{1,14}$/)) {
        throw new Error('SINCH_MMS_NUMBER must be in E.164 format (e.g., +12223334444)');
    }

    // Warn about default API key
    if (process.env.INTERNAL_API_KEY === 'REPLACE_WITH_A_SECURE_RANDOM_KEY') {
        console.warn('WARNING: Using default INTERNAL_API_KEY. Generate a secure key for production!');
    }
}

Team Onboarding Template (.env.example):

dotenv
# .env.example - Copy to .env and replace with your actual values

# Server Configuration
PORT=3000
NODE_ENV=development

# Sinch API Credentials (Get from https://dashboard.sinch.com/sms/api/rest)
SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID_HERE
SINCH_API_TOKEN=YOUR_API_TOKEN_HERE
SINCH_MMS_NUMBER=+1YOURSINCHNUMBER

# Regional Endpoints: us.sms.api.sinch.com (US) or eu.sms.api.sinch.com (EU)
SINCH_API_BASE_URL=https://us.sms.api.sinch.com

# Generate secure key: openssl rand -base64 32
INTERNAL_API_KEY=REPLACE_WITH_A_SECURE_RANDOM_KEY

# Logging
LOG_LEVEL=debug

Source: Sinch SMS API Reference

  1. Add npm Scripts: Add scripts to package.json for running, linting, and testing the server.

    json
    // package.json
    "scripts": {
      "start": "node src/server.js",
      "dev": "nodemon src/server.js",
      "lint": "eslint src/**/*.js",
      "format": "prettier --write src/**/*.js",
      "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
    },

How Do You Implement the Sinch MMS Service in Node.js?

Create a dedicated service to handle interactions with the Sinch MMS API, incorporating logging and retry logic.

src/config/logger.js

javascript
// src/config/logger.js
import winston from 'winston';
import dotenv from 'dotenv';

dotenv.config();

const { combine, timestamp, json, errors, colorize, printf } = winston.format;

// Custom format for console logging with colors
const consoleFormat = combine(
    colorize(),
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    printf(({ timestamp, level, message, stack }) => {
        return `${timestamp} ${level}: ${stack || message}`;
    })
);

// Format for file logging (JSON)
const fileFormat = combine(
    timestamp(),
    errors({ stack: true }), // Log stack traces
    json()
);

const logger = winston.createLogger({
    level: process.env.LOG_LEVEL || 'info', // Default to 'info'
    format: fileFormat, // Default format (used by file transports)
    transports: [
        // Console transport – only in non-production or if explicitly enabled
        ...(process.env.NODE_ENV !== 'production' ? [
            new winston.transports.Console({
                format: consoleFormat, // Use colorful format for console
                handleExceptions: true,
            })
        ] : []),

        // File transports (optional, adjust paths and levels)
        // new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
        // new winston.transports.File({ filename: 'logs/combined.log' }),
    ],
    exitOnError: false, // Do not exit on handled exceptions
});

// If we're in production and didn't add a console transport above,
// add a basic JSON console transport for platforms that aggregate stdout/stderr.
if (process.env.NODE_ENV === 'production' && logger.transports.length === 0) {
    logger.add(new winston.transports.Console({
        format: fileFormat, // Use JSON format for production console logs
        handleExceptions: true,
    }));
     logger.info('Production environment detected, configuring JSON console logging.');
} else if (process.env.NODE_ENV !== 'production') {
     logger.info('Development environment detected, configuring colorful console logging.');
}

export default logger;

Log Rotation and Retention:

For production environments, add log rotation to prevent disk space issues:

javascript
// Add to logger.js transports array
import 'winston-daily-rotate-file';

new winston.transports.DailyRotateFile({
    filename: 'logs/application-%DATE%.log',
    datePattern: 'YYYY-MM-DD',
    maxSize: '20m',
    maxFiles: '14d', // Keep logs for 14 days
    format: fileFormat
})

External Logging Integration:

For production monitoring, integrate with services like Datadog, New Relic, or Sentry:

javascript
// Add Sentry transport
import '@sentry/node';
import winston from 'winston';

if (process.env.SENTRY_DSN) {
    logger.add(new winston.transports.Sentry({
        dsn: process.env.SENTRY_DSN,
        level: 'error'
    }));
}

src/services/sinchService.js

javascript
// src/services/sinchService.js
import axios from 'axios';
import axiosRetry from 'axios-retry';
import dotenv from 'dotenv';
import logger from '../config/logger.js';

dotenv.config(); // Load environment variables

const SINCH_API_BASE_URL = process.env.SINCH_API_BASE_URL;
const SINCH_SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID;
const SINCH_API_TOKEN = process.env.SINCH_API_TOKEN;
const SINCH_MMS_NUMBER = process.env.SINCH_MMS_NUMBER;

if (!SINCH_API_BASE_URL || !SINCH_SERVICE_PLAN_ID || !SINCH_API_TOKEN || !SINCH_MMS_NUMBER) {
    logger.error("FATAL ERROR: Missing required Sinch environment variables. Check .env file.");
    process.exit(1); // Exit if critical configuration is missing
}

// Construct the full API endpoint URL using the Sinch XMS (Unified Messaging) API
// Format: {region}.sms.api.sinch.com/xms/v1/{service_plan_id}/batches
const SINCH_XMS_ENDPOINT = `${SINCH_API_BASE_URL}/xms/v1/${SINCH_SERVICE_PLAN_ID}/batches`;

// Create an Axios instance for Sinch requests
const sinchAxios = axios.create({
    timeout: 30000, // 30 second timeout for API requests
});

// Configure axios-retry for the instance
axiosRetry(sinchAxios, {
    retries: 3, // Number of retry attempts
    retryDelay: (retryCount, error) => {
        logger.warn(`Retry attempt ${retryCount} for request to ${error.config.url} due to ${error.message}`);
        return retryCount * 1000; // Exponential backoff (1s, 2s, 3s)
    },
    retryCondition: (error) => {
        // Retry on network errors or 5xx server errors from Sinch
        const shouldRetry = axiosRetry.isNetworkOrIdempotentRequestError(error) ||
                           (error.response && error.response.status >= 500);
        if (shouldRetry) {
            logger.info(`Condition met for retry: ${error.message}`);
        }
        return shouldRetry;
    },
    shouldResetTimeout: true, // Reset timeout on retries
});

/**
 * Sends an MMS message using the Sinch XMS JSON API (/batches endpoint).
 * Assumes validation is handled by the controller/route layer.
 *
 * @param {string} recipientPhoneNumber - The recipient's phone number in E.164 format.
 * @param {string} mediaUrl - The publicly accessible URL of the media file (must return Content-Type and Content-Length headers).
 * @param {string} mediaType - The type of media (e.g., "image", "video"). Supported: image, audio, video, pdf, contact, calendar.
 * @param {string} [messageText=""] - Optional text message. Max 5000 chars.
 * @param {string} [subject=""] - Optional MMS subject. Max 80 chars (40 recommended).
 * @param {string} [fallbackText=""] - Optional text for SMS fallback. Max 160 chars (~110 recommended).
 * @param {boolean} [strictValidation=false] - Enable Sinch MMS channel best practices validation.
 * @returns {Promise<object>} - The response data from the Sinch API (typically includes batch ID).
 * @throws {Error} - Throws an error if the API request ultimately fails after retries.
 */
export const sendMms = async (
    recipientPhoneNumber,
    mediaUrl,
    mediaType,
    messageText = '',
    subject = '',
    fallbackText = '',
    strictValidation = false
) => {
    // Construct the Sinch XMS API payload for sending MMS via the /batches endpoint
    // Type field must always be mt_media for MMS messages
    const batchPayload = {
        to: [recipientPhoneNumber], // Array of recipients
        from: SINCH_MMS_NUMBER,
        body: {
            type: "mt_media", // Indicates a mobile-terminated media message (MMS)
            url: mediaUrl, // Must be publicly accessible with Content-Type and Content-Length headers
            message: messageText || undefined, // Optional text associated with the media
            parameters: {
                // Only include parameters if they have a non-empty value
                ...(subject && { "mms_subject": { default: subject } }),
                ...(fallbackText && { "mms_fallback_text": { default: fallbackText } }),
                // Add other parameters as needed, check Sinch XMS API docs
            }
        },
        // Optional: Enable strict validation against Sinch MMS channel best practices
        ...(strictValidation && { strict_validation: true }),
        // Optional: Configure delivery report callback for status updates
        // delivery_report: "FULL", // Or "SUMMARY"
        // callback_url: "YOUR_WEBHOOK_URL_HERE"
    };

    // Remove empty parameters object if no optional params were provided
    if (Object.keys(batchPayload.body.parameters).length === 0) {
        delete batchPayload.body.parameters;
    }
     // Remove empty message field if not provided
    if (!batchPayload.body.message) {
        delete batchPayload.body.message;
    }

    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${SINCH_API_TOKEN}` // Use Bearer token authentication
    };

    logger.info(`Attempting to send MMS via Sinch XMS to ${recipientPhoneNumber} from ${SINCH_MMS_NUMBER}`);
    logger.debug(`Using Endpoint: ${SINCH_XMS_ENDPOINT}`);
    logger.debug("Request Payload:", { payload: batchPayload }); // Log payload structure

    try {
        // Use the Axios instance with retry configured
        const response = await sinchAxios.post(SINCH_XMS_ENDPOINT, batchPayload, { headers });

        logger.info('Sinch API Response Received:', { status: response.status, data: response.data });

        // Check for potential errors within a successful (2xx) HTTP response structure
        const batch = response.data?.batches?.[0];
        if (batch && (batch.error_code || batch.status === 'FAILED')) {
             logger.error('Sinch API reported an error or failure status within the batch response:', { batchData: batch });
             const errorMessage = batch.status_details || `Sinch API Error Code: ${batch.error_code || 'N/A'}, Status: ${batch.status}`;
             throw new Error(errorMessage);
        }
        // Also check for top-level errors if the structure varies
        if (response.data?.error) {
             logger.error('Sinch API top-level error:', { errorData: response.data.error });
             throw new Error(`Sinch API Error: ${response.data.error.message || JSON.stringify(response.data.error)}`);
        }

        // Assuming success if status is 2xx and no specific error structure is found.
        return response.data;

    } catch (error) {
        logger.error('Error sending MMS via Sinch service:', {
            message: error.message,
            status: error.response?.status,
            responseData: error.response?.data,
            requestConfig: error.config
        });

        // Construct a more informative error message
        let errorMessage = 'Failed to send MMS via Sinch.';
        if (error.response) {
            const errorDetails = error.response.data?.error?.message
                              || error.response.data?.batches?.[0]?.status_details
                              || JSON.stringify(error.response.data);
            errorMessage = `Sinch API request failed with status ${error.response.status}: ${errorDetails}`;
        } else if (error.request) {
            errorMessage = 'Sinch API request failed: No response received from server after retries.';
        } else {
            errorMessage = `Failed to send MMS: ${error.message}`;
        }
        throw new Error(errorMessage); // Re-throw the processed error
    }
};

/**
 * Sends bulk MMS messages to multiple recipients.
 *
 * @param {Array<string>} recipients - Array of phone numbers in E.164 format
 * @param {string} mediaUrl - The publicly accessible URL of the media file
 * @param {string} mediaType - The type of media
 * @param {string} [messageText=""] - Optional text message
 * @param {string} [subject=""] - Optional MMS subject
 * @param {string} [fallbackText=""] - Optional SMS fallback text
 * @returns {Promise<object>} - Batch response from Sinch API
 */
export const sendBulkMms = async (
    recipients,
    mediaUrl,
    mediaType,
    messageText = '',
    subject = '',
    fallbackText = ''
) => {
    const batchPayload = {
        to: recipients, // Send to multiple recipients in one batch
        from: SINCH_MMS_NUMBER,
        body: {
            type: "mt_media",
            url: mediaUrl,
            message: messageText || undefined,
            parameters: {
                ...(subject && { "mms_subject": { default: subject } }),
                ...(fallbackText && { "mms_fallback_text": { default: fallbackText } }),
            }
        }
    };

    if (Object.keys(batchPayload.body.parameters).length === 0) {
        delete batchPayload.body.parameters;
    }
    if (!batchPayload.body.message) {
        delete batchPayload.body.message;
    }

    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${SINCH_API_TOKEN}`
    };

    logger.info(`Attempting to send bulk MMS to ${recipients.length} recipients`);

    try {
        const response = await sinchAxios.post(SINCH_XMS_ENDPOINT, batchPayload, { headers });
        logger.info('Bulk MMS request successful:', { batchId: response.data.id, recipientCount: recipients.length });
        return response.data;
    } catch (error) {
        logger.error('Error sending bulk MMS:', { message: error.message });
        throw new Error(`Failed to send bulk MMS: ${error.message}`);
    }
};

Sinch API Error Codes:

Error CodeHTTP StatusDescriptionSolution
400400 Bad RequestInvalid JSON payload or missing required fieldsVerify payload structure matches API docs
401401 UnauthorizedInvalid or missing API tokenCheck SINCH_API_TOKEN in .env
402402 Payment RequiredInsufficient account balanceAdd credits to your Sinch account
403403 ForbiddenService Plan ID lacks MMS permissionsContact Sinch support to enable MMS
404404 Not FoundInvalid Service Plan ID or endpoint URLVerify SINCH_SERVICE_PLAN_ID and base URL
429429 Too Many RequestsRate limit exceededImplement exponential backoff, reduce request rate
500500 Internal Server ErrorSinch server errorRetry with exponential backoff
503503 Service UnavailableTemporary service outageRetry after delay, check Sinch status page

Rate Limit Handling:

Implement custom rate limit detection and backoff:

javascript
// Add to sinchService.js
const handleRateLimit = (error) => {
    if (error.response?.status === 429) {
        const retryAfter = error.response.headers['retry-after'] || 60;
        logger.warn(`Rate limit hit. Retry after ${retryAfter} seconds`);
        return new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
    }
    throw error;
};

Explanation:

  1. Logger: Imports the configured winston logger.
  2. Configuration: Loads Sinch credentials and base URL, logging a fatal error and exiting if any are missing.
  3. Endpoint: Defines the URL for the Sinch XMS /batches endpoint using the format {region}.sms.api.sinch.com/xms/v1/{service_plan_id}/batches.
  4. Axios Instance & Retry: Creates a dedicated axios instance with 30-second timeout and configures axios-retry to automatically retry requests on network errors or 5xx responses from Sinch, using exponential backoff. Logs retry attempts.
  5. sendMms Function:
    • Takes recipient, media URL, media type, text, subject, fallback text, and strict validation flag.
    • Payload Construction: Builds the JSON object for the /batches endpoint. Key fields include to, from, body.type (must be "mt_media"), body.url, body.message, and body.parameters (conditionally added).
    • Strict Validation: Optional strict_validation parameter enables validation against Sinch MMS channel best practices.
    • Media URL Requirements: The URL must be publicly accessible with valid Content-Type and Content-Length headers.
    • Removes empty parameters or message fields if not used.
    • Headers: Sets Content-Type and Authorization: Bearer.
    • Logging: Uses the logger (logger.info, logger.debug, logger.warn, logger.error). Logs payload at debug level.
    • API Call: Uses the sinchAxios instance (with retry) to make the POST request.
    • Response Handling: Logs the response. Checks the response body for specific error codes or failure statuses even if the HTTP status is 2xx. Throws an error if an issue is found.
    • Error Handling: The catch block logs detailed error information and constructs a specific error message before re-throwing it.

Performance Optimization:

  • Use connection pooling with axios.defaults.httpAgent
  • Implement request queuing for high-volume scenarios
  • Cache media URL validations to reduce redundant checks
  • Consider Redis for distributed rate limiting across multiple servers

Source: Sinch Batches API Reference

How Do You Build the Express API for Sending MMS?

Create the Express endpoint that uses the sinchService.

src/middleware/authMiddleware.js

javascript
// src/middleware/authMiddleware.js
import dotenv from 'dotenv';
import logger from '../config/logger.js';

dotenv.config();

const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY;

if (!INTERNAL_API_KEY || INTERNAL_API_KEY === 'REPLACE_WITH_A_SECURE_RANDOM_KEY') {
    logger.warn('SECURITY WARNING: INTERNAL_API_KEY is not set or is using the default placeholder. The API endpoint is effectively unprotected. Generate a strong key in .env.');
}

/**
 * Simple API Key Authentication Middleware.
 * Checks for "X-API-Key" header.
 */
export const apiKeyAuth = (req, res, next) => {
    // Allow requests if no key is configured (useful ONLY for local dev, insecure)
    if (!INTERNAL_API_KEY || INTERNAL_API_KEY === 'REPLACE_WITH_A_SECURE_RANDOM_KEY') {
        logger.warn('Proceeding without API key check because INTERNAL_API_KEY is not properly configured.');
        return next();
    }

    const providedKey = req.headers['x-api-key'];

    if (!providedKey) {
        logger.warn('Unauthorized access attempt: Missing X-API-Key header.');
        return res.status(401).json({ message: 'Unauthorized: Missing API Key' });
    }

    if (providedKey !== INTERNAL_API_KEY) {
         logger.warn(`Forbidden access attempt: Invalid API Key provided. Key: ${providedKey.substring(0, 5)}`);
         return res.status(403).json({ message: 'Forbidden: Invalid API Key' });
    }

    logger.debug('API Key authentication successful.');
    next(); // Key is valid, proceed
};

Advanced Authentication Options:

For production systems, consider implementing JWT or OAuth2:

javascript
// JWT-based authentication example
import jwt from 'jsonwebtoken';

export const jwtAuth = (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];

    if (!token) {
        return res.status(401).json({ message: 'Missing authentication token' });
    }

    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        next();
    } catch (error) {
        return res.status(403).json({ message: 'Invalid or expired token' });
    }
};

Rate Limiting Per API Key:

Implement abuse prevention with per-key rate limiting:

javascript
// Add to authMiddleware.js
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';

export const apiKeyRateLimit = rateLimit({
    store: new RedisStore({
        client: redisClient,
        prefix: 'rl:apikey:'
    }),
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // 100 requests per API key per window
    keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
    message: 'Too many requests from this API key. Try again later.'
});

src/controllers/mmsController.js

javascript
// src/controllers/mmsController.js
import { validationResult } from 'express-validator';
import { sendMms } from '../services/sinchService.js';
import logger from '../config/logger.js';

/**
 * Handles the request to send an MMS message.
 * Validates input, calls the Sinch service, and sends the response.
 */
export const handleSendMms = async (req, res) => {
    // 1. Validate Request Input using express-validator results
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        logger.warn('Validation failed for /send MMS request:', { errors: errors.array() });
        return res.status(400).json({ errors: errors.array() });
    }

    // Generate correlation ID for request tracking
    const correlationId = `mms-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    req.correlationId = correlationId;

    // 2. Extract validated data from request body
    const { to, mediaUrl, mediaType, messageText, subject, fallbackText } = req.body;

    try {
        // 3. Call the Sinch service function
        logger.info(`[${correlationId}] Controller received valid request to send MMS to: ${to}`);
        const sinchResponse = await sendMms(
            to,
            mediaUrl,
            mediaType,
            messageText,
            subject,
            fallbackText
        );

        // 4. Send successful response back to client (202 Accepted)
        logger.info(`[${correlationId}] MMS request successfully processed by Sinch service.`, { sinchResponse });
        res.status(202).json({
            message: 'MMS request accepted for processing by Sinch.',
            correlationId,
            details: sinchResponse // Include Sinch's response (e.g., batch ID)
        });

    } catch (error) {
        // 5. Handle errors thrown by the Sinch service
        logger.error(`[${correlationId}] Error in handleSendMms controller after calling service: ${error.message}`, { stack: error.stack });

        // Default to 500 for server/service errors.
        const statusCode = 500;

        res.status(statusCode).json({
             message: 'Failed to process MMS request.',
             correlationId,
             error: error.message // Provide the specific error message from the service
        });
    }
};

Async Queue Implementation:

For high-volume scenarios, implement message queuing with Bull or BullMQ:

javascript
// Add to mmsController.js
import Queue from 'bull';

const mmsQueue = new Queue('mms-sending', {
    redis: { host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }
});

export const handleSendMmsQueued = async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }

    const job = await mmsQueue.add({
        to: req.body.to,
        mediaUrl: req.body.mediaUrl,
        mediaType: req.body.mediaType,
        messageText: req.body.messageText,
        subject: req.body.subject,
        fallbackText: req.body.fallbackText
    }, {
        attempts: 3,
        backoff: { type: 'exponential', delay: 2000 }
    });

    res.status(202).json({
        message: 'MMS queued for processing',
        jobId: job.id
    });
};

// Process jobs
mmsQueue.process(async (job) => {
    const { to, mediaUrl, mediaType, messageText, subject, fallbackText } = job.data;
    return await sendMms(to, mediaUrl, mediaType, messageText, subject, fallbackText);
});

src/routes/mmsRoutes.js

javascript
// src/routes/mmsRoutes.js
import express from 'express';
import { body, validationResult } from 'express-validator';
import { handleSendMms } from '../controllers/mmsController.js';
import { apiKeyAuth } from '../middleware/authMiddleware.js';
import logger from '../config/logger.js';

const router = express.Router();

// Define supported media types (align with Sinch capabilities)
// Supported: image (GIF/JPG/PNG), audio (MP3/WAV), video (3GP/MP4/MPEG/MPG/AVI/WMV), text, pdf, contact, calendar
const SUPPORTED_MEDIA_TYPES = ['image', 'audio', 'video', 'pdf', 'contact', 'calendar', 'text'];

// Input validation rules using express-validator
const sendMmsValidationRules = [
    body('to')
        .trim()
        .notEmpty().withMessage('Recipient phone number (to) is required.')
        .isString()
        .matches(/^\+[1-9]\d{1,14}$/).withMessage('Recipient phone number must be in E.164 format (e.g., +12223334444).'),
    body('mediaUrl')
        .trim()
        .notEmpty().withMessage('Media URL (mediaUrl) is required.')
        .isURL({ protocols: ['http', 'https'], require_protocol: true })
        .withMessage('Media URL must be a valid HTTP or HTTPS URL. Ensure the URL returns Content-Type and Content-Length headers.'),
    body('mediaType')
        .trim()
        .notEmpty().withMessage('Media type (mediaType) is required.')
        .toLowerCase()
        .isIn(SUPPORTED_MEDIA_TYPES)
        .withMessage(`Invalid media type. Must be one of: ${SUPPORTED_MEDIA_TYPES.join(', ')}. Supported formats: GIF/JPG/PNG (image), MP3/WAV (audio), 3GP/MP4/MPEG/MPG/AVI/WMV (video).`),
    body('messageText')
        .optional({ checkFalsy: true }) // Allow empty string or null/undefined
        .isString()
        .isLength({ max: 5000 }).withMessage('Message text cannot exceed 5000 characters.'),
    body('subject')
        .optional({ checkFalsy: true })
        .isString()
        .isLength({ max: 80 }).withMessage('Subject cannot exceed 80 characters (40 recommended).'),
    body('fallbackText')
        .optional({ checkFalsy: true })
        .isString()
        .isLength({ max: 160 }).withMessage('Fallback text cannot exceed 160 characters (approx. SMS limit).'),
    body('strictValidation')
        .optional()
        .isBoolean().withMessage('strictValidation must be a boolean value.')
];

// Middleware to log validation results (optional, for debugging)
const logValidationErrors = (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        logger.debug('Express-validator validation errors:', { errors: errors.array() });
    }
    next();
};

// Define the POST route for sending MMS
// Chain: Authentication -> Validation -> Controller
router.post(
    '/send',
    apiKeyAuth,             // 1. Check API Key
    sendMmsValidationRules, // 2. Validate request body
    // logValidationErrors, // Uncomment for debugging validation errors
    handleSendMms           // 3. Process the request if auth/validation pass
);

// GET endpoint to check batch status by batch ID
router.get('/status/:batchId', apiKeyAuth, async (req, res) => {
    const { batchId } = req.params;

    try {
        const statusEndpoint = `${process.env.SINCH_API_BASE_URL}/xms/v1/${process.env.SINCH_SERVICE_PLAN_ID}/batches/${batchId}`;
        const response = await axios.get(statusEndpoint, {
            headers: { 'Authorization': `Bearer ${process.env.SINCH_API_TOKEN}` }
        });

        res.json({
            batchId,
            status: response.data.status,
            created: response.data.created_at,
            modified: response.data.modified_at,
            details: response.data
        });
    } catch (error) {
        logger.error(`Failed to fetch batch status for ${batchId}:`, error.message);
        res.status(500).json({ message: 'Failed to fetch batch status', error: error.message });
    }
});

export default router;

Media URL Validation Utility:

Add preemptive validation to catch hosting issues:

javascript
// Add to routes or create src/utils/mediaValidator.js
import axios from 'axios';

export const validateMediaUrl = async (url) => {
    try {
        const response = await axios.head(url, { timeout: 5000 });

        const contentType = response.headers['content-type'];
        const contentLength = response.headers['content-length'];

        if (!contentType) {
            throw new Error('Media URL does not return Content-Type header');
        }

        if (!contentLength) {
            throw new Error('Media URL does not return Content-Length header');
        }

        const sizeMB = parseInt(contentLength) / (1024 * 1024);
        if (sizeMB > 1) {
            throw new Error(`Media file too large: ${sizeMB.toFixed(2)} MB (max 1 MB)`);
        }

        return { valid: true, contentType, sizeMB };
    } catch (error) {
        return { valid: false, error: error.message };
    }
};

// Use in validation middleware
body('mediaUrl').custom(async (value) => {
    const validation = await validateMediaUrl(value);
    if (!validation.valid) {
        throw new Error(validation.error);
    }
    return true;
});

Test Media URLs with cURL:

bash
# Verify Content-Type and Content-Length headers
curl -I https://your-cdn.com/image.jpg

# Expected response:
# HTTP/1.1 200 OK
# Content-Type: image/jpeg
# Content-Length: 524288
# Access-Control-Allow-Origin: *

Explanation:

  1. Auth Middleware (authMiddleware.js): Checks for X-API-Key header against .env. Logs warnings if the key is missing or invalid.
  2. Controller (mmsController.js):
    • Uses validationResult to check for errors from express-validator. Returns 400 if invalid.
    • Generates correlation ID for distributed tracing.
    • Extracts validated data.
    • Calls sendMms service function within a try…catch block.
    • Logs actions and outcomes.
    • Returns 202 Accepted on success, including the service response.
    • Catches errors from the service, logs them, and returns 500 Internal Server Error.
  3. Router (mmsRoutes.js):
    • Defines strict validation rules for all input fields using express-validator.
    • Defines the POST /api/mms/send route.
    • Applies middleware sequentially: apiKeyAuth, validation rules, then the controller.
    • Includes GET /status/:batchId endpoint to check delivery status.

How Do You Integrate the Sinch MMS API?

Focus on the specific Sinch integration points.

  1. Credentials:

    • Set SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN, SINCH_MMS_NUMBER, SINCH_API_BASE_URL in your .env file (locally) and as secure environment variables in your deployment environment. Never commit .env or hardcode credentials.
    • Refer to the Prerequisites section for details on finding these values.
  2. API Endpoint:

    • Use the Sinch XMS API endpoint: ${SINCH_API_BASE_URL}/xms/v1/${SINCH_SERVICE_PLAN_ID}/batches.
    • Regional endpoints: us.sms.api.sinch.com (United States) or eu.sms.api.sinch.com (European Union). Choose based on your location and data protection requirements.
    • Refer to the official Sinch XMS API documentation for the latest specifications.

Source: Sinch SMS API Reference

  1. Authentication:

    • Sinch API uses Bearer Token authentication.
    • Set the Authorization: Bearer ${SINCH_API_TOKEN} header in sinchService.js.
  2. Payload:

    • Use the mt_media type within the /batches structure in your JSON payload. Set the type field to mt_media for MMS messages.
    • To include text with your media, populate the message field of the body object.
    • Use the optional strict_validation field to enable message validation against Sinch MMS channel best practices.
    • Verify parameter names against the official Sinch XMS API documentation when adding more options.

Source: Sinch MMS Support Documentation

  1. Media URL Requirements:
    • Make the mediaUrl publicly accessible via HTTP GET.
    • Ensure the hosting server returns a Content-Length header. Check using curl -I <your_media_url>.
    • Ensure the hosting server returns a correct Content-Type header (e.g., image/jpeg, video/mp4, audio/mp3).
    • File size recommendations:
      • Keep files under 1 MB (most provider limit)
      • Without transcoding: under 500 KB to 740 KB maximum
      • With Sinch transcoding: under 1 MB
    • Base64 encoding overhead: Multiply file size by 1.37 and add 814 bytes for headers to estimate final MMS size.

Source: Sinch MMS Best Practices

  1. Fallback Mechanism:
    • Use the parameters.mms_fallback_text field to control the SMS text if MMS fails.
    • Check Sinch documentation for parameters like disable_fallback_sms_link or disable_fallback_sms if needed.

Fallback Failure Scenarios:

Understand when and how MMS falls back to SMS:

ScenarioFallback BehaviorTesting Method
Non-MMS deviceSends SMS with fallback textTest with feature phone
Carrier doesn't support MMSSends SMS with fallback textTest with MVNO carriers
Media URL unreachableSends SMS with fallback textUse invalid URL in test
File size exceeds limitMay fail silently or send SMSSend 2 MB file
Invalid Content-TypeFails with errorRemove Content-Type header

Testing Fallback:

javascript
// Test fallback behavior
const testFallback = async () => {
    // Test 1: Valid MMS
    await sendMms('+12223334444', 'https://cdn.example.com/image.jpg', 'image',
        'Check out this image!', '', 'Visit example.com to view');

    // Test 2: Force fallback with invalid URL
    await sendMms('+12223334444', 'https://invalid-url-404.example.com/image.jpg', 'image',
        'Check out this image!', '', 'Visit example.com to view');
};
  1. MMS Enablement (US Region):
    • Important: In the US region, contact your Sinch account manager to enable MMS functionality. Verify your Service Plan ID has MMS capabilities activated before attempting to send MMS messages.

Source: Sinch Batches API Documentation

How Do You Handle Errors and Implement Retries for MMS?

  • Error Handling: Implement at multiple levels:
    • Validation (400): express-validator in routes
    • Authentication (401/403): apiKeyAuth middleware
    • Service Errors (Sinch API): Handle in sinchService.js catch block with retries via axios-retry
    • Controller Errors (500): Controller's catch block handles service errors
  • Logging:
    • Use winston for structured, leveled logging (src/config/logger.js)
    • Configure different formats for console (dev) and potential files/services (prod)
    • Include timestamps and stack traces
    • Replace console.* with logger.*
  • Retry Mechanisms:
    • Configure axios-retry in sinchService.js
    • Retry network issues or 5xx errors from Sinch with exponential backoff

Monitoring and Alerting:

Implement production monitoring with health checks and alerts:

javascript
// Add to server.js or create src/routes/health.js
router.get('/health', async (req, res) => {
    const health = {
        uptime: process.uptime(),
        timestamp: Date.now(),
        status: 'OK',
        checks: {}
    };

    // Check Sinch API connectivity
    try {
        await axios.head(`${process.env.SINCH_API_BASE_URL}`, { timeout: 5000 });
        health.checks.sinch = 'OK';
    } catch (error) {
        health.checks.sinch = 'DEGRADED';
        health.status = 'DEGRADED';
    }

    // Check environment variables
    const requiredEnv = ['SINCH_SERVICE_PLAN_ID', 'SINCH_API_TOKEN', 'SINCH_MMS_NUMBER'];
    health.checks.config = requiredEnv.every(key => process.env[key]) ? 'OK' : 'ERROR';

    const statusCode = health.status === 'OK' ? 200 : 503;
    res.status(statusCode).json(health);
});

Dead Letter Queue for Failed Messages:

Implement DLQ for messages that fail after all retries:

javascript
// Add to mmsController.js
import Queue from 'bull';

const deadLetterQueue = new Queue('mms-dlq', {
    redis: { host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }
});

// In error handler
catch (error) {
    logger.error(`[${correlationId}] MMS send failed after retries`);

    // Send to dead letter queue
    await deadLetterQueue.add({
        originalRequest: { to, mediaUrl, mediaType, messageText },
        error: error.message,
        timestamp: new Date(),
        correlationId
    });

    res.status(500).json({ message: 'Failed to process MMS request', correlationId });
}

// Process DLQ for manual review or retry
deadLetterQueue.process(async (job) => {
    logger.warn('Processing dead letter queue item', job.data);
    // Manual intervention, notifications, or retry logic
});

Frequently Asked Questions About Sending MMS with Sinch

What are the file size limits for Sinch MMS messages?

Keep media files under 1 MB – most MMS providers use this limit. Without transcoding, keep video and image files under 500 KB, with a maximum of 740 KB. With Sinch transcoding enabled, files under 1 MB work best. To estimate the final MMS size, multiply your file size by 1.37 and add 814 bytes for Base64 encoding headers that Sinch uses for MMS delivery.

Example: A 500 KB image becomes (500 KB × 1.37) + 814 bytes = approximately 685.8 KB after encoding.

Which media formats does Sinch support for MMS?

Sinch supports Images (GIF, JPG, PNG), Audio (MP3, WAV), Video (3GP, MP4, MPEG, MPG, AVI, WMV), and Text (TEXT/PLAIN). All media files must be served with a valid Content-Type header (e.g., image/jpeg, video/mp4, audio/mp3) and a Content-Length header from publicly accessible URLs.

How do you calculate Base64 encoding overhead for MMS?

Sinch delivers MMS messages in Base64 encoding. Calculate the final size by multiplying your file size by 1.37 and adding 814 bytes for headers. For example, a 500 KB image becomes approximately (500 KB × 1.37) + 814 bytes = 685.8 KB after encoding. This calculation helps ensure your media stays within the 1 MB MMS provider limit.

What is the mt_media type in Sinch API?

The mt_media type is a required field in the Sinch XMS API /batches endpoint for sending MMS messages. Set body.type to "mt_media" (mobile-terminated media message) to indicate you're sending MMS rather than plain SMS. This type enables the body.url field where you specify the publicly accessible media URL.

How do you enable MMS functionality in the US region?

In the US region, contact your Sinch account manager to enable MMS functionality for your Service Plan ID. Verify in the Sinch dashboard (under "Numbers" → "Your Numbers") that your chosen phone number has MMS sending capabilities enabled before attempting to send MMS messages. This requirement is specific to the US region.

What headers must media URLs return for Sinch MMS?

Media URLs must return two required headers: Content-Type (e.g., image/jpeg, video/mp4, audio/mp3) to specify the media format, and Content-Length to indicate file size. Test your media URL using curl -I <your_media_url> to verify both headers are present before sending MMS messages.

What are the regional endpoint differences for Sinch?

Sinch offers two regional endpoints: us.sms.api.sinch.com (United States) and eu.sms.api.sinch.com (European Union). Choose based on your location and data protection requirements. Configure the SINCH_API_BASE_URL environment variable with the appropriate regional endpoint. The full XMS API endpoint follows the format {region}.sms.api.sinch.com/xms/v1/{service_plan_id}/batches.

How do you implement retry logic for failed MMS sends?

Use the axios-retry plugin configured in sinchService.js to automatically retry failed requests. The implementation retries network errors or 5xx server responses from Sinch up to 3 times with exponential backoff (1s, 2s, 3s delays). The retry mechanism uses axiosRetry.isNetworkOrIdempotentRequestError(error) to determine which errors warrant retries, ensuring transient failures don't cause message delivery to fail.

How much does sending MMS cost compared to SMS?

MMS messages typically cost 3–10× more than SMS, depending on your Sinch plan and destination country. US domestic MMS costs approximately $0.02–$0.04 per message, while SMS costs $0.005–$0.01. International rates vary significantly by country. Check your Sinch dashboard under "Billing" → "Pricing" for exact rates, and consider implementing cost tracking in your application to monitor spending.

How do you track MMS delivery status and receipts?

Enable delivery reports by setting delivery_report: "FULL" in your batch payload and providing a callback_url where Sinch sends status updates. Implement a webhook endpoint to receive delivery notifications with statuses: Delivered, Failed, Expired, or Rejected. Store batch IDs returned from Sinch API responses and use the /batches/{batchId} GET endpoint to poll message status programmatically.

Can you test MMS sending in sandbox or development mode?

Sinch doesn't offer a dedicated sandbox mode for MMS. For development testing, provision a low-cost phone number and send test messages to your own devices. Keep file sizes minimal during testing to reduce costs. Implement feature flags to disable actual MMS sending in local development, returning mock success responses instead. Use environment variables to toggle between production and test modes.

Frequently Asked Questions

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

Use the Sinch MMS JSON API with Express.js and Node.js to create an API endpoint that accepts requests for sending multimedia messages. This involves setting up a Node.js project, installing required dependencies like Axios and Express, and implementing a service to interact with the Sinch API.

What is the Sinch MMS JSON API?

The Sinch MMS JSON API, specifically the XMS API endpoint, allows developers to send multimedia messages programmatically. It handles requests containing recipient details, message content, and media URLs for sending MMS messages via the Sinch platform.

Why does Sinch require a publicly accessible media URL?

Sinch requires a publicly accessible media URL so its servers can directly fetch the media content (images, audio, video) to include in the MMS message. Ensure your media hosting server includes the Content-Length header for Sinch to process correctly.

When should I use the Sinch MMS API instead of SMS?

Use the Sinch MMS API when you need to send rich media content like images, audio, or video along with your messages. SMS is limited to plain text, making MMS essential for marketing campaigns, notifications with visuals, or other communication needing more than text.

Can I send MMS messages internationally using Sinch?

The article doesn't explicitly address international MMS. However, it does mention E.164 number formatting for the "SINCH_MMS_NUMBER" environment variable, implying international number support might be available. Consult the official Sinch documentation for details on supported countries and regions for sending international MMS messages.

How to set up a Node.js project for sending MMS with Sinch?

Initialize a Node.js project using npm init, then install necessary packages such as express, axios, dotenv, and winston. Configure environment variables like SINCH_SERVICE_PLAN_ID and SINCH_API_TOKEN in a .env file to securely store your Sinch credentials.

What is the role of Axios in sending MMS via Sinch?

Axios, a promise-based HTTP client, is used to make POST requests to the Sinch XMS API. It simplifies sending the MMS payload to Sinch and handles the API responses.

How to handle errors when sending MMS messages with Sinch?

The example code provides error handling at multiple levels. Input validation using express-validator catches bad requests, authentication middleware handles unauthorized access, and a try-catch block in the Sinch service manages errors during API calls. Winston logs error details for debugging.

Why use Winston for logging with the Sinch MMS API?

Winston provides structured logging with different levels (debug, info, warn, error), facilitating efficient debugging and monitoring. It's configured to log timestamps, stack traces, and specific error data for better insight into API interactions and potential issues.

What are the prerequisites for sending MMS with Sinch?

You need a Sinch account with an active MMS API subscription, a provisioned phone number (Short Code, Toll-Free, or 10DLC) enabled for MMS, Sinch Service Plan ID and API Token, and a publicly accessible URL for your media file. Node.js and npm must also be installed.

How to secure my Sinch MMS API integration?

Secure your integration by storing Sinch credentials as environment variables, never committing your .env file, and using a strong, random API key for authentication (INTERNAL_API_KEY) to protect your Express API endpoint. Use HTTPS and consider rate limiting with express-rate-limit and security headers with Helmet.

How to structure a Node.js project for maintainability when using Sinch MMS?

The article recommends creating separate directories for routes, controllers, services, middleware, and configuration files. Place your main server logic in server.js. This structure promotes code organization and makes future updates or extensions easier.

What are the rate limits for the Sinch MMS API?

The article doesn't specify Sinch's rate limits. Refer to the official Sinch documentation for details on rate limits, as exceeding them can lead to throttled or rejected requests. Consider implementing your own rate limiting using express-rate-limit.

How does the retry mechanism work with the Sinch MMS API?

The code uses axios-retry to automatically retry failed Axios requests to the Sinch API. It retries on network errors or 5xx server errors with exponential backoff (1s, 2s, 3s delays between retries), increasing resilience to temporary issues.