code examples
code examples
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 Case | Choose MMS | Choose 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
.envfile - 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
/batchesendpoint withmt_mediatype
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:
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) --> ClientPrerequisites:
- 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-Typeheader (e.g.,image/jpeg,video/mp4) and aContent-Lengthheader.
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-Typeheader (e.g.,text/plain,image/gif,audio/mp3,video/mp4)
File Size Calculation Examples:
| Original File | Calculation | Final Size | Status |
|---|---|---|---|
| 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 Type | Formats | Recommended Size | Max Dimension | Notes |
|---|---|---|---|---|
| Images | GIF, JPG, PNG | < 500 KB | 1024×768 px | Use JPEG for photos, PNG for graphics |
| Audio | MP3, WAV | < 500 KB | N/A | MP3 recommended for smaller size |
| Video | 3GP, MP4, MPEG, MPG, AVI, WMV | < 500 KB | 640×480 px | MP4 H.264 codec recommended |
| Text | TEXT/PLAIN | < 100 KB | N/A | Plain 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.
-
Create Project Directory: Open your terminal and create a new directory for the project.
bashmkdir sinch-mms-sender cd sinch-mms-sender -
Initialize Node.js Project: Create a
package.jsonfile.bashnpm init -y -
Enable ES Modules: Use
import/exportsyntax by adding"type": "module"to yourpackage.json. Alternatively, rename all.jsfiles to.mjs. Openpackage.jsonand add:json{ "name": "sinch-mms-sender", "version": "1.0.0", "description": "", "main": "src/server.js", "type": "module", "scripts": { }, } -
Install Dependencies: Install Express, Axios, dotenv, validation, rate limiting, security headers, logging, and retry libraries with pinned versions for production stability.
bashnpm 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 -
Install Development Dependencies: Install
nodemonfor automatic server restarts during development.bashnpm install --save-dev nodemon -
Create Project Structure: Organize the code for better maintainability.
bashmkdir 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 -
Configure
.gitignore: Prevent sensitive files and unnecessary modules from version control.dotenv# .gitignore node_modules .env npm-debug.log logs/ *.log -
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 integrationPORT: Port the Express server listens onNODE_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) oreu.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:
// 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):
# .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=debugSource: Sinch SMS API Reference
-
Add npm Scripts: Add scripts to
package.jsonfor 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
// 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:
// 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:
// 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
// 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 Code | HTTP Status | Description | Solution |
|---|---|---|---|
400 | 400 Bad Request | Invalid JSON payload or missing required fields | Verify payload structure matches API docs |
401 | 401 Unauthorized | Invalid or missing API token | Check SINCH_API_TOKEN in .env |
402 | 402 Payment Required | Insufficient account balance | Add credits to your Sinch account |
403 | 403 Forbidden | Service Plan ID lacks MMS permissions | Contact Sinch support to enable MMS |
404 | 404 Not Found | Invalid Service Plan ID or endpoint URL | Verify SINCH_SERVICE_PLAN_ID and base URL |
429 | 429 Too Many Requests | Rate limit exceeded | Implement exponential backoff, reduce request rate |
500 | 500 Internal Server Error | Sinch server error | Retry with exponential backoff |
503 | 503 Service Unavailable | Temporary service outage | Retry after delay, check Sinch status page |
Rate Limit Handling:
Implement custom rate limit detection and backoff:
// 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:
- Logger: Imports the configured
winstonlogger. - Configuration: Loads Sinch credentials and base URL, logging a fatal error and exiting if any are missing.
- Endpoint: Defines the URL for the Sinch XMS
/batchesendpoint using the format{region}.sms.api.sinch.com/xms/v1/{service_plan_id}/batches. - Axios Instance & Retry: Creates a dedicated
axiosinstance with 30-second timeout and configuresaxios-retryto automatically retry requests on network errors or 5xx responses from Sinch, using exponential backoff. Logs retry attempts. sendMmsFunction:- Takes recipient, media URL, media type, text, subject, fallback text, and strict validation flag.
- Payload Construction: Builds the JSON object for the
/batchesendpoint. Key fields includeto,from,body.type(must be"mt_media"),body.url,body.message, andbody.parameters(conditionally added). - Strict Validation: Optional
strict_validationparameter enables validation against Sinch MMS channel best practices. - Media URL Requirements: The URL must be publicly accessible with valid
Content-TypeandContent-Lengthheaders. - Removes empty
parametersormessagefields if not used. - Headers: Sets
Content-TypeandAuthorization: Bearer. - Logging: Uses the logger (
logger.info,logger.debug,logger.warn,logger.error). Logs payload at debug level. - API Call: Uses the
sinchAxiosinstance (with retry) to make thePOSTrequest. - 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
catchblock 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
// 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:
// 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:
// 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
// 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:
// 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
// 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:
// 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:
# 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:
- Auth Middleware (
authMiddleware.js): Checks forX-API-Keyheader against.env. Logs warnings if the key is missing or invalid. - Controller (
mmsController.js):- Uses
validationResultto check for errors fromexpress-validator. Returns 400 if invalid. - Generates correlation ID for distributed tracing.
- Extracts validated data.
- Calls
sendMmsservice function within atry…catchblock. - Logs actions and outcomes.
- Returns
202 Acceptedon success, including the service response. - Catches errors from the service, logs them, and returns
500 Internal Server Error.
- Uses
- Router (
mmsRoutes.js):- Defines strict validation rules for all input fields using
express-validator. - Defines the
POST /api/mms/sendroute. - Applies middleware sequentially:
apiKeyAuth, validation rules, then the controller. - Includes
GET /status/:batchIdendpoint to check delivery status.
- Defines strict validation rules for all input fields using
How Do You Integrate the Sinch MMS API?
Focus on the specific Sinch integration points.
-
Credentials:
- Set
SINCH_SERVICE_PLAN_ID,SINCH_API_TOKEN,SINCH_MMS_NUMBER,SINCH_API_BASE_URLin your.envfile (locally) and as secure environment variables in your deployment environment. Never commit.envor hardcode credentials. - Refer to the Prerequisites section for details on finding these values.
- Set
-
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) oreu.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.
- Use the Sinch XMS API endpoint:
Source: Sinch SMS API Reference
-
Authentication:
- Sinch API uses Bearer Token authentication.
- Set the
Authorization: Bearer ${SINCH_API_TOKEN}header insinchService.js.
-
Payload:
- Use the
mt_mediatype within the/batchesstructure in your JSON payload. Set the type field tomt_mediafor MMS messages. - To include text with your media, populate the
messagefield of the body object. - Use the optional
strict_validationfield to enable message validation against Sinch MMS channel best practices. - Verify parameter names against the official Sinch XMS API documentation when adding more options.
- Use the
Source: Sinch MMS Support Documentation
- Media URL Requirements:
- Make the
mediaUrlpublicly accessible via HTTP GET. - Ensure the hosting server returns a
Content-Lengthheader. Check usingcurl -I <your_media_url>. - Ensure the hosting server returns a correct
Content-Typeheader (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.
- Make the
Source: Sinch MMS Best Practices
- Fallback Mechanism:
- Use the
parameters.mms_fallback_textfield to control the SMS text if MMS fails. - Check Sinch documentation for parameters like
disable_fallback_sms_linkordisable_fallback_smsif needed.
- Use the
Fallback Failure Scenarios:
Understand when and how MMS falls back to SMS:
| Scenario | Fallback Behavior | Testing Method |
|---|---|---|
| Non-MMS device | Sends SMS with fallback text | Test with feature phone |
| Carrier doesn't support MMS | Sends SMS with fallback text | Test with MVNO carriers |
| Media URL unreachable | Sends SMS with fallback text | Use invalid URL in test |
| File size exceeds limit | May fail silently or send SMS | Send 2 MB file |
| Invalid Content-Type | Fails with error | Remove Content-Type header |
Testing Fallback:
// 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');
};- 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-validatorin routes - Authentication (401/403):
apiKeyAuthmiddleware - Service Errors (Sinch API): Handle in
sinchService.jscatch block with retries viaaxios-retry - Controller Errors (500): Controller's
catchblock handles service errors
- Validation (400):
- Logging:
- Use
winstonfor 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.*withlogger.*
- Use
- Retry Mechanisms:
- Configure
axios-retryinsinchService.js - Retry network issues or 5xx errors from Sinch with exponential backoff
- Configure
Monitoring and Alerting:
Implement production monitoring with health checks and alerts:
// 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:
// 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.