code examples
code examples
Send MMS with Sinch API in Next.js 15 and NextAuth v5 | Complete Guide
Learn how to send MMS messages using Sinch API with Next.js 15 App Router and NextAuth v5 authentication. Includes file size limits, Content-Length requirements, and TypeScript code examples.
Sinch MMS with Next.js 15 and NextAuth v5: Send Multimedia Messages
Learn how to send MMS messages using the Sinch API with Next.js 15 and NextAuth v5 authentication. This comprehensive tutorial shows you how to build a production-ready MMS messaging application using the App Router, TypeScript, and server-side authentication. Send images, videos, and audio files to mobile numbers with proper security, error handling, and file size validation.
This guide covers implementing NextAuth v5 authentication (beta v5.0.0-beta.25+), configuring Sinch MMS API credentials, and handling critical MMS requirements including mandatory Content-Length headers and 500 KB file size limits for optimal cross-carrier delivery.
Setting Up Next.js 15 with NextAuth v5 for Sinch MMS Integration
Let's initialize our Next.js 15 project with TypeScript and install the necessary dependencies.
-
Create Next.js 15 Project: Open your terminal or command prompt and create a new Next.js 15 application with TypeScript.
bashnpx create-next-app@latest sinch-mms-nextauth --typescript --app --eslint cd sinch-mms-nextauthSelect the following options when prompted:
- Would you like to use TypeScript? › Yes
- Would you like to use ESLint? › Yes
- Would you like to use Tailwind CSS? › Yes (optional, but recommended)
- Would you like to use
src/directory? › Yes (recommended) - Would you like to use App Router? › Yes (required)
- Would you like to customize the default import alias? › No
-
Install Dependencies: Install
axiosfor HTTP requests andnext-auth@betafor authentication. NextAuth v5 beta is required for Next.js 15 compatibility due to React 19 support and App Router enhancements introduced in Next.js 15.bashnpm install axios next-auth@beta # or using pnpm (recommended for Next.js 15): pnpm add axios next-auth@betaWhy NextAuth v5 beta? NextAuth v4 uses the Pages Router architecture and is incompatible with Next.js 15's enhanced App Router. NextAuth v5 (Auth.js) provides native App Router support, improved TypeScript types, and simplified configuration with the
auth()helper function for server components. -
Create Project Structure: Set up the Next.js 15 App Router structure:
textsinch-mms-nextauth/ ├── src/ │ ├── app/ │ │ ├── api/ │ │ │ ├── auth/[...nextauth]/ │ │ │ │ └── route.ts # NextAuth API route handlers │ │ │ └── send-mms/ │ │ │ └── route.ts # MMS sending API endpoint │ │ ├── page.tsx # Home page with send MMS form │ │ └── layout.tsx # Root layout with SessionProvider │ ├── lib/ │ │ ├── auth.ts # NextAuth v5 configuration │ │ └── sinch.ts # Sinch MMS service functions │ ├── types/ │ │ └── mms.ts # TypeScript types for MMS ├── auth.ts # NextAuth v5 config (root level) ├── middleware.ts # Route protection middleware ├── .env.local # Environment variables (NEVER COMMIT) ├── .gitignore # Includes .env.local by default ├── next.config.mjs ├── package.json └── tsconfig.json -
Configure
.gitignore: Ensure your.gitignoreincludes all Next.js build artifacts and sensitive files:text# .gitignore # dependencies node_modules/ # next.js .next/ out/ build/ dist/ # env files .env .env.local .env*.local # logs npm-debug.log* yarn-debug.log* yarn-error.log* # testing coverage/ # misc .DS_Store *.pem # vercel .vercel -
Set up Environment Variables (
.env.local): Create a file named.env.localin the project root. NextAuth v5 requiresAUTH_SECRETand optionallyAUTH_URLfor production deployments.dotenv# .env.local - DO NOT COMMIT TO GIT # NextAuth v5 Configuration AUTH_SECRET=your_generated_secret_here # Generate with: openssl rand -hex 32 # AUTH_URL=http://localhost:3000 # Optional: defaults to localhost:3000 in dev # Sinch Credentials - Obtain from your Sinch Dashboard # Path: SMS > APIs > Your Service Plan ID > REST API Configuration SINCH_SERVICE_PLAN_ID=YOUR_SERVICE_PLAN_OR_CAMPAIGN_ID SINCH_API_TOKEN=YOUR_API_TOKEN # Your provisioned Sinch number (international format, e.g., +1...) SINCH_FROM_NUMBER=+1xxxxxxxxxx # Sinch API Base URL for MMS # Verify your specific region and endpoint in Sinch Dashboard SINCH_API_BASE_URL=https://us.mms.api.sinch.com/v1/{your-path} # URL of the media file(s) you want to send # Ensure these are publicly accessible and return Content-Length header MEDIA_IMAGE_URL=https://your-cdn.com/path/to/image.jpg MEDIA_VIDEO_URL=https://your-cdn.com/path/to/video.mp4Key environment variables:
AUTH_SECRET: Required by NextAuth v5 to sign JWT tokens. Generate withopenssl rand -hex 32. Minimum 32 characters recommended.SINCH_SERVICE_PLAN_ID: Found in Sinch Dashboard under SMS > APIs. Identifies your messaging configuration.SINCH_API_TOKEN: Your Bearer token for API authentication from Sinch Dashboard.SINCH_FROM_NUMBER: Must be MMS-enabled and in E.164 international format (e.g.,+17745559144).SINCH_API_BASE_URL: Region-specific endpoint. Verify correct URL for your account – using wrong endpoint causes 404 errors.
Configuring NextAuth v5 Authentication in Next.js 15
NextAuth v5 uses a simplified configuration pattern. Create the authentication configuration at the project root.
-
Create
auth.tsConfiguration: Createauth.tsin the project root directory:typescript// auth.ts import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [ Credentials({ name: "Credentials", credentials: { username: { label: "Username", type: "text", placeholder: "admin" }, password: { label: "Password", type: "password" } }, async authorize(credentials) { // Replace with your own authentication logic // This is a simple demo - use proper password hashing in production if ( credentials.username === "admin" && credentials.password === "password" ) { return { id: "1", name: "Admin User", email: "admin@example.com" }; } return null; } }) ], session: { strategy: "jwt" }, pages: { signIn: "/api/auth/signin" }, callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; } return token; }, async session({ session, token }) { if (session.user) { session.user.id = token.id as string; } return session; } } });Note: This uses simple Credentials provider for demonstration. In production, use OAuth providers (Google, GitHub) or database-backed authentication with proper password hashing (bcrypt).
-
Create NextAuth API Route: Create the catch-all NextAuth route handler at
src/app/api/auth/[...nextauth]/route.ts:typescript// src/app/api/auth/[...nextauth]/route.ts import { handlers } from "@/auth"; export const { GET, POST } = handlers;This single file handles all NextAuth endpoints:
/api/auth/signin,/api/auth/signout,/api/auth/session, etc. -
Create Middleware for Route Protection: Create
middleware.tsin the project root to protect API routes:typescript// middleware.ts import { auth } from "./auth"; export default auth((req) => { // req.auth contains the session const isLoggedIn = !!req.auth; const isApiRoute = req.nextUrl.pathname.startsWith("/api/send-mms"); if (isApiRoute && !isLoggedIn) { return Response.json( { error: "Authentication required" }, { status: 401 } ); } }); export const config = { matcher: ["/api/send-mms/:path*"] };This middleware automatically protects the
/api/send-mmsendpoint, requiring authentication before processing MMS requests.
Implementing MMS Message Sending with Sinch API
Now implement the TypeScript service layer and API route for authenticated MMS sending.
-
Define TypeScript Types: Create
src/types/mms.tsfor type safety:typescript// src/types/mms.ts export interface MMSSlide { image?: { url: string }; video?: { url: string }; audio?: { url: string }; pdf?: { url: string }; contact?: { url: string }; calendar?: { url: string }; 'message-text'?: string; } export interface SendMMSParams { to: string; from: string; subject?: string; slides: MMSSlide[]; fallbackText?: string; disableFallbackSms?: boolean; disableFallbackSmsLink?: boolean; clientReference?: string; } export interface SinchMMSResponse { status: 'success' | 'failure'; to?: string; 'tracking-id'?: string; 'status-details'?: string; 'error-code'?: string; 'error-info'?: string; } -
Create Sinch MMS Service (
src/lib/sinch.ts): This module handles all Sinch API communication with proper error handling:typescript// src/lib/sinch.ts import axios, { AxiosError } from 'axios'; import type { SendMMSParams, SinchMMSResponse, MMSSlide } from '@/types/mms'; const servicePlanId = process.env.SINCH_SERVICE_PLAN_ID; const apiToken = process.env.SINCH_API_TOKEN; const sinchApiBaseUrl = process.env.SINCH_API_BASE_URL; /** * Validates MMS slides according to Sinch API constraints * @see https://developers.sinch.com/docs/mms/api-reference/sendmms */ function validateSlides(slides: MMSSlide[]): void { if (!slides || slides.length === 0) { throw new Error('At least one slide is required'); } // Maximum 8 slides per MMS per Sinch documentation if (slides.length > 8) { throw new Error('Maximum of 8 slides allowed per MMS message'); } slides.forEach((slide, index) => { const mediaTypes = ['image', 'video', 'audio', 'pdf', 'contact', 'calendar']; const presentMedia = mediaTypes.filter(type => type in slide); // Each slide must have text and/or ONE media element if (presentMedia.length === 0 && !slide['message-text']) { throw new Error(`Slide ${index + 1} must contain media or message-text`); } if (presentMedia.length > 1) { throw new Error(`Slide ${index + 1} cannot contain multiple media types`); } // Validate message-text length (5000 char max per Sinch docs) if (slide['message-text'] && slide['message-text'].length > 5000) { throw new Error(`Slide ${index + 1} text exceeds 5000 character limit`); } }); } /** * Sends an MMS message using the Sinch API * @see https://developers.sinch.com/docs/mms/api-reference/sendmms */ export async function sendMmsMessage( params: SendMMSParams ): Promise<SinchMMSResponse> { // Validate environment configuration if (!servicePlanId || !apiToken || !sinchApiBaseUrl) { throw new Error( 'Sinch API credentials not configured. Check SINCH_SERVICE_PLAN_ID, ' + 'SINCH_API_TOKEN, and SINCH_API_BASE_URL in .env.local' ); } // Validate required parameters const { to, from, slides, fallbackText, disableFallbackSms = false } = params; if (!to || !from) { throw new Error('Required parameters missing: to and from are required'); } // Validate E.164 format for phone numbers const e164Regex = /^\+[1-9]\d{1,14}$/; if (!e164Regex.test(to)) { throw new Error(`Invalid recipient number format. Use E.164 format (e.g., +17745559144)`); } if (!e164Regex.test(from)) { throw new Error(`Invalid sender number format. Use E.164 format (e.g., +17745559144)`); } // Validate slides validateSlides(slides); // Fallback SMS validation per Sinch requirements if (!disableFallbackSms && !fallbackText) { console.warn( 'Warning: Fallback SMS is enabled but fallback-sms-text is not provided. ' + 'This may cause delivery failures.' ); } // Validate subject length (80 char max, 40 recommended per Sinch docs) if (params.subject && params.subject.length > 80) { throw new Error('Subject exceeds 80 character maximum'); } // Construct Sinch API payload per official schema const payload = { action: 'sendmms', 'service-id': servicePlanId, to, from, slide: slides, ...(params.subject && { 'message-subject': params.subject }), ...(fallbackText && { 'fallback-sms-text': fallbackText }), 'disable-fallback-sms': disableFallbackSms, 'disable-fallback-sms-link': params.disableFallbackSmsLink || false, ...(params.clientReference && { 'client-reference': params.clientReference }), 'cache-content': true }; const config = { headers: { 'Content-Type': 'application/json; charset=utf-8', 'Authorization': `Bearer ${apiToken}` } }; console.log(`[Sinch MMS] Sending to ${to} via endpoint: ${sinchApiBaseUrl}`); try { const response = await axios.post<SinchMMSResponse>( sinchApiBaseUrl, payload, config ); console.log('[Sinch MMS] Response status:', response.status); console.log('[Sinch MMS] Response data:', response.data); // Sinch returns 200 OK with status field indicating success/failure if (response.status >= 200 && response.status < 300 && response.data) { if (response.data.status === 'success') { console.log( `[Sinch MMS] Successfully queued for delivery to ${to}. ` + `Tracking ID: ${response.data['tracking-id'] || 'N/A'}` ); return response.data; } else { // API returned 2xx but with failure status const errorMsg = response.data['error-info'] || 'Unknown error'; const errorCode = response.data['error-code'] || 'UNKNOWN'; throw new Error(`Sinch API error [${errorCode}]: ${errorMsg}`); } } else { throw new Error(`Unexpected response status: ${response.status}`); } } catch (error) { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError<SinchMMSResponse>; if (axiosError.response) { // Server responded with error status const status = axiosError.response.status; const data = axiosError.response.data; console.error('[Sinch MMS] API Error:', status, data); // Common Sinch error codes if (status === 400) { throw new Error( `Bad Request: ${data?.['error-info'] || 'Invalid parameters or missing Content-Length header from media URL'}` ); } else if (status === 401) { throw new Error('Unauthorized: Invalid API token'); } else if (status === 404) { throw new Error('Not Found: Check SINCH_API_BASE_URL is correct for your account'); } else { throw new Error( `Sinch API Error ${status}: ${data?.['error-info'] || 'Unknown error'}` ); } } else if (axiosError.request) { // Request made but no response received console.error('[Sinch MMS] No response received:', axiosError.message); throw new Error('No response from Sinch API. Check network connectivity and API endpoint.'); } else { // Request setup failed console.error('[Sinch MMS] Request setup error:', axiosError.message); throw new Error(`Request configuration error: ${axiosError.message}`); } } // Non-Axios error throw error; } } -
Create Protected API Route (
src/app/api/send-mms/route.ts): This Next.js 15 API route handles authenticated MMS requests:typescript// src/app/api/send-mms/route.ts import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/auth'; import { sendMmsMessage } from '@/lib/sinch'; import type { SendMMSParams } from '@/types/mms'; /** * POST /api/send-mms - Send authenticated MMS via Sinch * Requires NextAuth v5 session */ export async function POST(req: NextRequest) { try { // Verify authentication using NextAuth v5 auth() helper const session = await auth(); if (!session || !session.user) { return NextResponse.json( { error: 'Authentication required' }, { status: 401 } ); } // Parse and validate request body const body = await req.json(); const { to, subject, slides, fallbackText } = body; // Validate required fields if (!to || !slides || !Array.isArray(slides) || slides.length === 0) { return NextResponse.json( { error: 'Missing required fields: to, slides (array)' }, { status: 400 } ); } // Get sender number from environment const from = process.env.SINCH_FROM_NUMBER; if (!from) { return NextResponse.json( { error: 'Server configuration error: SINCH_FROM_NUMBER not set' }, { status: 500 } ); } // Prepare MMS parameters const mmsParams: SendMMSParams = { to, from, subject, slides, fallbackText, clientReference: `nextjs-${session.user.id}-${Date.now()}` }; // Send MMS via Sinch const result = await sendMmsMessage(mmsParams); // Return 202 Accepted - MMS queued for delivery return NextResponse.json( { success: true, message: 'MMS accepted for delivery', trackingId: result['tracking-id'], details: result }, { status: 202 } ); } catch (error) { console.error('[API] MMS send error:', error); // Return user-friendly error without exposing internals const errorMessage = error instanceof Error ? error.message : 'Failed to send MMS'; return NextResponse.json( { error: errorMessage, success: false }, { status: 500 } ); } } -
Create Client Component with Form (
src/app/page.tsx): Build the authenticated UI for sending MMS:typescript// src/app/page.tsx 'use client'; import { useState } from 'react'; import { signIn, signOut, useSession } from 'next-auth/react'; export default function MMSPage() { const { data: session, status } = useSession(); const [recipient, setRecipient] = useState(''); const [subject, setSubject] = useState(''); const [messageText, setMessageText] = useState(''); const [mediaUrl, setMediaUrl] = useState(''); const [sending, setSending] = useState(false); const [result, setResult] = useState<any>(null); const handleSendMMS = async (e: React.FormEvent) => { e.preventDefault(); setSending(true); setResult(null); try { const response = await fetch('/api/send-mms', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ to: recipient, subject, slides: [ { 'message-text': messageText, ...(mediaUrl && { image: { url: mediaUrl } }) } ], fallbackText: `View your message: ${messageText}` }) }); const data = await response.json(); setResult(data); if (response.ok) { alert('MMS sent successfully! Tracking ID: ' + data.trackingId); } else { alert('Error: ' + data.error); } } catch (error) { console.error('Send error:', error); setResult({ error: 'Network error occurred' }); } finally { setSending(false); } }; if (status === 'loading') { return <div className="p-8">Loading...</div>; } if (!session) { return ( <div className="flex flex-col items-center justify-center min-h-screen"> <h1 className="text-2xl font-bold mb-4">Sinch MMS with NextAuth v5</h1> <p className="mb-4">Sign in to send MMS messages</p> <button onClick={() => signIn()} className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700" > Sign In </button> </div> ); } return ( <div className="max-w-2xl mx-auto p-8"> <div className="flex justify-between items-center mb-6"> <h1 className="text-2xl font-bold">Send MMS via Sinch</h1> <button onClick={() => signOut()} className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700" > Sign Out </button> </div> <p className="mb-6 text-gray-600"> Logged in as: {session.user?.email || session.user?.name} </p> <form onSubmit={handleSendMMS} className="space-y-4"> <div> <label className="block mb-1 font-medium"> Recipient Number (E.164 format) </label> <input type="tel" value={recipient} onChange={(e) => setRecipient(e.target.value)} placeholder="+17745559144" required className="w-full px-4 py-2 border rounded focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block mb-1 font-medium"> Subject (optional, max 80 chars) </label> <input type="text" value={subject} onChange={(e) => setSubject(e.target.value)} maxLength={80} placeholder="Hello from Next.js" className="w-full px-4 py-2 border rounded focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block mb-1 font-medium">Message Text</label> <textarea value={messageText} onChange={(e) => setMessageText(e.target.value)} placeholder="Your message here..." required maxLength={5000} rows={4} className="w-full px-4 py-2 border rounded focus:ring-2 focus:ring-blue-500" /> </div> <div> <label className="block mb-1 font-medium"> Media URL (optional, must return Content-Length header) </label> <input type="url" value={mediaUrl} onChange={(e) => setMediaUrl(e.target.value)} placeholder="https://example.com/image.jpg" className="w-full px-4 py-2 border rounded focus:ring-2 focus:ring-blue-500" /> <p className="text-sm text-gray-500 mt-1"> Keep files under 500 KB for best delivery. Must be publicly accessible. </p> </div> <button type="submit" disabled={sending} className="w-full px-6 py-3 bg-blue-600 text-white font-semibold rounded hover:bg-blue-700 disabled:bg-gray-400" > {sending ? 'Sending...' : 'Send MMS'} </button> </form> {result && ( <div className={`mt-6 p-4 rounded ${result.success ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'}`}> <h3 className="font-bold mb-2"> {result.success ? 'Success' : 'Error'} </h3> <pre className="text-sm overflow-auto"> {JSON.stringify(result, null, 2)} </pre> </div> )} </div> ); } -
Create Root Layout with SessionProvider (
src/app/layout.tsx): Wrap the application with NextAuth session context:typescript// src/app/layout.tsx import './globals.css'; import { SessionProvider } from 'next-auth/react'; export const metadata = { title: 'Sinch MMS with NextAuth v5', description: 'Send MMS with Sinch API, Next.js 15, and NextAuth v5', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <SessionProvider> {children} </SessionProvider> </body> </html> ); }
Configuring Sinch MMS API Credentials and Regional Endpoints
This section covers the critical configuration aspects for Sinch MMS integration.
-
API Credentials:
SINCH_SERVICE_PLAN_ID/ Campaign ID: Identifies your specific Sinch messaging configuration.SINCH_API_TOKEN: Your secret key for authenticating requests.- Location: Sinch Customer Dashboard → SMS → APIs → Select your Service Plan ID → REST API Configuration. Click "Show" to reveal the API Token.
- Security: Store in
.env.localand ensure.env.localis in.gitignore. In production, use platform secrets (Vercel Environment Variables, AWS Secrets Manager, etc.).
-
Sender Number (
SINCH_FROM_NUMBER):- Format: Must be in international E.164 format (e.g.,
+17745559144,+442071234567). - Capability: Must be an MMS-enabled number provisioned on your Sinch account and associated with the Service Plan ID.
- Location: View provisioned numbers in Sinch Dashboard under "Numbers" or within your Service Plan configuration.
- Format: Must be in international E.164 format (e.g.,
-
API Endpoint (
SINCH_API_BASE_URL):- Region: Sinch operates in multiple regions (us, eu, etc.). The base URL is region-specific.
- Path: The exact path varies by account type and API version. Common patterns include
/v1/submissionsor/v1/projects/{PROJECT_ID}/messages. - Verification: CRITICAL – Double-check the correct API endpoint URL in your Sinch Dashboard or API documentation for your specific account. Using wrong endpoint causes 404 errors.
-
Media URLs:
- Accessibility: Must be publicly reachable via HTTP GET from Sinch's servers.
Content-LengthHeader: MANDATORY requirement. The server MUST returnContent-Lengthheader. Failure causes 400 Bad Request. CDNs usingTransfer-Encoding: chunkedwithoutContent-Lengthwill fail.Content-TypeHeader: Should return valid MIME type (e.g.,image/jpeg,video/mp4). While Sinch may infer from extensions, proper headers are best practice.application/octet-streammay be rejected if MIME type cannot be determined.- Testing: Use
curl -I https://your-cdn.com/image.jpgto verify bothContent-LengthandContent-Typeheaders are present.
Error Handling and Retry Logic for Sinch MMS API
Robust applications need comprehensive error handling for API failures and network issues.
-
Error Handling Strategy:
- The
sendMmsMessagefunction uses try-catch with Axios error differentiation. - Distinguishes between API errors (4xx/5xx), network errors (no response), and request setup errors.
- Logs detailed internal errors while returning user-friendly messages in API responses.
- Common errors:
- 400 Bad Request: Invalid parameters, missing Content-Length header from media URL, or malformed JSON.
- 401 Unauthorized: Invalid
SINCH_API_TOKEN. - 404 Not Found: Incorrect
SINCH_API_BASE_URLor Service Plan ID. - 500/502/503: Temporary Sinch API issues (retryable).
- The
-
Client-Side Error Display: The Next.js page component displays error responses to users without exposing sensitive internal details. Server logs contain full error traces for debugging.
-
Logging:
- Currently uses
console.logandconsole.errorwith[Sinch MMS]prefix for easy filtering. - Production Recommendations:
- Use structured logging libraries: Pino for performance, Winston for flexibility.
- Send logs to centralized services: Vercel Analytics, Datadog, Sentry.
- Include request IDs, user IDs, and timestamps for correlation.
- Mask sensitive data (phone numbers, API tokens) in logs.
- Currently uses
-
Retry Mechanisms:
- Sinch handles delivery retries after accepting the MMS request (returns tracking ID immediately).
- Your application should retry the initial API call for transient failures (5xx errors, network timeouts).
- Recommended: Use axios-retry for automatic retries with exponential backoff:
typescript// Add to src/lib/sinch.ts import axiosRetry from 'axios-retry'; // Configure retry logic axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay, retryCondition: (error) => { // Retry on network errors or 5xx server errors return axiosRetry.isNetworkOrIdempotentRequestError(error) || (error.response?.status ? error.response.status >= 500 : false); }, onRetry: (retryCount, error, requestConfig) => { console.log(`[Sinch MMS] Retry attempt ${retryCount} for ${requestConfig.url}`); } });
Security Features
Security is paramount when handling API keys and user data in production applications.
-
API Key Security:
- NEVER hardcode API keys (
SINCH_API_TOKEN,AUTH_SECRET) in source code. - Use
.env.localfor local development (excluded from git via.gitignore). - Use platform-specific secrets in production:
- Vercel: Environment Variables in project settings
- AWS: Secrets Manager or Parameter Store
- Docker: Docker secrets or encrypted environment files
- Rotate API keys periodically (quarterly recommended).
- Restrict access to production environment variables to essential team members.
- NEVER hardcode API keys (
-
NextAuth v5 Session Security:
- Sessions use JWT strategy (stateless, no database required).
AUTH_SECRETsigns JWTs – must be cryptographically secure (minimum 32 bytes).- JWTs are HTTP-only cookies by default, preventing XSS attacks.
- Auth.js provides CSRF protection automatically for form submissions.
- For database sessions with longer expiry, add
@auth/prisma-adapterand configure session storage.
-
Input Validation:
- API route validates all incoming request data:
- Phone numbers checked against E.164 regex pattern.
- Slides validated for structure, count (max 8), and media type constraints.
- Subject limited to 80 characters, message text to 5000 characters.
- Consider Zod for TypeScript-first schema validation:
typescriptimport { z } from 'zod'; const MMSRequestSchema = z.object({ to: z.string().regex(/^\+[1-9]\d{1,14}$/, 'Invalid E.164 format'), subject: z.string().max(80).optional(), slides: z.array(z.object({ 'message-text': z.string().max(5000).optional(), image: z.object({ url: z.string().url() }).optional(), // ... other media types })).min(1).max(8), fallbackText: z.string().optional() }); - API route validates all incoming request data:
-
Rate Limiting:
- Implement rate limiting on API routes to prevent abuse.
- For Next.js on Vercel, use Vercel Edge Config rate limiting or Upstash Redis.
- Alternative: @vercel/edge-rate-limit package for middleware-based limiting.
- Also respect Sinch API rate limits (consult documentation for your specific plan).
-
HTTPS:
- All communication with Sinch API uses HTTPS (enforced by
axiosforhttps://URLs). - Host media on HTTPS CDNs (Cloudflare, AWS CloudFront, Vercel Edge).
- Next.js on Vercel/Netlify automatically serves over HTTPS.
- All communication with Sinch API uses HTTPS (enforced by
-
CORS and API Route Security:
- Next.js API routes are server-side only (not exposed via CORS by default).
- If exposing to external clients, add explicit CORS headers with origin whitelist.
- Middleware authentication ensures only authenticated users access MMS endpoints.
Handling Special Cases
Real-world MMS delivery involves various edge cases and constraints.
-
International Number Formatting: Always use E.164 international format for
toandfromnumbers (e.g.,+17745559144,+447123456789). No dialing prefixes (00,001), no punctuation, no spaces. Include country code. Validate with regex:/^\+[1-9]\d{1,14}$/. -
Fallback SMS:
- Parameters:
fallback-sms-text,disable-fallback-sms,disable-fallback-sms-link. - If
disable-fallback-smsisfalse(default),fallback-sms-textis required. - Sinch automatically includes a hosted content link in fallback SMS (unless
disable-fallback-sms-linkistrue). - Link expires after 1 year by default (configurable via
fallback-sms-link-expiration). - Provide meaningful fallback text: "We sent you a multimedia message. View it here: [link]"
- Parameters:
-
Content Constraints (per Sinch MMS documentation):
Constraint Limit Source Slides per MMS Maximum 8 Sinch sendmms API Media types per slide 1 only (image OR video OR audio OR pdf OR contact OR calendar) Sinch sendmms API Message text per slide Maximum 5,000 characters Sinch sendmms API Subject line Maximum 80 characters (40 recommended for compatibility) Sinch sendmms API Static image file size (no transcoding) Under 500 KB (max 740 KB) Sinch Best Practices Static image file size (with transcoding) Under 1 MB Sinch Best Practices Video file size (no transcoding) Under 500 KB (max 740 KB) Sinch Best Practices Video file size (with transcoding) Under 10 MB Sinch Best Practices Recommended image dimensions 1080×1920 px (9:16 aspect ratio) Sinch Best Practices MMS expiry 3 days default (configurable via mms-expiry-timestamp)Sinch sendmms API - Cannot mix: Image/audio/video with contact/calendar/pdf on same slide.
- Cannot combine: Multiple files of same MIME type on one slide.
- Each slide requires either
message-textor one media element (or both).
-
Media Hosting and Content-Length: The Content-Length header requirement is the most common cause of MMS failures. Test URLs before sending:
bashcurl -I https://your-cdn.com/image.jpgVerify response includes
Content-Length: 245678(example). If missing or showsTransfer-Encoding: chunkedwithoutContent-Length, configure your CDN or origin server to include the header.CDN Configuration:
- Cloudflare: Disable "Chunked Encoding" in Speed settings, or use
cf-cache-status: HITcached assets (includes Content-Length). - AWS CloudFront: Ensure origin response includes
Content-Length. CloudFront passes through the header. - Self-hosted: Configure Nginx/Apache to always send
Content-Lengthfor static files.
- Cloudflare: Disable "Chunked Encoding" in Speed settings, or use
-
Base64 Encoding Overhead: MMS messages are delivered in Base64 encoding. Estimate final size:
(original_size × 1.37) + 814 bytesfor headers. A 400 KB image becomes ~550 KB after encoding. Keep source files under 500 KB for reliable delivery.
Frequently Asked Questions
What Are the File Size Limits for Sinch MMS Messages?
Sinch recommends keeping files under 500 KB for reliable delivery across all carriers without transcoding. With transcoding enabled, you can send up to 1 MB for images and 10 MB for videos. However, after Base64 encoding (1.37× multiplier + 814 bytes overhead), a 500 KB file becomes ~685 KB delivered. For large subscriber bases (100K+ recipients), keep files under 300 KB to avoid database batch delivery delays.
Is NextAuth v5 Required to Send MMS with Sinch in Next.js?
No, authentication is optional. You can send MMS without NextAuth by removing the middleware and auth() checks. However, for production applications where you need to control access, NextAuth v5 (Auth.js) provides secure session management with Next.js 15 App Router. NextAuth v5 beta (v5.0.0-beta.25+) is required for Next.js 15 due to React 19 and App Router architectural changes.
Why Is the Content-Length Header Required for Sinch MMS?
The Content-Length header is mandatory for all MMS media URLs. Sinch's servers must know the file size before fetching content. URLs without this header fail with 400 Bad Request errors. CDNs using Transfer-Encoding: chunked without Content-Length cause issues. Test with curl -I <url> to verify the header exists. If using Cloudflare, ensure chunked encoding is disabled or use cached assets.
How Many Slides Can a Sinch MMS Message Include?
Sinch MMS supports up to 8 slides per message. Each slide can contain message text (max 5,000 characters) and/or one media element. Supported media types: image, video, audio, PDF, vCard, or iCalendar. You cannot mix multiple media types on the same slide (e.g., image + video together is invalid). Each slide must have at least text or media.
What Media Types and File Formats Does Sinch MMS Support?
Sinch MMS supports: image (JPEG, PNG, GIF), video (MP4, 3GP), audio (MP3, AMR), PDF, vCard (.vcf contact), and iCalendar (.ics calendar). Each media type has file size limits – images/videos under 500 KB for best delivery without transcoding, up to 1 MB with transcoding. Audio files may support up to 600 KB depending on carrier. Use proper MIME types (image/jpeg, video/mp4, etc.) and file extensions.
Does Sinch MMS API Work with Next.js 14 and Next.js 15?
The Sinch API integration works with both Next.js 14 and 15 – the Axios-based HTTP calls are identical. The main difference is NextAuth configuration: NextAuth v4 for Next.js 14, NextAuth v5 beta for Next.js 15. Next.js 15 requires NextAuth v5 due to React 19 support and App Router architectural changes. If using Next.js 14, install next-auth@latest (v4) instead of next-auth@beta.
What Happens When Sinch MMS Delivery Fails?
Sinch provides automatic SMS fallback functionality. If MMS fails (recipient device doesn't support MMS, message too large, or carrier blocks MMS), Sinch sends SMS with fallback-sms-text and a hosted content link (unless disable-fallback-sms-link is true). The link expires after 1 year by default. Control fallback with disable-fallback-sms parameter. The tracking-id from API response lets you track delivery status via webhooks or status check APIs.
How Do You Authenticate Requests to the Sinch MMS API?
Use Bearer token authentication. Include your API Token in the Authorization header: Authorization: Bearer YOUR_API_TOKEN. Obtain your Service Plan ID and API Token from Sinch Customer Dashboard under SMS > APIs > REST Configuration. Never commit credentials – store in .env.local for development, use platform secrets (Vercel Environment Variables, AWS Secrets Manager) for production.
Can You Send MMS from Next.js Serverless API Routes?
Yes. Next.js API routes run serverless by default on platforms like Vercel and Netlify. The App Router API routes (app/api/send-mms/route.ts) execute as serverless functions on-demand. Sinch API calls via Axios work perfectly in this environment. Store credentials in environment variables (automatically available in serverless functions), and the function handles MMS sending without maintaining a persistent server.
What Node.js Version Is Required for Next.js 15 with Sinch MMS?
Next.js 15 requires Node.js 18.18 or later. Use Node.js 18.18+, 20.x, or 22.x (LTS versions recommended) for optimal performance and security. The Sinch API integration works with any modern Node.js version supporting ES modules and async/await. Vercel automatically provides compatible Node.js versions for serverless deployments.
Deployment Considerations
When deploying your Next.js 15 + NextAuth v5 + Sinch MMS application to production:
-
Environment Variables:
- Vercel: Add all
.env.localvariables in Project Settings > Environment Variables. MarkAUTH_SECRETandSINCH_API_TOKENas sensitive. - Netlify: Configure in Site Settings > Environment Variables.
- Docker: Use Docker secrets or encrypted
.envfiles mounted as volumes. - Set
AUTH_URLto your production domain:https://yourdomain.com
- Vercel: Add all
-
NextAuth v5 Production Setup:
- Generate new
AUTH_SECRETfor production:openssl rand -hex 32 - Configure OAuth providers (Google, GitHub) with production callback URLs if using social login.
- Add production domain to authorized redirect URLs in OAuth provider settings.
- Generate new
-
Sinch Configuration:
- Verify
SINCH_API_BASE_URLmatches your production account region. - Ensure
SINCH_FROM_NUMBERis fully provisioned and MMS-enabled. - Test MMS sending in staging environment before production launch.
- Configure Sinch webhooks for delivery status notifications.
- Verify
-
Media Hosting:
- Host media files on CDN with
Content-Lengthheader support (Cloudflare, AWS CloudFront). - Enable HTTPS for all media URLs.
- Test URLs with
curl -Ibefore deployment. - Consider using signed URLs for private content with expiration.
- Host media files on CDN with
-
Monitoring:
Frequently Asked Questions
How to send MMS messages with Node.js?
Use the Sinch API and a Node.js library like Axios to send MMS messages. This involves setting up a Sinch account, obtaining API credentials, and constructing a request payload with recipient, sender, message content, and media URLs. The provided Node.js script offers a step-by-step implementation guide and code example to simplify the process. Refer to the "Implementing Core Functionality" section and the provided `sendMmsService.js` and `index.js` files for a detailed walkthrough and code examples.
What is the Sinch MMS API?
The Sinch MMS API is a service that allows developers to send multimedia messages programmatically. It handles the complexities of routing messages through carrier networks, supporting various media types (images, audio, video), and providing features like fallback SMS for devices that don't support MMS. You interact with the API by making HTTP requests with specific parameters, including recipient, sender, subject, and media URLs. You'll need a Sinch account and API credentials to use this service.
Why use Node.js for sending MMS?
Node.js is well-suited for sending MMS via the Sinch API because of its asynchronous, event-driven architecture, which efficiently handles I/O-heavy operations like API calls. The extensive npm ecosystem provides convenient libraries like Axios and dotenv, simplifying the development process. Furthermore, its non-blocking nature makes it ideal for handling potentially slow network interactions without impacting overall application performance.
How to set up Sinch MMS API Node.js project?
Start by creating a Node.js project, installing necessary dependencies like Axios, dotenv, and optionally Express, structuring your project files, and configuring a .gitignore file. You'll need a Sinch account with an active MMS plan, API credentials stored securely in a .env file, and a provisioned phone number capable of sending MMS. Remember to add necessary keys and values to the .env file related to API keys, sender number, and media URLs.
What is the role of Axios in Sinch MMS?
Axios is a promise-based HTTP client for Node.js. It is used to make the HTTP POST requests to the Sinch MMS API endpoint. Axios simplifies making API calls by handling request construction, data serialization, and response parsing. It's a commonly used library within the Node.js ecosystem, selected for its ease of use and efficient handling of asynchronous communication.
What are the prerequisites for Sinch MMS?
You will need Node.js and npm installed, a Sinch account with an MMS-enabled plan, Sinch API credentials (Service Plan ID and API Token), a provisioned Sinch phone number capable of sending MMS, publicly accessible URLs for your media files that return a Content-Length header, and basic familiarity with JavaScript and command-line operations.
Where to find Sinch Service Plan ID and API Token?
The Sinch Service Plan ID and API Token are found in your Sinch Customer Dashboard. Navigate to SMS > APIs > Select your Service Plan ID > REST API Configuration. Click "Show" to reveal the API Token. Copy these values and store them securely in your .env file, which should be ignored by version control.
How to structure Sinch MMS API request?
The request needs specific parameters for the Sinch 'sendmms' action, including 'action', 'service-id', 'to', 'from', 'slide', and optional parameters like 'message-subject', 'fallback-sms-text', etc. Ensure the media URLs in each slide are publicly accessible and the media server returns a Content-Length header. Verify all keys and structure against the official Sinch API documentation. This structure is essential for successful MMS delivery.
Why does Sinch MMS API require Content-Length header?
The Content-Length header is mandatory for Sinch to determine the size of the media files being sent. Without this header, Sinch cannot reliably fetch the media, leading to a 400 Bad Request error. This requirement is crucial even if your CDN uses chunked transfer encoding, so ensure your media server is configured correctly to include this header.
When should I use SMS fallback with Sinch MMS?
Fallback SMS is useful when the recipient's device doesn't support MMS or when MMS delivery fails for other reasons. By providing 'fallback-sms-text' in your API request, the recipient will receive a plain text SMS message instead, often including a link to the intended MMS content if not explicitly disabled via API parameters. This ensures the recipient gets the core message even if they can't view the rich media.
What is the correct format for Sinch phone numbers?
Always use the E.164 international format for both 'to' (recipient) and 'from' (sender) phone numbers. This format includes the plus sign (+) followed by the country code and the national subscriber number, without any spaces, hyphens, or other punctuation. For example, +1234567890. Correct formatting is essential for accurate message routing by Sinch.
How to handle Sinch MMS API errors in Node.js?
Implement error handling using try-catch blocks to catch potential exceptions during API calls. Differentiate between API errors and network issues. Log detailed error information for debugging, including HTTP status codes and error responses. Implement robust retry mechanisms with exponential backoff for transient errors and avoid exposing sensitive internal errors in your API responses.
How to implement error retries for Sinch MMS API?
Use a retry mechanism with exponential backoff to handle transient errors like network issues or temporary API unavailability. Use libraries like 'axios-retry' for robust implementations, which handle the backoff logic and retry attempts automatically. For manual implementations, ensure you manage retry counts and exponential backoff delays appropriately.
Can I send multiple media items in one Sinch MMS slide?
No, you cannot mix multiple media types (image, video, audio, etc.) within a single MMS slide. Each slide can contain text ('message-text') and/or *one* media element. If you need to send multiple media items, create separate slides for each one. Verify the allowed media types and combinations in the official Sinch documentation.
What are the security best practices for Sinch MMS API?
Never hardcode your Sinch API token directly in the code. Store it securely in environment variables, particularly a .env file for local development. Ensure .env is listed in .gitignore to prevent it from being committed to version control. Validate all incoming request data to prevent security vulnerabilities and implement rate limiting to protect your application from abuse.