code examples
code examples
How to Send MMS with Sinch API and Node.js: Complete 2025 Developer Guide
Learn how to send MMS messages with Sinch API, Node.js, and Express. Complete tutorial with media handling, fallback SMS, error handling, and production deployment best practices for multimedia messaging.
Sending MMS with Node.js, Express, and Sinch
You'll build a production-ready Node.js application using the Express framework to send Multimedia Messaging Service (MMS) messages via the Sinch MMS JSON API. This guide covers everything from initial project setup to deployment and monitoring.
Important: Sinch offers multiple APIs (e.g., legacy APIs, unified Messages API). This guide focuses on a specific MMS JSON API structure. Always verify endpoints, payload structures, authentication methods, and specific behaviors against the current, official Sinch documentation relevant to your account and the specific API product version you are using. API details can change.
By the end of this tutorial, you will have a functional Express API endpoint capable of accepting MMS details (recipient number, media URLs, text) and dispatching the message through Sinch, including handling fallbacks to SMS.
Project Overview and Goals
Goal: To create a robust backend service that enables sending MMS messages programmatically using Sinch's dedicated MMS API.
Problem Solved: Provides a reliable way to integrate rich media messaging into applications, automating communication workflows that require images, videos, or other media alongside text. This is essential for marketing campaigns, transactional notifications with visual content, and customer engagement workflows that need multimedia support.
Technologies Used:
- Node.js: A JavaScript runtime environment for building server-side applications.
- Express: A minimal and flexible Node.js web application framework used to build the API layer.
- Axios: A promise-based HTTP client for making requests to the Sinch API.
- dotenv: A module to load environment variables from a
.envfile for secure credential management. - Sinch MMS JSON API: The specific Sinch API endpoint and protocol for sending MMS messages (subject to verification against current documentation).
System Architecture:
+-------------+ +---------------------+ +-----------------+
| Your Client | ----> | Node.js/Express App | ----> | Sinch MMS API |
| (e.g., Web, | | (API Endpoint) | | |
| Mobile App) | +---------------------+ +-----------------+
+-------------+ |
| (Optional)
v
+-------------+
| Database |
| (Logging) |
+-------------+(Note: This is a simplified text-based representation.)
Prerequisites:
- Node.js and npm (or Yarn) installed. As of January 2025, Node.js 22 is the current LTS version (codenamed 'Jod') recommended for production use. Node.js 18.x reaches end-of-life on April 30, 2025 – plan to upgrade to Node.js 20 or 22 for continued support.
- Note: This guide uses Express v5.x (released October 2024) which requires Node.js 18+. Express 5 includes security improvements, async error handling, and drops support for Node.js versions before v18.
- Note: This guide uses Axios v1.12.x for HTTP requests. Current versions include improved fetch adapter support and enhanced timeout configuration.
- A Sinch account with access to the MMS API product you intend to use.
- Your Sinch Service Plan ID (may also be called Campaign ID or similar - verify term in your dashboard/docs).
- Your Sinch API Token associated with the Service Plan ID.
- A Sinch provisioned phone number (Short Code, Toll-Free, or 10DLC) capable of sending MMS.
- Publicly accessible URLs for the media files (images, videos, audio, etc.) you intend to send. These servers must return a
Content-Lengthheader. - Familiarity with the official Sinch MMS API documentation for verification.
How to Set Up a Node.js Project for Sinch MMS Integration
Initialize your Node.js project and install the necessary dependencies.
Step 1: Create Project Directory
Open your terminal and create a new directory for the project, then navigate into it.
mkdir sinch-mms-sender
cd sinch-mms-senderStep 2: Initialize Node.js Project
Initialize the project using npm. Accept the defaults or customize as needed.
npm init -yThis creates a package.json file.
Step 3: Install Dependencies
We need Express for the server, Axios to make HTTP requests, and dotenv to handle environment variables. We'll also add nodemon as a development dependency for easier development.
npm install express axios dotenv
npm install --save-dev nodemonStep 4: Configure Environment Variables
Create a file named .env in the root of your project. This file will store your sensitive credentials and configuration. Never commit this file to version control.
# .env
# --- Sinch Credentials ---
# Get these from your Sinch Customer Dashboard (e.g., under SMS -> APIs)
# Replace placeholders with your actual values.
# Your Service Plan ID (or Campaign ID - verify term)
SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID
# Your API Token for the Service Plan ID
SINCH_API_TOKEN=YOUR_API_TOKEN
# Your provisioned Sinch Number (use E.164 format, e.g., +1xxxxxxxxxx)
SINCH_NUMBER=YOUR_SINCH_PHONE_NUMBER
# --- Sinch API Configuration ---
# CRITICAL: Verify the correct API base URL and structure in the official Sinch documentation
# for your specific region (us, eu, au, etc.) and API product version.
# The structure might require the service plan ID in the path as shown, or it might be different.
# Example structure (VERIFY THIS):
SINCH_API_BASE_URL=https://us.mms.api.sinch.com/v1/services/YOUR_SERVICE_PLAN_ID
# --- Server Configuration ---
# Port your Express server will listen on
PORT=3000
# Optional: Logging level (e.g., 'info', 'debug', 'warn', 'error')
# LOG_LEVEL=infoSINCH_SERVICE_PLAN_ID: Found on your Sinch Customer Dashboard. The exact name and location might vary. ReplaceYOUR_SERVICE_PLAN_IDwith your actual ID.SINCH_API_TOKEN: Found on the Sinch Customer Dashboard, often near the Service Plan ID configuration. ReplaceYOUR_API_TOKENwith your actual token.SINCH_NUMBER: A virtual number assigned to your Sinch account, capable of sending MMS. Use the E.164 international format (e.g.,+12223334444). ReplaceYOUR_SINCH_PHONE_NUMBERwith your actual Sinch number.SINCH_API_BASE_URL: The base URL for the Sinch MMS API. Crucially, verify the correct URL and structure in the official Sinch documentation. It can vary based on region, API version, and whether the Service Plan ID is part of the path. The example provided is common but requires verification. ReplaceYOUR_SERVICE_PLAN_IDwithin the URL if this structure is correct for your API.PORT: The port your Express server will listen on.
Step 5: Set Up Project Structure
Create the following basic structure:
sinch-mms-sender/
├── node_modules/
├── public/ # (Optional: for static files if needed)
├── src/
│ ├── controllers/ # Request/response handling
│ │ └── mmsController.js
│ ├── services/ # Business logic (Sinch interaction)
│ │ └── sinchService.js
│ ├── routes/ # API route definitions
│ │ └── mmsRoutes.js
│ ├── app.js # Express app configuration
│ └── server.js # Server entry point
├── .env # Environment variables (DO NOT COMMIT)
├── .gitignore # Git ignore file
├── package.json
└── package-lock.jsonCreate the src directory and the subdirectories/files within it.
Step 6: Create .gitignore
Create a .gitignore file in the project root to prevent committing sensitive files and unnecessary directories.
# .gitignore
node_modules/
.env
npm-debug.log
*.logStep 7: Add Run Scripts to package.json
Modify the scripts section in your package.json:
{
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}(Note: The test script is a placeholder. It will be updated in Section 13 when testing with Jest is introduced.)
npm start: Runs the application using Node.npm run dev: Runs the application usingnodemon, which automatically restarts the server on file changes.
How to Implement the Sinch MMS Service Layer
This service will encapsulate the logic for interacting with the Sinch MMS API.
File: src/services/sinchService.js
// src/services/sinchService.js
const axios = require('axios');
// Load environment variables
require('dotenv').config();
const SINCH_SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID;
const SINCH_API_TOKEN = process.env.SINCH_API_TOKEN;
const SINCH_API_BASE_URL = process.env.SINCH_API_BASE_URL; // Assumes base URL includes service ID path per .env example
if (!SINCH_SERVICE_PLAN_ID || !SINCH_API_TOKEN || !SINCH_API_BASE_URL) {
console.error("Error: Missing Sinch credentials or API base URL in .env file. Please check SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN, and SINCH_API_BASE_URL.");
process.exit(1); // Stop the application if critical config is missing
}
// Construct the specific endpoint for the sendmms action
// **VERIFY THIS ENDPOINT STRUCTURE** against the official Sinch MMS JSON API documentation for your account/region.
// This assumes the base URL already contains '/v1/services/{service_plan_id}'
const SEND_MMS_ENDPOINT = `${SINCH_API_BASE_URL}/actions/sendmms`;
/**
* Sends an MMS message using the Sinch MMS JSON API.
*
* @param {string} to The recipient's phone number in E.164 international format (e.g., +1...).
* @param {string} from The Sinch phone number sending the message (E.164 format).
* @param {string} subject The subject line for the MMS (max 80 chars, 40 recommended).
* @param {Array<object>} slides An array of slide objects. Structure MUST match Sinch API docs.
* Each slide can contain text and/or one media type (image, audio, video, etc.).
* @param {string} [fallbackText] Optional text for the fallback SMS if MMS fails or is disabled. Required if fallback isn't explicitly disabled.
* @param {object} [options] Optional parameters like clientReference, disableFallbackSms, etc. (Refer to Sinch Docs).
* @returns {Promise<object>} The success response data from the Sinch API (contains tracking-id).
* @throws {Error} If the API request fails or returns an error status.
*/
const sendMms = async (to, from, subject, slides, fallbackText, options = {}) => {
console.log(`Attempting to send MMS to ${to} from ${from}`);
// Basic validation
if (!to || !from || !subject || !Array.isArray(slides) || slides.length === 0) {
throw new Error('Missing required parameters: to, from, subject, or non-empty slides array.');
}
if (!to.startsWith('+') || !from.startsWith('+')) {
console.warn(`Warning: Phone numbers should be in E.164 format (start with +). To: ${to}, From: ${from}`);
// Consider throwing an error here in production for strict validation
}
if (slides.length > 8) {
console.warn('Warning: Maximum of 8 slides recommended per Sinch guidelines.');
}
// Ensure fallbackText is provided if fallback SMS is enabled (default behavior)
const disableFallbackSms = options.disableFallbackSms === true;
if (!disableFallbackSms && !fallbackText) {
// Fallback is enabled by default, so text is required unless explicitly disabled.
throw new Error('fallbackText is required when disableFallbackSms is false (or not provided).');
}
// **VERIFY PAYLOAD STRUCTURE** against official Sinch MMS JSON API documentation.
const payload = {
action: 'sendmms',
'service-id': SINCH_SERVICE_PLAN_ID, // May or may not be required depending on API structure/endpoint used
to: to,
from: from,
'message-subject': subject,
slide: slides, // Array of slide objects - structure is critical
// --- Optional Parameters (Verify names and values in Sinch Docs) ---
'fallback-sms-text': disableFallbackSms ? undefined : fallbackText, // Only include if fallback is enabled
'disable-fallback-sms': options.disableFallbackSms || false, // Default: false (fallback enabled)
'disable-fallback-sms-link': options.disableFallbackSmsLink || false, // Default: false
// 'fallback-sms-link-expiration': options.fallbackLinkExpiration, // Example: ISO8601 Date String
// 'mms-expiry-timestamp': options.mmsExpiryTimestamp, // Example: ISO8601 Date String
'client-reference': options.clientReference, // Your internal reference ID (max 64 chars recommended)
'cache-content': options.cacheContent === undefined ? true : options.cacheContent, // Default: true (Sinch may cache media)
};
// Remove undefined optional keys to keep payload clean
Object.keys(payload).forEach(key => payload[key] === undefined && delete payload[key]);
console.log('Constructed Sinch Payload:', JSON.stringify(payload, null, 2)); // Log payload for debugging
try {
// **VERIFY AUTHENTICATION METHOD** (Bearer Token is common, but check Sinch Docs)
const response = await axios.post(SEND_MMS_ENDPOINT, payload, {
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${SINCH_API_TOKEN}` // Standard Bearer token auth
},
timeout: 15000 // Set a reasonable timeout (e.g., 15 seconds) for the API call
});
console.log('Sinch API Response Status:', response.status);
console.log('Sinch API Response Data:', JSON.stringify(response.data, null, 2));
// Sinch often returns 200 OK even for queued messages, but check response body for success indicator if available
// The exact success condition might vary based on the API version. This checks for a common pattern.
if (response.data && response.data.status === 'success' && response.data['tracking-id']) {
console.log(`MMS successfully queued with Sinch. Tracking ID: ${response.data['tracking-id']}`);
return response.data; // Contains tracking-id etc.
} else {
// Handle cases where Sinch might return 200 but indicate failure/issue in the body, or success structure differs
console.error('Sinch API response body indicates potential issue:', response.data);
throw new Error(`Sinch API did not return expected success status or tracking-id in response body: ${JSON.stringify(response.data)}`);
}
} catch (error) {
console.error('Error sending MMS via Sinch:', error.message);
let errorMessage = 'Failed to send MMS via Sinch API.';
if (error.response) {
// The request was made and the server responded with a status code outside the 2xx range
console.error('Sinch API Error Status:', error.response.status);
console.error('Sinch API Error Headers:', error.response.headers);
console.error('Sinch API Error Data:', JSON.stringify(error.response.data, null, 2));
// Include details from Sinch error response if available
const errorDetails = error.response.data ? `: ${JSON.stringify(error.response.data)}` : '';
errorMessage = `Sinch API request failed with status ${error.response.status}${errorDetails}`;
} else if (error.request) {
// The request was made but no response was received
console.error('Sinch API No Response Received. Request details:', error.request);
errorMessage = 'No response received from Sinch API. Check network connectivity and endpoint URL.';
} else {
// Something happened in setting up the request that triggered an Error
console.error('Axios Request Setup Error:', error.message);
errorMessage = `Error setting up request to Sinch API: ${error.message}`;
}
// Re-throw a more informative error to be handled by the controller
throw new Error(errorMessage);
}
};
module.exports = {
sendMms,
};Explanation:
- Dependencies & Config: Imports
axiosanddotenv. Loads environment variables and checks for essential Sinch credentials and the base URL, exiting if missing. - Endpoint Construction: Defines the full URL for the
sendmmsaction based on theSINCH_API_BASE_URL. Includes a prominent warning to verify this endpoint against official documentation. sendMmsFunction:- Takes required parameters (
to,from,subject,slides) and optionalfallbackTextandoptions. - Performs basic input validation (required fields, E.164 format hint, slide count).
- Validates that
fallbackTextis present if fallback SMS is not explicitly disabled. - Constructs the JSON
payloadaccording to a common Sinch MMS JSON API specification. Includes a warning to verify the payload structure against official documentation. Handles common optional fields and cleans undefined values. - Uses
axios.postto send the request. - Sets necessary headers:
Content-TypeandAuthorization(Bearer token - verify this method). - Includes robust error handling for different failure scenarios (API errors with status codes, network errors, request setup errors), logging detailed information.
- Logs the constructed payload and the received response for debugging.
- Checks the response body for a success indicator (e.g.,
status: 'success'andtracking-id) as 200 OK might not guarantee acceptance. This check might need adjustment based on the specific API response structure. - Returns the success response data from Sinch upon successful queueing.
- Throws detailed errors for upstream handling.
- Takes required parameters (
How to Build Express API Routes and Controllers for MMS
Now, let's create the Express route and controller to expose our MMS sending functionality.
File: src/routes/mmsRoutes.js
// src/routes/mmsRoutes.js
const express = require('express');
const mmsController = require('../controllers/mmsController');
// Optional: Import validation middleware if using express-validator (see Section 7)
// const { validateSendMms } = require('../middleware/validators'); // Example path
const router = express.Router();
// Define the route for sending MMS
// POST /api/mms/send
// Optional: Add validation middleware before the controller
// router.post('/send', validateSendMms, mmsController.handleSendMms);
router.post('/send', mmsController.handleSendMms);
module.exports = router;File: src/controllers/mmsController.js
// src/controllers/mmsController.js
const sinchService = require('../services/sinchService');
// Load environment variables to get the sender number
require('dotenv').config();
const SINCH_NUMBER = process.env.SINCH_NUMBER;
if (!SINCH_NUMBER) {
console.error("FATAL: SINCH_NUMBER is not defined in .env file. Cannot determine sender number.");
process.exit(1);
}
/**
* Handles the incoming API request to send an MMS.
* Expects request body adhering to the defined contract, e.g.:
* {
* "to": "+1xxxxxxxxxx", // E.164 format
* "subject": "Your Media Update", // Max 80 chars
* "slides": [ // 1-8 slides, structure defined by Sinch API docs
* { "image": { "url": "https://your-cdn.com/image.jpg" }, "message-text": "Check out this picture!" },
* { "video": { "url": "https://your-cdn.com/video.mp4" }, "message-text": "And this video too." }
* ],
* "fallbackText": "View your media update here: [link inserted by Sinch if enabled]", // Required if fallback not disabled
* "clientReference": "internal-order-123", // Optional, max 64 chars
* "disableFallbackSms": false, // Optional, default false
* "disableFallbackSmsLink": false // Optional, default false
* }
*/
const handleSendMms = async (req, res) => {
// Extract data from request body
const { to, subject, slides, fallbackText, clientReference,
disableFallbackSms, disableFallbackSmsLink /* Add other options if needed */ } = req.body;
const from = SINCH_NUMBER; // Use the number configured in .env as the sender
// Basic Input Validation (Strongly recommend using a library like express-validator for production - see Section 7)
if (!to || !subject || !Array.isArray(slides) || slides.length === 0) {
console.warn(`Validation failed for incoming request: Missing required fields.`);
return res.status(400).json({
status: 'error',
message: 'Bad Request: Missing required fields: `to`, `subject`, `slides` (must be a non-empty array).',
});
}
// Add more specific validation here or use middleware (e.g., check 'to' format, subject length, slide content structure)
// Prepare options object for the service layer
const serviceOptions = {
clientReference,
disableFallbackSms,
disableFallbackSmsLink,
// Pass through other relevant options extracted from req.body
};
try {
console.log(`Processing request to send MMS via Sinch: To=${to}, From=${from}, Subject=${subject}`);
const result = await sinchService.sendMms(
to,
from,
subject,
slides,
fallbackText, // Pass fallbackText explicitly
serviceOptions // Pass the options bundle
);
// If sendMms resolves, it means the request was accepted by Sinch (or seemed to be)
console.log(`Successfully queued MMS to ${to} via Sinch. Tracking ID: ${result['tracking-id']}`);
// Respond with the success status and the data received from Sinch (including tracking ID)
res.status(200).json({
status: 'success',
message: 'MMS request accepted by Sinch and queued for delivery.',
data: result, // Send back the full Sinch success response
});
} catch (error) {
// Log the detailed error caught from the service layer
console.error(`Failed to process MMS request for ${to}:`, error.message); // error.message now contains detailed info
// Determine appropriate status code based on error type if possible
// If the error came from Sinch API (indicated by the service layer error message), use 400 or a more specific code if known.
// Otherwise, use 500 for internal server errors.
const statusCode = error.message.includes("Sinch API request failed") || error.message.includes("required parameters") || error.message.includes("fallbackText is required") ? 400 : 500;
res.status(statusCode).json({
status: 'error',
message: 'Failed to send MMS.',
// Provide the specific error message back (be cautious about leaking too much detail in production)
error: error.message,
});
}
};
module.exports = {
handleSendMms,
};Explanation:
- Routes (
mmsRoutes.js): Defines a single POST route/api/mms/sendthat maps to thehandleSendMmscontroller function. Includes a commented-out example showing where validation middleware (Section 7) would go. - Controller (
mmsController.js):- Imports the
sinchService. - Loads the
SINCH_NUMBERfrom environment variables to use as thefromnumber, exiting if not found. - Extracts expected fields (
to,subject,slides,fallbackText, and optional fields likeclientReference,disableFallbackSms) from the request body (req.body). - Performs basic validation on required fields. Strongly recommends using
express-validator(Section 7) for robust validation in production. - Bundles optional parameters into a
serviceOptionsobject. - Calls
sinchService.sendMmswith the extracted and validated data. - Handles success: Logs the success and responds with a 200 status and the Sinch API success response (which should include the
tracking-id). - Handles errors: Catches errors thrown by the service layer, logs the detailed error message, determines an appropriate HTTP status code (400 for client-side/API input errors, 500 for internal server issues), and responds with a JSON error message including the error details from the service.
- Imports the
How to Configure Express Application with Sinch MMS Integration
Let's tie everything together in our main Express application file.
File: src/app.js
// src/app.js
const express = require('express');
const mmsRoutes = require('./routes/mmsRoutes');
// Optional: Import security middleware like rate limiter (see Section 7)
// const { apiLimiter } = require('./middleware/rateLimiter');
// Optional: Import request logger like pino-http (see Section 5)
// const { httpLogger } = require('./utils/logger');
// Load environment variables early, especially for PORT and NODE_ENV
require('dotenv').config();
const app = express();
// --- Core Middlewares ---
// Request Logging (Replace with a structured logger like pino in production - See Section 5)
// app.use(httpLogger); // Example using pino-http
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] Incoming Request: ${req.method} ${req.originalUrl}`);
next(); // Pass control to the next middleware/route handler
});
// JSON Body Parser: Crucial for reading req.body from JSON payloads
app.use(express.json({ limit: '1mb' })); // Set a reasonable payload size limit
// URL-encoded Body Parser (optional, if you expect form data)
app.use(express.urlencoded({ extended: true }));
// --- Security Middlewares (Examples - See Section 7) ---
// app.use('/api/', apiLimiter); // Apply rate limiting to API routes
// Add other security middleware here (helmet, cors if needed)
// --- API Routes ---
app.use('/api/mms', mmsRoutes); // Mount the MMS routes under /api/mms
// --- Health Check Endpoint ---
app.get('/health', (req, res) => {
// Basic health check, can be expanded later
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
// --- Error Handling Middlewares (Must be defined LAST) ---
// Catch-all for 404 Not Found errors (requests falling through routes)
app.use((req, res, next) => {
res.status(404).json({ status: 'error', message: `Resource not found: ${req.originalUrl}` });
});
// Global Error Handler: Catches errors passed via next(error) or thrown in async route handlers
// Needs 4 arguments to be recognized by Express as an error handler
app.use((err, req, res, next) => {
// Log the error internally (use a proper logger in production)
console.error("Unhandled Error:", err.stack || err);
// Determine status code: Use error's status if set, otherwise default to 500
const statusCode = typeof err.status === 'number' ? err.status : 500;
// Send generic error response to the client
res.status(statusCode).json({
status: 'error',
message: err.message || 'Internal Server Error',
// Optionally include stack trace in development mode ONLY
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
module.exports = app;File: src/server.js
// src/server.js
const app = require('./app');
// Ensure environment variables are loaded (dotenv.config() might be called in app.js already)
require('dotenv').config();
const PORT = process.env.PORT || 3000; // Use PORT from .env or default to 3000
const NODE_ENV = process.env.NODE_ENV || 'development';
const server = app.listen(PORT, () => { // Store server instance for graceful shutdown
console.log(`Server running in ${NODE_ENV} mode on port ${PORT}`);
console.log("--- Configuration Check ---");
console.log(`Sinch Service Plan ID configured: ${process.env.SINCH_SERVICE_PLAN_ID ? 'Yes' : 'NO - Check .env!'}`);
console.log(`Sinch API Token configured: ${process.env.SINCH_API_TOKEN ? 'Yes' : 'NO - Check .env!'}`);
console.log(`Sinch Sender Number configured: ${process.env.SINCH_NUMBER || 'NO - Check .env!'}`);
console.log(`Sinch API Base URL configured: ${process.env.SINCH_API_BASE_URL || 'NOT SET - Check .env!'}`);
console.log("---------------------------");
if (!process.env.SINCH_SERVICE_PLAN_ID || !process.env.SINCH_API_TOKEN || !process.env.SINCH_NUMBER || !process.env.SINCH_API_BASE_URL) {
console.error("FATAL: One or more required Sinch environment variables are missing. Application might not function correctly.");
}
});
// Optional: Graceful shutdown handling
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
// Add cleanup logic here (e.g., close database connections)
server.close(() => {
console.log('HTTP server closed');
process.exit(0);
});
});Explanation:
app.js:- Initializes the Express application (
app). - Loads
dotenvearly. - Includes essential middleware: a basic request logger (recommend replacing with
pino-httpor similar for production),express.json()for parsing JSON bodies, andexpress.urlencoded()(optional). - Includes placeholders for security middleware (rate limiting, etc. - see Section 7).
- Mounts the
mmsRoutesunder the/api/mmspath prefix. - Adds a basic
/healthcheck endpoint for monitoring. - Includes a 404 handler for requests that don't match any route.
- Implements a global error handler middleware (must have 4 arguments:
err, req, res, next) to catch unhandled errors from routes ornext(error)calls. It logs the error and sends a standardized JSON error response to the client, avoiding leaking stack traces in production.
- Initializes the Express application (
server.js:- Imports the configured
appfromapp.js. - Loads
dotenvagain (safe if already loaded) to ensure environment variables are available here too. - Gets the
PORTandNODE_ENVfrom environment variables (with defaults). - Starts the Express server using
app.listenand stores the server instance. - Logs essential configuration status on startup for quick verification, including warnings if critical Sinch variables are missing.
- Includes optional basic graceful shutdown handling for
SIGTERM, using the storedserverinstance.
- Imports the configured
How to Implement Error Handling, Logging, and Retry Logic for MMS
We've built foundational error handling. Let's refine logging and consider retries.
Error Handling Strategy (Recap):
sinchService.js: Catchesaxioserrors during the API call. Logs detailed Sinch API error responses. Throws a new, informativeErrorobject containing relevant details.mmsController.js: Uses atry...catchblock to catch errors fromsinchService. Logs a summary. Responds to the client with an appropriate HTTP status (400 for known client/API errors, 500 for unexpected internal errors) and a JSON error message derived from the caught error.app.js(Global Handler): Catches any errors missed by route handlers or thrown unexpectedly. Logs the full stack trace (server-side) and sends a generic 500 response to the client.
Logging Enhancements (Production):
Standard console.log/error is okay for development, but insufficient for production. Use a dedicated, structured logging library like pino.
- Benefits: Structured JSON output (machine-readable), log levels (info, debug, warn, error, fatal), faster performance, easy integration with log shippers (Fluentd, Logstash) and management systems (ELK, Datadog, Splunk).
- Implementation:
- Install:
npm install pino pino-http - Configure: Create a logger instance (e.g., in
src/utils/logger.js). - Integrate
pino-httpmiddleware inapp.jsfor automatic request/response logging. - Replace
console.log/errorcalls throughout your services and controllers withlogger.info(),logger.warn(),logger.error(), etc. Pass error objects directly tologger.error(error, "Optional message")for stack trace logging.
- Install:
Retry Mechanisms:
Network issues or temporary Sinch API unavailability (e.g., 5xx errors) can occur. Retrying failed requests can improve resilience.
- When to Retry: Typically retry on transient errors: network timeouts, DNS issues, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout. Do not retry on permanent errors like 4xx (Bad Request, Unauthorized, Not Found) as the request is unlikely to succeed without changes.
- Strategy: Implement exponential backoff (wait longer between each retry) to avoid overwhelming the API.
- Library:
axios-retrysimplifies this.
Example using axios-retry:
- Install:
npm install axios-retry - Modify
src/services/sinchService.js:
// src/services/sinchService.js
const axios = require('axios');
// Use default import for axios-retry
const axiosRetry = require('axios-retry').default;
// ... other imports and config (SINCH_SERVICE_PLAN_ID, TOKEN, BASE_URL, SEND_MMS_ENDPOINT) ...
// Create an Axios instance specifically for Sinch requests
const sinchApiClient = axios.create({
baseURL: SINCH_API_BASE_URL, // Set base URL here if consistent
timeout: 15000, // Default timeout for requests
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${SINCH_API_TOKEN}` // Set common headers
}
});
// Configure axios-retry on the instance
axiosRetry(sinchApiClient, {
retries: 3, // Number of retries
retryDelay: (retryCount) => {
console.log(`Retry attempt: ${retryCount}`);
return retryCount * 1000; // Exponential backoff (1s, 2s, 3s)
},
retryCondition: (error) => {
// Retry on network errors or specific server errors (5xx)
return (
axiosRetry.isNetworkOrIdempotentRequestError(error) ||
(error.response && error.response.status >= 500 && error.response.status <= 599)
);
},
onRetry: (retryCount, error, requestConfig) => {
console.warn(`Retrying request to ${requestConfig.url} due to error: ${error.message}. Attempt ${retryCount}`);
}
});
// ... inside sendMms function ...
try {
// Use the configured axios instance instead of axios.post directly
// Note: If SEND_MMS_ENDPOINT was constructed using SINCH_API_BASE_URL,
// you might need to adjust the URL passed here (e.g., just the path part).
// Assuming SEND_MMS_ENDPOINT is the full URL:
// const response = await sinchApiClient.post(SEND_MMS_ENDPOINT, payload);
// OR if SINCH_API_BASE_URL is set in the instance and SEND_MMS_ENDPOINT
// contains the full URL, extract the path:
const endpointPath = new URL(SEND_MMS_ENDPOINT).pathname; // Requires full URL in SEND_MMS_ENDPOINT
const response = await sinchApiClient.post(endpointPath, payload);
// ... rest of the success/error handling logic ...
} catch (error) {
// Error handling remains largely the same, but logs might show retry attempts
console.error('Error sending MMS via Sinch (after potential retries):', error.message);
// ... rest of error handling ...
}
// ... rest of the file ...(Note: The axios-retry example above is illustrative and requires careful integration with the existing sendMms function, particularly regarding how the URL/endpoint is passed to the sinchApiClient.post method based on whether baseURL is set on the instance.)
Frequently Asked Questions About Sinch MMS Integration
What Node.js version do I need for Sinch MMS integration?
You need Node.js 18 or later for this Sinch MMS implementation. As of January 2025, Node.js 22 (codenamed 'Jod') is the current LTS version recommended for production. Node.js 18.x reaches end-of-life on April 30, 2025 – plan to upgrade to Node.js 20 or 22 for continued support. This guide uses Express v5.x, which requires Node.js 18+ and includes security improvements and async error handling.
What are the MMS file size limits for Sinch API?
Sinch MMS supports up to 8 slides per message, with each slide containing text and/or one media type (image, audio, video). The recommended file size is under 500 KB for reliable delivery across all carriers. While most US and Canadian carriers accept up to 1 MB, carrier-specific limits vary: AT&T accepts up to 1 MB, Verizon up to 1.7 MB, and T-Mobile up to 3 MB. All media files must serve a valid Content-Length header and proper Content-Type headers (e.g., image/jpeg, video/mp4).
How do I authenticate with the Sinch MMS API?
Sinch MMS API uses Bearer token authentication. Include your API token in the Authorization header as Bearer YOUR_API_TOKEN. You'll also need your Service Plan ID (sometimes called Campaign ID), which may be included in the API endpoint path or request payload depending on your API version. Always verify the authentication method and endpoint structure in the official Sinch documentation for your specific account and region (US, EU, AU).
What's the difference between Sinch MMS JSON API and XML API?
Sinch offers two MMS APIs: the XML API (industry standard for sending rich multimedia content with higher throughput) and the MM7 API (SOAP-based protocol used by carriers). This guide focuses on the JSON API, which sends MMS messages defined in JSON format to mobile numbers via HTTP POST requests. The JSON API requires UTF-8 encoded JSON data and supports slides with embedded text, images, videos, audio, and other objects.
How do I handle MMS delivery failures and fallback to SMS?
The Sinch MMS API includes automatic fallback to SMS when MMS delivery fails or is disabled by the recipient. Set fallback-sms-text in your payload with the SMS message to send if MMS fails. You can disable fallback by setting disableFallbackSms: true, though this is not recommended. Set disable-fallback-sms-link: false to include a link to view media in the fallback SMS. Implement delivery status webhooks to track message delivery and handle failures in your application logic.
What error handling should I implement for Sinch MMS requests?
Implement comprehensive error handling for three scenarios: API errors (4xx/5xx status codes), network errors (timeouts, DNS failures), and request setup errors. Log detailed error information including Sinch API error responses. Use axios-retry with exponential backoff for transient errors (5xx, network issues) but don't retry 4xx client errors. Set reasonable timeouts (15 seconds recommended). Use structured logging (pino) in production. Implement global error handlers in Express to catch unhandled errors.
How do I validate phone numbers and media URLs for MMS?
Validate that phone numbers use E.164 international format (starting with +, e.g., +12223334444) for both sender and recipient. Check that the recipient number is MMS-capable. Validate media URLs are publicly accessible and return proper Content-Length and Content-Type headers. Verify file sizes are under carrier limits (500 KB recommended). Validate slide count (max 8), subject length (max 80 chars, 40 recommended), and text length (up to 1600 chars). Use express-validator middleware for robust production validation.
What's the recommended project structure for Sinch MMS integration?
Use a layered architecture: services/ for business logic (Sinch API interaction), controllers/ for request/response handling, routes/ for API route definitions, and app.js for Express configuration. Separate concerns by keeping API logic in the service layer, validation in middleware, and HTTP handling in controllers. Store credentials in .env files (never commit to version control). Use environment-specific configurations. Implement proper error boundaries at each layer.
How do I test MMS sending in development without sending real messages?
Use Sinch's sandbox/test environment if available for your account. Mock the Sinch API responses using tools like nock or msw (Mock Service Worker) in your unit tests. Create integration tests that verify request payloads without calling the real API. Use Jest for testing with axios-mock-adapter to intercept HTTP requests. Implement feature flags to disable MMS sending in development. Log request payloads to verify correct message formatting before enabling production sends.
What are the rate limits for Sinch MMS API?
Sinch MMS API rate limits vary by account and service plan. Each service plan has a maximum messages per second (MPS) setting, with default limits varying by account type. For batch messages with multiple recipients, each recipient counts toward the rate limit. Status query limits are typically 1 request per second per IP address (HTTP 429 if exceeded). Maximum API concurrency is often 700 requests per second per IP address. Check your specific rate limits in the Sinch Dashboard under your service plan details and contact Sinch support for higher volume adjustments.
Frequently Asked Questions
How to send MMS messages with Node.js and Express?
You can send MMS messages by creating a Node.js application with Express that uses the Sinch MMS JSON API. This involves setting up a project with dependencies like Axios and dotenv, configuring your Sinch credentials, and implementing an API endpoint to handle MMS requests. Refer to the provided code examples for a step-by-step guide.
What is the Sinch MMS JSON API?
The Sinch MMS JSON API is a specific Sinch API endpoint and protocol for sending multimedia messages. It's important to consult the official Sinch documentation for the most up-to-date details on its structure, authentication methods, and required parameters, as these may change.
Why does Sinch offer multiple APIs for messaging?
Sinch offers multiple APIs, such as legacy APIs and a unified Messages API, to cater to different integration needs and preferences. This tutorial specifically uses the MMS JSON API for its dedicated MMS capabilities.
When should I use the Sinch MMS JSON API?
Use the Sinch MMS JSON API when you need to send multimedia messages (MMS) containing images, videos, or audio alongside text within your application. It's ideal for automating communication workflows requiring rich media.
Can I use a different HTTP client besides Axios?
While this guide utilizes Axios, you can potentially use other HTTP client libraries for Node.js, but you'll have to adapt the code to the respective client's API and usage patterns. Axios is recommended for its ease of use with promises.
How to set up Sinch credentials for MMS?
Set up Sinch credentials by creating a .env file containing your `SINCH_SERVICE_PLAN_ID`, `SINCH_API_TOKEN`, and `SINCH_NUMBER`. These are essential for interacting with the Sinch API. Never commit the .env file to version control, as it contains sensitive information.
What is the role of 'fallbackText' in Sinch MMS?
The `fallbackText` parameter provides the message content for a fallback SMS if the recipient's device cannot receive MMS or if MMS delivery fails. It's required unless fallback is explicitly disabled using `disableFallbackSms`.
How to structure the MMS payload for the Sinch API?
The MMS payload should be a JSON object with specific keys like 'to', 'from', 'message-subject', and 'slide'. The 'slide' key holds an array of slide objects. You must verify the correct structure according to the latest Sinch documentation for the MMS JSON API.
How to handle MMS delivery failures with Sinch?
The provided code examples demonstrate error handling using try...catch blocks and status codes. The code logs detailed error information from Sinch, allowing you to identify the cause of failure. For production, enhance logging with tools like Pino and consider retry mechanisms with exponential backoff for transient errors.
What is the 'client-reference' field for in the Sinch API?
The `client-reference` parameter is an optional field where you can include your own internal reference ID for the message, up to a recommended maximum of 64 characters. This helps you track and identify messages within your system.
When should I implement retry mechanisms for sending MMS with Sinch?
Implement retry mechanisms when you want to increase the resilience of your MMS sending. Retries are useful for transient errors like network issues or temporary Sinch API unavailability (5xx errors). Use exponential backoff to prevent overwhelming the API.
Why is verifying the Sinch documentation important for the MMS API?
Verifying the official Sinch MMS API documentation is crucial because endpoint URLs, payload structures, and other API specifics may vary depending on factors like your account settings, region, and API version. Using outdated information can lead to integration errors.
How to add media to MMS messages using Sinch?
Media (images, videos, etc.) are added using the 'slides' parameter. Each 'slide' object can have media specified as a URL. These media URLs must be publicly accessible and return a Content-Length header. Always consult the Sinch API documentation for correct slide structure.