This guide provides a complete walkthrough for building a feature within a Next.js application to send Multimedia Messaging Service (MMS) messages using the Sinch platform (specifically the XMS API) and Node.js for the backend logic.
We will build a simple web interface using Next.js to capture recipient details, a text message, and a media URL, which then securely communicates with a Next.js API route. This route acts as our Node.js backend, handling the communication with the Sinch API to dispatch the MMS message.
This approach enables you to integrate robust MMS capabilities directly into your modern web applications.
Project Goals:
- Set up a Next.js project configured for sending MMS.
- Create a secure API endpoint (Next.js API Route) to handle MMS sending logic.
- Integrate with the Sinch XMS API using REST principles.
- Implement basic error handling and secure credential management.
- Provide a simple frontend interface to trigger the MMS sending process.
Technology Stack:
- Next.js: A React framework providing structure, server-side rendering, and API routes (our Node.js backend environment).
- Node.js: The runtime environment for our Next.js API route.
- Sinch XMS API: The third-party service used for dispatching MMS messages via its RESTful
/batches
endpoint. - axios (optional): A promise-based HTTP client for making requests to the Sinch API. Alternatively, the built-in
fetch
API can be used.
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 18.x or later installed.
- npm or yarn: Package manager for Node.js.
- Sinch Account: A registered Sinch account with access to the Messaging APIs.
- Sinch API Credentials: Your Service Plan ID and API Token from the Sinch dashboard.
- Sinch Phone Number: An MMS-capable phone number purchased or assigned within your Sinch account (Note: MMS support is primarily available in the US and Canada).
- Publicly Accessible Media URL: A URL pointing to the image/media file you want to send. Sinch needs to be able to fetch this URL.
Expected Outcome:
By the end of this guide_ you will have a functional Next.js application with a page containing a form. Submitting this form (with a recipient number_ message_ and media URL) will trigger an API call to your backend_ which in turn uses the Sinch XMS API to send an MMS message to the specified recipient. You'll also have basic error handling and feedback mechanisms in place.
1. Setting up the Next.js Project
Let's start by creating a new Next.js application and setting up the basic structure and dependencies.
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-nextjs
Follow the prompts. We recommend selecting:
- 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 defaultpages
structure)- App Router: No (we'll use the traditional
pages
directory and API routes for clarity) - Import alias: Default is fine
1.2 Navigate into the Project Directory:
cd sinch-mms-nextjs
1.3 Install Dependencies:
We need axios
to make HTTP requests from our backend API route to Sinch. If you prefer the built-in fetch
_ you can skip this step.
npm install axios
1.4 Configure Environment Variables:
Sensitive information like API keys should never be hardcoded. We'll use environment variables. Create a file named .env.local
in the root of your project.
touch .env.local
Open .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.com
Explanation of Environment Variables:
SINCH_SERVICE_PLAN_ID
: Your unique identifier for the API plan you're using. Found in the Sinch Customer Dashboard.SINCH_API_TOKEN
: The secret token used to authenticate your API requests. Also found in the Sinch Customer Dashboard. Treat this like a password.SINCH_NUMBER
: The MMS-capable phone number associated with your Sinch account that will be used as the sender ('From' number). It must be in E.164 format (e.g.,+12125551234
).SINCH_API_BASE_URL
: The base URL for the Sinch Messaging API specific to your account's region. Ensure this matches your account setup.
Important: Add .env.local
to your .gitignore
file (create one if it doesn't exist) to prevent accidentally committing your credentials. create-next-app
usually does this automatically.
# .gitignore (ensure this line exists)
.env*.local
Project Structure (Relevant Files):
sinch-mms-nextjs/
├── pages/
│ ├── api/
│ │ └── send-mms.js # Our backend API route logic
│ └── index.js # Our frontend page component
├── public/
├── styles/
├── .env.local # Stores secrets (DO NOT COMMIT)
├── .gitignore
├── next.config.js
├── package.json
└── README.md
2. Implementing Core Functionality: The API Route
Now, let's create the backend logic within a Next.js API route. This route will receive requests from our frontend, construct the payload for Sinch, and make 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.' });
}
// 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
};
// 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 if it contains sensitive info (recipient numbers, message body)
// Consider logging 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 if necessary (though 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) and media URL format. - Payload Construction: Creates the JSON
payload
for the Sinch/xms/v1/.../batches
endpoint.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
Authorization
andContent-Type
headers. - 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 Created
status from Sinch. Returns a200 OK
response 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 (e.g., forwarding Sinch's error code) and messages to the frontend.
3. Building the Frontend Interface
Now, let's create a simple React component on a Next.js page to collect the user input and trigger our 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
useState
for 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.
handleSubmit
Function:- Prevents default submission.
- Sets loading state.
- Performs basic frontend validation.
- Uses
fetch
to POST data to/api/send-mms
. - Handles the JSON response from the API route, updating
statusMessage
for success (showing Batch ID) or failure (showing error details from the API). - Catches network errors during
fetch
. - Resets loading state in the
finally
block.
- Feedback: Displays
statusMessage
below the form, styled for success/error. Disables the button during loading. - Head Component: Includes corrected
meta
andlink
tags with standard double quotes.
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 the section for SMS or Messaging APIs.
- Find the REST API configuration area.
- Locate your Service plan ID and API token. You might need to 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 that has MMS capability enabled. Ensure it's listed in the E.164 format (e.g.,
+1xxxxxxxxxx
). - Copy this number.
4.3 Secure Storage:
- As configured in Step 1.4, place these credentials (
SINCH_SERVICE_PLAN_ID
,SINCH_API_TOKEN
,SINCH_NUMBER
) into your.env.local
file. - Never commit
.env.local
or any file containing your API token to version control (like Git). Ensure.env*.local
is in your.gitignore
file. - When deploying, use your hosting provider's mechanism for setting environment variables securely.
5. Error Handling and Logging
We've implemented basic error handling in both the API route and the frontend.
API Route (pages/api/send-mms.js
):
- Input Validation: Returns
400 Bad Request
for missing/invalid inputs. - Method Check: Returns
405 Method Not Allowed
for non-POST requests. - Sinch API Errors: Catches errors from the
axios.post
call, logs details server-side (console.error
), and returns specific status codes/messages to the frontend. - Logging: Uses
console.log
andconsole.error
. Important: In a production environment, replaceconsole.log
/console.error
with 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
fetch
call. - 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. (Not implemented here).
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, you would 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-mms
to prevent abuse. Use libraries likerate-limiter-flexible
or platform features. - Authentication/Authorization: (Advanced) Protect the API route using authentication (e.g., NextAuth.js, Clerk) to ensure only authorized users can send MMS.
- Preventing SSRF Nuance: The
mediaUrl
is fetched by Sinch. While basic URL validation is in place, the main security consideration isn't typically Server-Side Request Forgery (SSRF) against your own server via Sinch's fetch. The risk revolves around ensuring your validation prevents users from submitting malicious or invalid URLs intended for Sinch to fetch (e.g., URLs that abuse Sinch infrastructure, or potentially URLs designed to reveal information if Sinch's fetcher had vulnerabilities, though this relies on Sinch's security). Robust URL validation on your end is key.
8. Handling Special Cases (MMS Specific)
- E.164 Format: Strictly required by Sinch.
- Public Media URL: Must be publicly accessible without authentication for Sinch to fetch.
- Media File Types/Size: Check Sinch documentation for supported types (JPEG, PNG, GIF, MP4 etc.) and size limits. Validate if necessary.
- Character Limits: MMS allows more text than SMS, but practical limits exist.
- Regional Restrictions: MMS primarily works in the US/Canada. Sending elsewhere may fail.
- Carrier Compliance: Adhere to content guidelines (e.g., CTIA in the US).
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/health
endpoint. - Performance Metrics: Use APM tools (Datadog, New Relic, Sentry) to monitor API route response times, 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 Unauthorized
from Sinch: CheckSINCH_API_TOKEN
,SINCH_SERVICE_PLAN_ID
,SINCH_API_BASE_URL
in environment variables. EnsureBearer
prefix is correct. - Error:
400 Bad Request
from Sinch (e.g.,INVALID_PARAMETER_VALUE
): Check payload format (E.164 numbers, publicmediaUrl
), required fields. Examinedetails
in the error response. Check against Sinch XMS API docs. - Error:
403 Forbidden
(MMS related): VerifySINCH_NUMBER
is MMS-enabled, account supports MMS, destination is supported. - Error: Media Not Displaying: Ensure
mediaUrl
is 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.
12. Deployment and CI/CD
12.1 Deployment Platforms: Vercel, 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.local
is gitignored). - Connect Git repo to Vercel.
- Configure Project Name, 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
/fetch
to simulate Sinch responses (success/errors) and assert handler logic.// 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
fetch
or 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.local
created with correct Sinch credentials/number (and not committed to Git).
-
-
-
.env.local
listed in.gitignore
.
-
-
- API route
pages/api/send-mms.js
implemented.
- API route
-
- Frontend page
pages/index.js
implemented.
- 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.