code examples
code examples
How to Send MMS Messages with Sinch API in Next.js and Node.js (2025)
Complete tutorial: Send MMS multimedia messages with images and videos using Sinch XMS API, Next.js, and Node.js. Includes working code, error handling, deployment, and US carrier compliance.
Send MMS Messages with Sinch API, Next.js, and Node.js
Learn how to send MMS multimedia messages with images and videos using Sinch XMS API, Next.js, and Node.js. This complete tutorial shows you how to build a secure Next.js API route that handles MMS delivery, create a React form for message composition, and deploy a production-ready messaging solution.
You'll build a web application with a user-friendly interface to capture recipient phone numbers, message content, and media URLs. The React frontend communicates with a Next.js API route that integrates with the Sinch XMS API to send MMS messages reliably.
Project Goals:
- Set up a Next.js project configured for sending MMS messages.
- Create a secure API endpoint (Next.js API Route) to handle MMS sending logic.
- Integrate with the Sinch XMS API using REST principles.
- Implement error handling and secure credential management.
- Provide a frontend interface to trigger the MMS sending process.
Technology Stack:
- Next.js: React framework providing structure, server-side rendering, and API routes (your Node.js backend environment).
- Node.js: Runtime environment for your Next.js API route.
- Sinch XMS API: Third-party service for dispatching MMS messages via its RESTful
/batchesendpoint. - axios (optional): Promise-based HTTP client for Sinch API requests. Alternatively, use the built-in
fetchAPI.
System Architecture:
+-----------------+ +-----------------------+ +----------------+
| User Browser |----->| Next.js Frontend |----->| Next.js API |
| (React Component)| | (pages/index.js) | | Route (Node.js)|
+-----------------+ +-----------------------+ | (pages/api/...) |
+--------+-------+
|
v
+-----------------+ +-----------------------+ +--------+-------+
| Sinch Platform |<-----| Sinch XMS API |<-----| axios / fetch |
| (MMS Delivery) | | (/batches endpoint) | | (HTTP Request) |
+-----------------+ +-----------------------+ +----------------+Prerequisites:
- Node.js: Version 22 LTS (Active LTS until October 2027) or later. Node.js 20 LTS is also supported.
- npm or yarn: Package manager for Node.js.
- Sinch Account: Registered Sinch account with access to Messaging APIs.
- Sinch API Credentials: Service Plan ID and API Token from the Sinch dashboard.
- Sinch Phone Number: MMS-capable phone number purchased or assigned within your Sinch account. Note: MMS functionality is only available in the US region and must be enabled by contacting your Sinch account manager.1
- Publicly Accessible Media URL: URL pointing to the image or video file you want to send. Sinch must be able to fetch this URL without authentication. Supported formats include JPEG, PNG, GIF, and MP4 (check Sinch documentation for current limits). Typical size limits range from 500 KB to 5 MB depending on carrier support.
Expected Outcome:
By the end of this guide, you'll have a functional Next.js application with a form for MMS submission. Entering a recipient number, message, and media URL triggers an API call to your backend, which uses the Sinch XMS API to send the MMS message. You'll also implement error handling and user feedback.
1. Next.js Project Setup for Sinch MMS Integration
Create a new Next.js application and configure the dependencies needed for MMS messaging with Sinch.
1.1 Create the Next.js App:
Open your terminal and run the following command, replacing sinch-mms-nextjs with your desired project name:
npx create-next-app@latest sinch-mms-nextjsFollow the prompts. Select these options:
- TypeScript: No (for simplicity in this guide, but feel free to use it)
- ESLint: Yes
- Tailwind CSS: No (optional, not needed for this core functionality)
src/directory: No (we'll use the defaultpagesstructure)- App Router: No (we'll use the traditional
pagesdirectory and API routes for clarity) - Import alias: Default is fine
1.2 Navigate into the Project Directory:
cd sinch-mms-nextjs1.3 Install Dependencies:
Install axios to make HTTP requests from your backend API route to Sinch. Skip this step if you prefer the built-in fetch API.
npm install axios1.4 Configure Environment Variables:
Never hardcode API keys. Use environment variables instead. Create a .env.local file in your project root:
touch .env.localOpen .env.local and add the following lines, replacing the placeholder values with your actual Sinch credentials and number:
# .env.local
# Obtain from Sinch Dashboard: SMS → APIs → REST configuration (or similar path)
SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_ID
SINCH_API_TOKEN=YOUR_API_TOKEN
# Obtain from Sinch Dashboard: Numbers → Your Active Numbers (Must be MMS capable)
# Use E.164 format (e.g., +12125551234)
SINCH_NUMBER=+12125551234
# Sinch API Region Base URL (Check Sinch docs for your region if not US)
# Common Regions:
# US: https://us.sms.api.sinch.com
# EU: https://eu.sms.api.sinch.com
# More available: https://developers.sinch.com/docs/sms/api-rest/ (or XMS docs)
SINCH_API_BASE_URL=https://us.sms.api.sinch.comExplanation of Environment Variables:
SINCH_SERVICE_PLAN_ID: Your unique identifier for the API plan. Find this in the Sinch Customer Dashboard.SINCH_API_TOKEN: Secret token to authenticate your API requests. Find this in the Sinch Customer Dashboard. Treat this like a password.SINCH_NUMBER: MMS-capable phone number from your Sinch account used as the sender ("From" number). Must be in E.164 format (e.g.,+12125551234).SINCH_API_BASE_URL: Base URL for the Sinch Messaging API specific to your region. Ensure this matches your account setup.
Important: Add .env.local to your .gitignore file to prevent accidentally committing your credentials. create-next-app usually does this automatically.
# .gitignore (ensure this line exists)
.env*.localProject Structure (Relevant Files):
sinch-mms-nextjs/
├── pages/
│ ├── api/
│ │ └── send-mms.js # Backend API route logic
│ └── index.js # Frontend page component
├── public/
├── styles/
├── .env.local # Stores secrets (DO NOT COMMIT)
├── .gitignore
├── next.config.js
├── package.json
└── README.mdThis structure separates concerns: pages/api/ contains server-side API routes (your Node.js backend), while pages/ contains client-side React components. Next.js automatically routes requests to /api/send-mms to the send-mms.js handler.
2. Building the Sinch MMS API Route in Next.js
Create the backend logic within a Next.js API route. This route receives requests from your frontend, constructs the MMS payload for Sinch XMS API, and makes the API call.
2.1 Create the API Route File:
Create a new file: pages/api/send-mms.js
2.2 Implement the API Handler:
Paste the following code into pages/api/send-mms.js:
// pages/api/send-mms.js
import axios from 'axios'; // Or use built-in fetch
// Ensure environment variables are loaded (Next.js handles this automatically)
const SERVICE_PLAN_ID = process.env.SINCH_SERVICE_PLAN_ID;
const API_TOKEN = process.env.SINCH_API_TOKEN;
const SINCH_NUMBER = process.env.SINCH_NUMBER;
const SINCH_API_BASE_URL = process.env.SINCH_API_BASE_URL;
export default async function handler(req, res) {
// 1. Only allow POST requests
if (req.method !== 'POST') {
res.setHeader('Allow', ['POST']);
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
}
// 2. Basic Input Validation
const { to, body, mediaUrl } = req.body;
if (!to || !body || !mediaUrl) {
return res.status(400).json({ error: 'Missing required fields: to, body, mediaUrl' });
}
// Validate phone number format (basic E.164 check)
const e164Regex = /^\+[1-9]\d{1,14}$/;
if (!e164Regex.test(to)) {
return res.status(400).json({ error: 'Invalid "to" phone number format. Use E.164 (e.g., +12125551234).' });
}
// Validate media URL format (basic check)
try {
new URL(mediaUrl);
} catch (error) {
return res.status(400).json({ error: 'Invalid "mediaUrl" format.' });
}
// Optional: Validate media file extension
const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.mp4', '.mov'];
const urlPath = new URL(mediaUrl).pathname.toLowerCase();
const hasValidExtension = validExtensions.some(ext => urlPath.endsWith(ext));
if (!hasValidExtension) {
return res.status(400).json({ error: 'Invalid media file type. Supported: JPEG, PNG, GIF, MP4, MOV.' });
}
// 3. Construct the Sinch XMS API Payload for MMS Batch
// The /batches endpoint is used for sending SMS/MMS.
const payload = {
from: SINCH_NUMBER,
to: [to], // API expects an array of recipients
body: body,
parameters: { // Parameters specific to MMS often go here
media_body: {
url: mediaUrl,
// Optional: message if URL cannot be fetched, filename
// message: "Could not display media content.",
// filename: "image.jpg"
}
},
// Optional: type: 'mt_media' // Explicitly setting type might be required by some Sinch setups
// Optional: delivery_report: 'full' // Request delivery reports
};
// Optional parameters explained:
// - delivery_report: 'none', 'summary', or 'full' – controls delivery status callbacks
// - type: 'mt_media' – explicitly marks message as MMS (usually auto-detected)
// - callback_url: URL where Sinch sends delivery reports (requires webhook endpoint)
// To receive delivery reports, set delivery_report: 'full' and implement a webhook endpoint
// 4. Prepare API Request Options
// Using the XMS (Cross-channel Messaging) API endpoint for batches
const API_ENDPOINT = `${SINCH_API_BASE_URL}/xms/v1/${SERVICE_PLAN_ID}/batches`;
const config = {
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
},
};
// 5. Send the request to Sinch
try {
console.log(`Sending MMS batch request to ${API_ENDPOINT}`);
// WARNING: Do NOT log the full payload in production – it contains sensitive recipient numbers and message content.
// Log only metadata or sanitized data.
// console.log('Payload (DEBUG):', JSON.stringify(payload, null, 2));
const sinchResponse = await axios.post(API_ENDPOINT, payload, config);
console.log('Sinch API Response Status:', sinchResponse.status);
// console.log('Sinch API Response Data:', sinchResponse.data); // Avoid logging full response data in production
// 6. Handle Sinch Response (Success)
// Sinch typically returns 201 Created for successful batch submission.
if (sinchResponse.status === 201) {
return res.status(200).json({ // Return 200 OK to the client
success: true,
message: 'MMS submitted successfully to Sinch.',
batchId: sinchResponse.data.id, // Include batch ID for tracking
});
} else {
// Handle unexpected success status codes (201 is standard)
console.warn('Received unexpected success status from Sinch:', sinchResponse.status);
return res.status(500).json({ error: 'Received unexpected success status from Sinch API.', details: sinchResponse.data });
}
} catch (error) {
// 7. Handle Errors
console.error('Error sending MMS via Sinch:', error);
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error('Sinch API Error Status:', error.response.status);
console.error('Sinch API Error Data:', error.response.data);
// Forward Sinch's error status and data if available
return res.status(error.response.status || 500).json({
error: 'Failed to send MMS via Sinch.',
details: error.response.data || 'No details provided.',
});
} else if (error.request) {
// The request was made but no response was received
console.error('Sinch API No Response:', error.request);
return res.status(504).json({ error: 'No response received from Sinch API.' });
} else {
// Something happened in setting up the request that triggered an Error
console.error('Sinch API Request Setup Error:', error.message);
return res.status(500).json({ error: 'Error setting up request to Sinch API.', details: error.message });
}
}
}Code Explanation:
- Method Check: Ensures only POST requests are accepted.
- Input Validation: Checks for required fields (
to,body,mediaUrl) and validates the phone number (E.164), media URL format, and file extension. - Payload Construction: Creates the JSON
payloadfor the Sinch/xms/v1/.../batchesendpoint.from: Your Sinch number.to: An array containing the recipient's E.164 number.body: The text part of the message.parameters.media_body.url: Specifies the media URL for MMS via the XMS API. This URL must be publicly accessible.- Optional parameters can be added per Sinch XMS API documentation.
- API Request Setup: Defines the target Sinch XMS API endpoint and sets
AuthorizationandContent-Typeheaders. - API Call: Uses
axios.post(orfetch) to send the request. Includes basic logging (with a warning about logging sensitive data in production). - Success Handling: Checks for the expected
201 Createdstatus from Sinch. Returns a200 OKresponse to the frontend with success status and the SinchbatchId. - Error Handling: Catches errors from the API call, logging details server-side and returning appropriate error statuses and messages to the frontend.
Common Sinch Error Responses:
| Status Code | Error Type | Cause | Solution |
|---|---|---|---|
| 401 | unauthorized | Invalid API token or Service Plan ID | Verify credentials in .env.local |
| 400 | syntax_invalid_parameter_format | Invalid phone number format | Ensure E.164 format (e.g., +12125551234) |
| 403 | forbidden | MMS not enabled or unavailable for region | Contact Sinch account manager to enable MMS |
| 400 | syntax_constraint_violation | Media URL not accessible | Verify URL is publicly accessible without authentication |
| 429 | too_many_requests | Rate limit exceeded | Implement exponential backoff and retry logic |
3. Creating the React Frontend for MMS Composition
Build a React component on a Next.js page to collect user input (recipient, message, media URL) and trigger your MMS API route.
3.1 Modify the Index Page:
Replace the contents of pages/index.js with the following code:
// pages/index.js
import { useState } from 'react';
import Head from 'next/head';
export default function HomePage() {
const [to, setTo] = useState('');
const [body, setBody] = useState('Check out this cool image!');
const [mediaUrl, setMediaUrl] = useState('');
const [statusMessage, setStatusMessage] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setIsLoading(true);
setStatusMessage('');
// Basic frontend validation (optional, enhance as needed)
if (!to || !body || !mediaUrl) {
setStatusMessage('Error: Please fill in all fields.');
setIsLoading(false);
return;
}
if (!mediaUrl.startsWith('http://') && !mediaUrl.startsWith('https://')) {
setStatusMessage('Error: Media URL must start with http:// or https://');
setIsLoading(false);
return;
}
try {
const response = await fetch('/api/send-mms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ to, body, mediaUrl }),
});
const data = await response.json();
if (response.ok) { // Check if status code is 2xx
setStatusMessage(`Success! MMS submitted. Batch ID: ${data.batchId}`);
// Optional: Clear form on success
// setTo('');
// setBody('Check out this cool image!');
// setMediaUrl('');
} else {
// Handle errors reported by our API route
setStatusMessage(`Error: ${data.error} ${data.details ? `(${JSON.stringify(data.details)})` : ''}`);
}
} catch (error) {
console.error('Frontend Error sending MMS:', error);
setStatusMessage('Error: Failed to send request. Check console.');
} finally {
setIsLoading(false);
}
};
// Basic inline styles for demonstration
const styles = {
container: { padding: '2rem', maxWidth: '500px', margin: 'auto', fontFamily: 'sans-serif' },
form: { display: 'flex', flexDirection: 'column', gap: '1rem' },
label: { display: 'flex', flexDirection: 'column', gap: '0.5rem' },
input: { padding: '0.5rem', border: '1px solid #ccc', borderRadius: '4px' },
button: { padding: '0.75rem', border: 'none', borderRadius: '4px', backgroundColor: '#0070f3', color: 'white', cursor: 'pointer', fontSize: '1rem' },
buttonDisabled: { backgroundColor: '#ccc', cursor: 'not-allowed' },
status: { marginTop: '1rem', padding: '0.5rem', borderRadius: '4px', wordBreak: 'break-word' },
statusSuccess: { backgroundColor: '#d4edda', color: '#155724' },
statusError: { backgroundColor: '#f8d7da', color: '#721c24' },
};
return (
<div style={styles.container}>
<Head>
<title>Send Sinch MMS with Next.js</title>
<meta name="description" content="Send MMS messages using Sinch API and Next.js" />
<link rel="icon" href="/favicon.ico" />
</Head>
<h1>Send MMS via Sinch</h1>
<form onSubmit={handleSubmit} style={styles.form}>
<label style={styles.label}>
Recipient Phone Number (E.164):
<input
type="tel"
value={to}
onChange={(e) => setTo(e.target.value)}
placeholder="+12125551234"
required
style={styles.input}
/>
</label>
<label style={styles.label}>
Message Body:
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
required
rows={3}
style={styles.input}
/>
</label>
<label style={styles.label}>
Public Media URL (Image/Video):
<input
type="url"
value={mediaUrl}
onChange={(e) => setMediaUrl(e.target.value)}
placeholder="https://example.com/image.jpg"
required
style={styles.input}
/>
</label>
<button
type="submit"
disabled={isLoading}
style={{ ...styles.button, ...(isLoading ? styles.buttonDisabled : {}) }}
>
{isLoading ? 'Sending...' : 'Send MMS'}
</button>
</form>
{statusMessage && (
<div style={{
...styles.status,
...(statusMessage.startsWith('Success') ? styles.statusSuccess : styles.statusError)
}}>
{statusMessage}
</div>
)}
</div>
);
}Code Explanation:
- State Management: Uses
useStatefor form inputs (to,body,mediaUrl), loading state (isLoading), and feedback (statusMessage). - Form Structure: Standard HTML form with inputs for recipient, message, and media URL. Basic inline styles applied.
handleSubmitFunction:- Prevents default submission.
- Sets loading state.
- Performs basic frontend validation.
- Uses
fetchto POST data to/api/send-mms. - Handles the JSON response from the API route, updating
statusMessagefor success (showing Batch ID) or failure (showing error details from the API). - Catches network errors during
fetch. - Resets loading state in the
finallyblock.
- Feedback: Displays
statusMessagebelow the form, styled for success/error. Disables the button during loading. - Head Component: Includes
metaandlinktags for proper SEO.
4. Integrating with Sinch (Credentials & Dashboard)
This section reiterates how to obtain and securely manage your Sinch API credentials, which we've already placed in .env.local.
4.1 Obtaining Credentials:
- Log in to your Sinch Customer Dashboard.
- Navigate to SMS or Messaging APIs.
- Find the REST API configuration area.
- Locate your Service plan ID and API token. Click "Show" or "Generate" for the token.
- Copy these values carefully.
4.2 Obtaining Your Sinch Number:
- In the Sinch Customer Dashboard, navigate to Numbers.
- Go to Your virtual numbers.
- Identify an active number with MMS capability enabled. Verify it's in E.164 format (e.g.,
+1xxxxxxxxxx). - Copy this number.
4.3 Secure Storage:
- Place these credentials (
SINCH_SERVICE_PLAN_ID,SINCH_API_TOKEN,SINCH_NUMBER) into your.env.localfile. - Never commit
.env.localor any file containing your API token to version control. Ensure.env*.localis in your.gitignorefile. - When deploying, use your hosting provider's environment variable management.
Environment Variable Configuration by Platform:
| Platform | Configuration Location | Documentation |
|---|---|---|
| Vercel | Project Settings → Environment Variables | Add variables for Production, Preview, and Development |
| Netlify | Site Settings → Environment Variables | Configure build and runtime variables |
| AWS Amplify | App Settings → Environment Variables | Set variables per branch |
| Railway | Project → Variables | Auto-injects into runtime |
| Render | Environment → Environment Variables | Supports .env file upload |
5. Error Handling and Logging
We've implemented error handling in both the API route and the frontend.
API Route (pages/api/send-mms.js):
- Input Validation: Returns
400 Bad Requestfor missing/invalid inputs. - Method Check: Returns
405 Method Not Allowedfor non-POST requests. - Sinch API Errors: Catches errors from the
axios.postcall, logs details server-side (console.error), and returns specific status codes/messages to the frontend. - Logging: Uses
console.logandconsole.error. Important: In a production environment, replaceconsole.log/console.errorwith a robust logging library (e.g., Pino, Winston). Critically, ensure that any debug logging (like the commented-out payload log in the API route) is removed or heavily sanitized in production to avoid logging sensitive information like recipient phone numbers or message content.
Frontend (pages/index.js):
- API Response Errors: Displays error messages received from the API route.
- Fetch Errors: Catches network errors during the
fetchcall. - Loading State: Provides visual feedback during the API call.
Retry Mechanisms (Advanced):
For production, consider implementing retries with exponential backoff in the API route for transient network errors or temporary Sinch API issues (e.g., 5xx errors). Libraries like axios-retry can help with this implementation.
6. Database Schema and Data Layer (Not Applicable)
This guide focuses on the stateless action of sending an MMS and does not require a database. For applications needing message history or tracking, integrate a database (e.g., PostgreSQL, MongoDB) and data layer (e.g., Prisma, TypeORM).
7. Adding Security Features
- Environment Variables: (Covered) Keep secrets out of code.
- Input Validation: (Covered) Essential on both client and server (API route). Sanitize inputs if displayed elsewhere.
- HTTPS: Use HTTPS for your Next.js app in production. Sinch API communication is already over HTTPS.
- Rate Limiting: (Advanced) Implement rate limiting on
/api/send-mmsto prevent abuse. Use libraries likerate-limiter-flexibleor platform features. - Authentication/Authorization: (Advanced) Protect the API route using authentication (e.g., NextAuth.js, Clerk) to ensure only authorized users can send MMS.
- URL Validation: The
mediaUrlis fetched by Sinch. While basic URL validation is in place, ensure your validation prevents users from submitting malicious or invalid URLs. Robust URL validation and file extension checking protect against abuse.
8. Handling Special Cases (MMS Specific)
- E.164 Format: Strictly required by Sinch for all phone numbers.
- Public Media URL: Must be publicly accessible without authentication for Sinch to fetch. The URL should return appropriate
Content-Typeheaders. - Media File Types/Size: Check Sinch documentation for supported types (JPEG, PNG, GIF, MP4, etc.) and size limits. Common limits are around 500 KB to 5 MB depending on carrier support. The code includes basic file extension validation.
- Character Limits: MMS allows more text than SMS (typically 1,600 characters), but practical limits exist based on carrier support.
- Regional Restrictions: MMS is only available in the US region.1 Contact your Sinch account manager to enable MMS functionality on your account. Sending MMS to or from non-US regions will fail. For international messaging, use SMS or other supported channels.
- Carrier Compliance: Adhere to content guidelines and regulations (e.g., CTIA guidelines in the US, TCPA (Telephone Consumer Protection Act) compliance for commercial messages). Ensure you have proper consent from recipients before sending commercial MMS messages. Implement opt-in/opt-out mechanisms to comply with TCPA requirements.
9. Implementing Performance Optimizations (Limited Scope)
Performance is mainly dictated by the Sinch API response time.
- API Route Performance: Keep the handler lightweight; avoid blocking operations before the API call.
- Frontend Performance: Standard React optimization techniques apply if the page grows complex.
- Caching: Not applicable for the sending action itself.
- Resource Usage: Ensure adequate server resources for expected volume.
10. Adding Monitoring, Observability, and Analytics (Basic Concepts)
For production:
- Health Checks: Implement a
/api/healthendpoint. - Performance Metrics: Use APM tools (Datadog, New Relic, Sentry) to monitor API route response times and error rates.
- Error Tracking: Use services like Sentry or Bugsnag for capturing exceptions.
- Sinch Dashboard: Monitor API logs and delivery reports in the Sinch portal.
- Logging: Implement structured logging (Pino/Winston) and send logs to a central system (Datadog Logs, CloudWatch) for analysis and alerting (e.g., on high error rates).
11. Troubleshooting and Caveats
- Error:
401 Unauthorizedfrom Sinch: CheckSINCH_API_TOKEN,SINCH_SERVICE_PLAN_ID,SINCH_API_BASE_URLin environment variables. EnsureBearerprefix is correct. - Error:
400 Bad Requestfrom Sinch (e.g.,INVALID_PARAMETER_VALUE): Check payload format (E.164 numbers, publicmediaUrl), required fields. Examinedetailsin the error response. Check against Sinch XMS API docs. - Error:
403 Forbidden(MMS related): VerifySINCH_NUMBERis MMS-enabled, account supports MMS, destination is supported. - Error: Media Not Displaying: Ensure
mediaUrlis public and accessible. Check file type/size limits. - API Route Returns
500 Internal Server Error: Check server logs for exceptions (often missing/undefined environment variables). - Caveat: Sinch API Rate Limits: Sinch enforces limits. Implement client-side rate limiting and consider retries with backoff if needed. Expect
429 Too Many Requests. - Caveat: Asynchronous Delivery: A successful API response (
201 Created) means Sinch accepted the batch, not that the MMS was delivered. Use Delivery Reports for status updates. - Caveat: Cost: MMS messages incur costs. Monitor usage.
- Caveat: MMS Regional Availability: MMS functionality is only available in the US region and must be explicitly enabled by your Sinch account manager.1 Attempting to send MMS without enablement or from non-US regions will result in API errors. For other regions, use SMS or alternative channels.
12. Deployment and CI/CD
12.1 Deployment Platforms: Vercel and Netlify are excellent choices for Next.js.
12.2 Environment Variables: Configure SINCH_SERVICE_PLAN_ID, SINCH_API_TOKEN, SINCH_NUMBER, SINCH_API_BASE_URL securely in your hosting provider's settings. Do not commit .env.local.
12.3 Deployment Process (Example with Vercel):
- Push code to Git (ensure
.env.localis gitignored). - Connect Git repo to Vercel.
- Configure Project Name and Root Directory.
- Crucially: Add the four Sinch variables in Vercel's Environment Variables settings.
- Deploy.
12.4 CI/CD:
- Git integration enables automatic deployments on push.
- Integrate automated testing (Jest, Cypress) in your build pipeline (
npm test). - Use platform features for rollbacks if needed.
// package.json (example scripts)
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest" // Or your test runner command
}
}13. Verification and Testing
13.1 Manual Verification:
- Run locally:
npm run dev. - Open
http://localhost:3000. - Fill form with a valid test number (E.164), message, and a publicly accessible image URL.
- Submit.
- Check frontend for success/error message (with Batch ID on success).
- Check test phone for MMS (text + image).
- Check server console logs for API route output.
- Check Sinch Dashboard API logs.
13.2 Automated Testing (Concepts & Examples):
-
Unit Tests (API Route): Use Jest to test
pages/api/send-mms.js. Mockaxios/fetchto simulate Sinch responses (success/errors) and assert handler logic.javascript// Example using Jest and mocking axios (requires setup) // __tests__/api/send-mms.test.js (simplified concept) import handler from '../../pages/api/send-mms'; import axios from 'axios'; jest.mock('axios'); // Mock the axios module // Mock environment variables before tests run process.env.SINCH_SERVICE_PLAN_ID = 'test-plan-id'; process.env.SINCH_API_TOKEN = 'test-api-token'; process.env.SINCH_NUMBER = '+15550001111'; process.env.SINCH_API_BASE_URL = 'https://fake.sinch.api'; describe('/api/send-mms handler', () => { let req, res; beforeEach(() => { jest.clearAllMocks(); req = { method: 'POST', body: { /* ... valid body ... */ } }; res = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis(), setHeader: jest.fn().mockReturnThis() }; // Default mock for successful Sinch call (201 Created) axios.post.mockResolvedValue({ status: 201, data: { id: 'test-batch-id' } }); }); it('should return 405 if method is not POST', async () => { req.method = 'GET'; await handler(req, res); expect(res.status).toHaveBeenCalledWith(405); }); it('should return 400 if required fields are missing', async () => { req.body = { to: '+123' }; // Invalid/missing fields await handler(req, res); expect(res.status).toHaveBeenCalledWith(400); }); it('should call Sinch API with correct payload and return 200 on valid request', async () => { req.body = { to: '+15552223333', body: 'Test', mediaUrl: 'https://valid.url/img.jpg' }; await handler(req, res); expect(axios.post).toHaveBeenCalledWith( expect.stringContaining('/xms/v1/test-plan-id/batches'), // Check endpoint expect.objectContaining({ from: '+15550001111', to: ['+15552223333'] }), // Check payload essentials expect.any(Object) // Check config object presence ); expect(res.status).toHaveBeenCalledWith(200); // API route returns 200 to client expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ success: true, batchId: 'test-batch-id' })); }); it('should handle Sinch API errors by forwarding status and details', async () => { const errorResponse = { response: { status: 401, data: { error: 'Auth failed' } } }; axios.post.mockRejectedValue(errorResponse); req.body = { to: '+15552223333', body: 'Test', mediaUrl: 'https://valid.url/img.jpg' }; await handler(req, res); expect(res.status).toHaveBeenCalledWith(401); expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ error: 'Failed to send MMS via Sinch.', details: { error: 'Auth failed' } })); }); // Add more tests... }); -
Integration Tests (Frontend-API): Use React Testing Library or Cypress to test form submission and handling of API responses (mocking
fetchor usingcy.intercept). -
End-to-End (E2E) Tests: Use Cypress/Playwright sparingly against a test environment to simulate full user flow.
13.3 Verification Checklist:
-
- Project created, dependencies installed.
-
-
.env.localcreated with correct Sinch credentials/number (and not committed to Git).
-
-
-
.env.locallisted in.gitignore.
-
-
- API route
pages/api/send-mms.jsimplemented.
- API route
-
- Frontend page
pages/index.jsimplemented.
- Frontend page
-
- Application runs locally (
npm run dev).
- Application runs locally (
-
- Form submission triggers POST to API route (check browser dev tools).
-
- API route logs expected output (request received, Sinch response/error).
-
- Successful submission shows success message with Batch ID on frontend.
-
- MMS (text + media) received on test phone number.
-
- Invalid input (e.g., bad phone number, missing URL) shows error message on frontend.
-
- API errors (simulated or real, e.g., bad token) show error message on frontend.
-
- (Optional) Automated tests pass.
-
- (Deployment) Environment variables configured correctly in hosting.
-
- (Deployment) Deployed application functions correctly.
Related Guides
- Send SMS Messages with Sinch and Next.js - Learn the basics of SMS messaging with Sinch
- Implement Two-Way Messaging with Sinch - Handle inbound MMS and SMS messages
- Set Up Delivery Status Callbacks - Track MMS delivery status
References
Additional Technical References:
- Node.js LTS Schedule: Node.js 22 (Jod) is Active LTS until October 2025, then Maintenance LTS until April 2027. Node.js 20 (Iron) remains in Maintenance LTS until April 2026. Source: https://nodejs.org/en/about/previous-releases
- Next.js Version: This guide is compatible with Next.js 14.x and 15.x. As of October 2025, Next.js 15.5 is the latest stable version with Turbopack builds in beta.
- Sinch XMS API: The
/xms/v1/{service_plan_id}/batchesendpoint supports SMS, MMS (US only), and binary messages. MMS is sent by includingparameters.media_body.urlin the payload.
Footnotes
-
Sinch. "SMS API Reference - MMS Support." Sinch Developer Documentation. Accessed October 2025. https://developers.sinch.com/docs/sms/api-reference/ - "If you are in the US region, you are also able to send and receive MMS messages. To enable MMS functionality, contact your account manager." ↩ ↩2 ↩3
Frequently Asked Questions
How to send MMS messages with Next.js?
Use a Next.js API route as your backend to handle communication with the Sinch XMS API. Create a form on your frontend to collect recipient details, message body, and media URL, then send a POST request to the API route, which will interact with Sinch to send the MMS.
What is the Sinch XMS API used for in Next.js MMS?
The Sinch XMS API is the core service that handles sending MMS messages. Your Next.js API route interacts with its `/batches` endpoint, sending a specifically formatted JSON payload to trigger the message dispatch.
Why use environment variables for Sinch credentials?
Storing sensitive information like API tokens directly in your code is a security risk. Environment variables (like those in `.env.local`) provide a secure way to manage and access these credentials without exposing them in your codebase.
When should I validate user input for Sinch MMS?
Input validation is crucial on both the client-side (in your form) and server-side (in your API route). This prevents bad data from reaching the Sinch API and causing errors. Validate for missing fields, correct phone number format (E.164), and valid media URLs.
Can I use the built-in fetch API with Sinch MMS?
Yes, the built-in `fetch` API can be used instead of `axios` to make the API call to Sinch. The core logic for constructing the request remains the same, just the syntax for making the HTTP request will differ slightly.
How to handle Sinch API errors in Next.js?
Implement error handling in your API route to catch potential errors during the API call to Sinch. Log the errors server-side, return specific error codes/messages to the frontend, and consider implementing retry mechanisms with exponential backoff for transient errors.
What is the role of a Next.js API route in Sinch MMS integration?
The API route acts as your serverless backend. It receives requests from the frontend, constructs the payload for the Sinch API (including recipient, message, and media URL), makes the API call, and returns the result (success or error) to the frontend.
How to structure the Sinch MMS API request payload?
The payload should be a JSON object sent to the Sinch `/batches` endpoint. It must include the recipient's number (`to`), the message body (`body`), and the publicly accessible `mediaUrl`. Authentication is handled via headers, using Bearer authorization with your Sinch API token.
What are the prerequisites for sending Sinch MMS with Next.js?
You'll need a Sinch account with an MMS-enabled number, your Sinch API credentials (Service Plan ID and API Token), Node.js and npm/yarn, a publicly accessible media URL, and the Next.js project setup as described in the article.
How to secure Sinch API integration in a Next.js app?
Use environment variables to store API credentials, implement thorough input validation on both frontend and backend, enforce HTTPS for all communication, consider rate limiting, and use authentication/authorization to protect your API route if needed.
How do I obtain my Sinch Service Plan ID and API Token?
Log in to your Sinch Dashboard and navigate to the SMS/Messaging API section. Find the REST API configuration area to locate your Service Plan ID and API Token. Copy these securely.
What phone number format is required for Sinch MMS?
Sinch requires the E.164 format for phone numbers (e.g., +12125551234). Validate and enforce this format on both your frontend and API route to prevent errors.
Why is my Sinch MMS media not displaying?
Verify that the `mediaUrl` you're providing is publicly accessible by Sinch. Also, check Sinch's documentation for any file type or size restrictions that might be causing the issue. If the URL requires any sort of authentication it will not be fetchable by Sinch.
How to troubleshoot "401 Unauthorized" error from Sinch?
Double-check your `SINCH_API_TOKEN`, `SINCH_SERVICE_PLAN_ID`, and `SINCH_API_BASE_URL` in your `.env.local` file and your deployment environment variables. Ensure the Authorization header has the correct `Bearer` prefix and that these values match exactly what is in your Sinch dashboard.
What does a 201 Created response from Sinch mean?
It means Sinch has successfully received your MMS request and added it to its queue for processing. It does *not* guarantee immediate delivery. You'll need to rely on Sinch's delivery reports for actual delivery status.