How to Send MMS Messages with Sinch and NestJS: Complete Tutorial 2025
Learn how to send MMS messages using Sinch API in your NestJS application. Complete TypeScript tutorial with code examples, validation, error handling, and production deployment guide.
Learn how to send MMS messages using Sinch API in your NestJS application. This comprehensive tutorial covers everything you need to build a production-ready multimedia messaging service: initial project setup, TypeScript implementation, request validation, error handling, security best practices, and deployment.
Whether you're building notification systems, marketing campaigns, or two-way multimedia messaging features, this guide shows you how to integrate Sinch MMS capabilities into your Node.js backend. By the end, you'll have a robust NestJS service that reliably sends MMS messages with images, videos, and rich media content.
What you'll learn:
Setting up a NestJS project for Sinch MMS integration
Implementing the Sinch MMS JSON API with TypeScript
Validating MMS requests with class-validator
Handling errors and edge cases in production
Securing your API with environment variables and authentication
Deploying your MMS service with Docker
What is Sinch MMS and Why Use It with NestJS?
Sinch MMS (Multimedia Messaging Service) enables you to send rich media messages—images, videos, audio files, and documents—to mobile devices via the Sinch platform. Unlike SMS, which is limited to text, MMS allows you to deliver engaging visual content that enhances user communication.
Why NestJS for Sinch MMS Integration?
NestJS is a progressive Node.js framework that provides a solid foundation for building scalable server-side applications. Its modular architecture, built-in dependency injection, TypeScript support, and extensive tooling make it ideal for implementing reliable Sinch API integrations with proper error handling and maintainability.
Project Overview and Goals
Goal: Create a NestJS backend service that exposes an API endpoint to send MMS messages via the Sinch platform.
Problem Solved: Programmatically send rich media messages (images, videos, audio, contacts) as part of your application's workflows – notifications, marketing campaigns, or user interactions – leveraging Sinch's MMS capabilities.
Technologies You'll Use:
NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications using TypeScript. Its modular architecture, dependency injection, and built-in tooling make it ideal for structured backend development.
Sinch MMS JSON API: The specific Sinch API endpoint (sendmms action) designed for sending individual MMS messages with detailed configuration options.
TypeScript: Provides static typing for enhanced code quality, maintainability, and developer productivity.
Axios (via @nestjs/axios): A promise-based HTTP client for making requests to the Sinch API.
@nestjs/config: Manages environment variables and application configuration securely.
class-validator & class-transformer: Provides robust request data validation and transformation.
@nestjs/swagger: Generates API documentation automatically.
Prisma (Optional): For database interaction, specifically logging message attempts and tracking IDs.
Docker: For containerizing your application for consistent deployment.
System Architecture:
graph LR
Client[Client Application / Postman] -- HTTP POST Request --> A(NestJS API Endpoint /mms/send);
A -- Calls MmsService.sendMms() --> B(MmsService);
B -- Loads Config --> C(ConfigService / .env);
B -- Makes HTTP POST --> D(Sinch MMS API);
D -- Fetches Media --> E(Media URL Host);
D -- Sends Response --> B;
B -- (Optional) Logs to DB --> F(Database / Prisma);
B -- Returns Result --> A;
A -- HTTP Response --> Client;
B -- Logs Info/Errors --> G(Logger);
style D fill:#f9f,stroke:#333,stroke-width:2px
style E fill:#ccf,stroke:#333,stroke-width:1px
style F fill:#eee,stroke:#333,stroke-width:1px
style G fill:#eee,stroke:#333,stroke-width:1px
Prerequisites:
Node.js (LTS version recommended, e.g., v18 or v20) and npm/yarn installed.
A Sinch account with MMS capabilities enabled. You need:
Your Sinch Service ID (often referred to as Project ID or Campaign ID in the context of the MMS API).
A Sinch API Token (Bearer Token).
A Sinch-provisioned phone number (Short code, Toll-Free, or 10DLC) capable of sending MMS.
Basic understanding of TypeScript, NestJS concepts, and REST APIs.
Access to a terminal or command prompt.
(Optional) Docker installed for containerization.
(Optional) A database (e.g., PostgreSQL) and Prisma CLI installed if implementing database logging.
A publicly accessible URL hosting the media files you intend to send (Sinch needs to fetch these).
Media File Size Best Practices: Keep media files under 500 KB to ensure reliable MMS delivery across all US carriers. While technical limits vary by carrier (AT&T: 1 MB, Verizon: 1.7 MB, T-Mobile: 3 MB), industry consensus recommends 300–500 KB for optimal deliverability (source: Bandwidth Support, verified January 2025). All carriers reliably handle messages up to 300 KB.
1. Set Up Your NestJS Project for Sinch MMS
Initialize a new NestJS project and install the necessary dependencies for Sinch API integration.
Create Your New NestJS Project:
Open your terminal and run the NestJS CLI command:
Bash
npx @nestjs/cli new sinch-mms-sender
cd sinch-mms-sender
Choose your preferred package manager (npm or yarn) when prompted.
Install Dependencies:
Install modules for HTTP requests, configuration management, validation, and optionally Swagger and Prisma.
class-validator & class-transformer: For validating incoming request payloads.
@nestjs/swagger & swagger-ui-express: (Optional) For generating OpenAPI documentation.
@prisma/client & prisma: (Optional) For database interactions if you choose to log messages.
Initialize Prisma (Optional):
If you plan to add database logging:
Bash
npx prisma init --datasource-provider postgresql # Or your preferred DB provider
This creates a prisma directory with a schema.prisma file and a .env file.
Configure Environment Variables:
Create or update the .env file in your project root. Never commit this file to version control. Add your Sinch credentials and other necessary configurations:
.env
# .env
# Sinch API Credentials
SINCH_API_TOKEN=YOUR_API_TOKEN_HERE
SINCH_SERVICE_ID=YOUR_SERVICE_ID_HERE
# IMPORTANT: Verify this base URL and the API endpoint path against Sinch documentation for your specific region/account.
SINCH_BASE_URL=https://mms.api.sinch.com
SINCH_FROM_NUMBER=+1xxxxxxxxxx # Your Sinch MMS-enabled number in E.164 format
# Application Port
PORT=3000
# Optional: Database URL (if using Prisma)
# Example for PostgreSQL: DATABASE_URL="postgresql://user:password@host:port/database?schema=public"
DATABASE_URL="YOUR_DATABASE_CONNECTION_STRING"
E.164 Format Requirements: E.164 is the ITU-T international standard (ITU-T Recommendation E.164) for phone number formatting. Numbers must start with +, followed by the country code (1–3 digits) and subscriber number (max 12 digits), for a maximum total of 15 digits. No spaces, parentheses, or dashes are allowed. Examples: +14151231234 (US), +442012341234 (UK). This format ensures correct international SMS/MMS routing (source: ITU-T E.164, Twilio E.164 Guide).
Why .env? Keeps sensitive credentials out of your codebase and allows different configurations per environment (development, staging, production).
Important: Always verify the SINCH_BASE_URL and the specific API endpoint path (used later in MmsService) against the official Sinch documentation for your account and region, as these can differ.
Load Configuration:
Modify src/app.module.ts to load environment variables using ConfigModule.
TypeScript
// src/app.module.tsimport { Module } from'@nestjs/common';
import { ConfigModule } from'@nestjs/config';
import { AppController } from'./app.controller';
import { AppService } from'./app.service';
import { MmsModule } from'./mms/mms.module'; // We'll create this next// import { PrismaModule } from './prisma/prisma.module'; // Optional: Uncomment if using Prisma@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // Make ConfigService available globallyenvFilePath: '.env',
}),
MmsModule,
// PrismaModule, // Optional: Uncomment if using Prisma
],
controllers: [AppController], // Keep or remove the default AppController/Serviceproviders: [AppService], // Keep or remove the default AppController/Service
})
exportclassAppModule {}
isGlobal: true: Makes ConfigService injectable in any module without needing to import ConfigModule explicitly everywhere.
Review Project Structure:
After completing the next steps, your structure will resemble:
Create a dedicated module and service to handle Sinch API interaction logic for sending MMS messages.
Generate MMS Module and Service:
Use the NestJS CLI:
Bash
nest generate module mms
nest generate service mms --flat # Use --flat to avoid creating a subdirectory
This creates src/mms/mms.module.ts and src/mms/mms.service.ts.
Define Data Transfer Objects (DTOs):
Create DTOs to represent the data structure needed to send an MMS, including validation rules. Create a src/mms/dto directory.
// src/mms/dto/mms-slide.dto.tsimport { Type } from'class-transformer';
import { IsNotEmptyObject, IsOptional, IsString, MaxLength, ValidateNested } from'class-validator';
import { ApiProperty, ApiPropertyOptional } from'@nestjs/swagger';
import { MmsSlideContentDto } from'./mms-media.dto';
exportclassMmsSlideDtoextendsMmsSlideContentDto {
@ApiPropertyOptional({ description: 'Text message for the slide', maxLength: 5000 })
@IsString()
@MaxLength(5000)
@IsOptional()
'message-text'?: string; // Note: Use quotes for property names with hyphens
}
TypeScript
// src/mms/dto/send-mms.dto.tsimport { ApiProperty, ApiPropertyOptional } from'@nestjs/swagger';
import { Type } from'class-transformer';
import {
IsArray,
IsBoolean,
IsDateString,
IsISO8601,
IsNotEmpty,
IsOptional,
IsPhoneNumber,
IsString,
MaxLength,
ValidateNested,
ArrayMinSize,
ArrayMaxSize,
} from'class-validator';
import { MmsSlideDto } from'./mms-slide.dto';
exportclassSendMmsDto {
@ApiProperty({ description: 'Destination phone number in E.164 format (e.g., +15551234567)' })
@IsPhoneNumber(null) // Use null for international format validation@IsNotEmpty()
to: string;
@ApiProperty({ description: 'Array of MMS slides (1-8 slides)', type: [MmsSlideDto] })
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(8)
@ValidateNested({ each: true })
@Type(() =>MmsSlideDto)
@IsNotEmpty()
slide: MmsSlideDto[];
@ApiPropertyOptional({ description: 'MMS subject text', maxLength: 80 })
@IsString()
@MaxLength(80)
@IsOptional()
'message-subject'?: string;
@ApiPropertyOptional({ description: 'Text for fallback SMS if MMS fails or is unsupported', maxLength: 160 })
@IsString()
@MaxLength(160) // Keep fallback SMS concise@IsOptional() // Required only if disable-fallback-sms is false (default)'fallback-sms-text'?: string;
@ApiPropertyOptional({ description: 'Set to true to disable sending fallback SMS link', default: false })
@IsBoolean()
@IsOptional()
'disable-fallback-sms-link'?: boolean = false;
@ApiPropertyOptional({ description: 'Set to true to disable sending fallback SMS entirely', default: false })
@IsBoolean()
@IsOptional()
'disable-fallback-sms'?: boolean = false;
@ApiPropertyOptional({ description: 'ISO8601 timestamp for fallback SMS link expiration (max 1 year)', format: 'date-time' })
@IsISO8601()
@IsOptional()
'fallback-sms-link-expiration'?: string;
@ApiPropertyOptional({ description: 'ISO8601 timestamp for MMS delivery expiration (max 3 days)', format: 'date-time' })
@IsISO8601()
@IsOptional()
'mms-expiry-timestamp'?: string;
@ApiPropertyOptional({ description: 'Client-specific reference ID (max 64 chars)', maxLength: 64 })
@IsString()
@MaxLength(64)
@IsOptional()
'client-reference'?: string;
// Note: 'from-mask' is omitted as it's often restricted and requires specific carrier approval.// Note: 'cache-content' defaults to true and usually doesn't need to be overridden.
}
MMS Slide Technical Note: MMS messages use SMIL (Synchronized Multimedia Integration Language) to structure slide presentations. Each slide can contain up to two regions: an image/video region and a text region, plus optional audio. The 8-slide maximum is a common API provider limit; actual technical limits depend on total message size (typically 300–600 KB) rather than slide count (source: W3C SMIL, OMA MMS Specification).
Why DTOs? They define the expected shape of incoming data and leverage class-validator decorators for automatic validation, improving data integrity and security. @ApiProperty is used for Swagger documentation.
Implement MMS Service:
Inject HttpService (from @nestjs/axios) and ConfigService into MmsService. Implement the sendMms method.
TypeScript
// src/mms/mms.service.tsimport { HttpService } from'@nestjs/axios';
import { Injectable, Logger, InternalServerErrorException, BadRequestException } from'@nestjs/common';
import { ConfigService } from'@nestjs/config';
import axios, { AxiosError } from'axios'; // Import axios directly for isAxiosErrorimport { firstValueFrom } from'rxjs';
import { SendMmsDto } from'./dto/send-mms.dto';
// import { PrismaService } from '../prisma/prisma.service'; // Optional: Uncomment if using Prisma// Define a specific interface for the expected Sinch success response structureinterfaceSinchMmsSuccessResponse {
status: string;
to: string;
'tracking-id': string;
'status-details'?: string;
// Add other potential fields based on Sinch documentation
}
// Define a specific interface for the expected Sinch error response structureinterfaceSinchMmsErrorResponse {
'error-info'?: string;
message?: string;
'error-code'?: string | number;
// Add other potential fields based on Sinch documentation
}
@Injectable()
exportclassMmsService {
privatereadonly logger = newLogger(MmsService.name);
privatereadonlysinchApiToken: string;
privatereadonlysinchServiceId: string;
privatereadonlysinchBaseUrl: string;
privatereadonlysinchFromNumber: string;
constructor(privatereadonlyhttpService: HttpService,
privatereadonlyconfigService: ConfigService,
// private readonly prisma: PrismaService, // Optional: Inject PrismaService) {
// Load credentials securely from configthis.sinchApiToken = this.configService.get<string>('SINCH_API_TOKEN');
this.sinchServiceId = this.configService.get<string>('SINCH_SERVICE_ID');
this.sinchBaseUrl = this.configService.get<string>('SINCH_BASE_URL');
this.sinchFromNumber = this.configService.get<string>('SINCH_FROM_NUMBER');
if (!this.sinchApiToken || !this.sinchServiceId || !this.sinchBaseUrl || !this.sinchFromNumber) {
this.logger.error('Sinch API credentials or configuration missing in environment variables.');
thrownewInternalServerErrorException('MMS Service configuration error.');
}
}
asyncsendMms(sendMmsDto: SendMmsDto): Promise<SinchMmsSuccessResponse> {
// IMPORTANT: Verify this endpoint path against Sinch documentation for your specific region/account.const apiUrl = `${this.sinchBaseUrl}/v1/projects/${this.sinchServiceId}/messages`;
// Construct the payload required by the Sinch APIconst payload = {
action: 'sendmms',
'service-id': this.sinchServiceId,
from: this.sinchFromNumber,
...sendMmsDto, // Spread the validated DTO properties
};
// Default fallback text if not provided and fallback is enabledif (!payload['disable-fallback-sms'] && !payload['fallback-sms-text']) {
payload['fallback-sms-text'] = 'View your MMS message:'; // Provide a sensible default// Alternatively, throw a BadRequestException if it's mandatory for your use case// throw new BadRequestException('Fallback SMS text is required when fallback is enabled.');
}
const headers = {
'Content-Type': 'application/json; charset=utf-8',
'Authorization': `Bearer ${this.sinchApiToken}`,
};
this.logger.log(`Attempting to send MMS to ${payload.to} from ${payload.from}`);
try {
const response = awaitfirstValueFrom(
this.httpService.post<SinchMmsSuccessResponse>(apiUrl, payload, { headers }),
);
this.logger.log(`Sinch API Success Response for ${payload.to}: Status ${response.status}`);
this.logger.debug(`Sinch Response Data: ${JSON.stringify(response.data)}`);
return response.data; // Return the Sinch API response body
} catch (error) {
this.handleSinchApiError(error, payload.to, payload); // Pass payload for logging on error
}
}
privatehandleSinchApiError(error: any, recipient: string, failedPayload: any): never {
if (axios.isAxiosError(error)) {
const axiosError = error asAxiosError<SinchMmsErrorResponse>; // Use the error response interfaceconst status = axiosError.response?.status;
const data = axiosError.response?.data;
// Sinch often puts error details in 'error-info' or similar fieldsconst errorMessage = data?.['error-info'] || data?.message || axiosError.message;
const errorCode = data?.['error-code'];
this.logger.error(
`Sinch API Error for ${recipient}: Status ${status}, Code: ${errorCode}, Info: "${errorMessage}"`,
axiosError.stack
);
if (status >= 400 && status < 500) {
// Client-side errors (e.g., bad request, auth failure)thrownewBadRequestException(`Sinch API Error: ${errorMessage} (Code: ${errorCode})`);
} else {
// Server-side errors or network issuesthrownewInternalServerErrorException(`Sinch API Error: ${errorMessage} (Code: ${errorCode})`);
}
} else {
// Non-Axios errorsthis.logger.error(`Unexpected error sending MMS to ${recipient}: ${error.message}`, error.stack);
thrownewInternalServerErrorException('An unexpected error occurred while sending MMS.');
}
}
}
Why this structure? Encapsulates Sinch interaction logic, making it reusable and testable. Configuration is loaded safely. Error handling is centralized. firstValueFrom converts the Observable returned by httpService into a Promise.
Return Type: Updated sendMms to return Promise<SinchMmsSuccessResponse> and added interfaces for better type safety.
Update MMS Module:
Import and configure HttpModule and make MmsService available for injection.
TypeScript
// src/mms/mms.module.tsimport { Module } from'@nestjs/common';
import { HttpModule } from'@nestjs/axios';
import { ConfigModule, ConfigService } from'@nestjs/config'; // Import ConfigServiceimport { MmsService } from'./mms.service';
import { MmsController } from'./mms.controller'; // We'll create this next// import { PrismaModule } from '../prisma/prisma.module'; // Optional: Import if using Prisma@Module({
imports: [
HttpModule.registerAsync({
// imports: [ConfigModule], // Only needed if ConfigModule is not globaluseFactory: async (configService: ConfigService) => ({
timeout: configService.get<number>('HTTP_TIMEOUT', 15000), // Example: configurable timeout (increased default)maxRedirects: 5,
}),
inject: [ConfigService],
}),
// ConfigModule, // Only needed if ConfigModule is not global// PrismaModule, // Optional: Import if using Prisma
],
providers: [MmsService],
controllers: [MmsController], // Add the controllerexports: [MmsService], // Export if needed by other modules
})
exportclassMmsModule {}
Why HttpModule.registerAsync? Allows configuring HttpModule dynamically, potentially using values from ConfigService (like timeouts).
3. Create NestJS API Endpoint for Sending MMS
Expose MMS sending functionality via a REST API endpoint that clients can call to send multimedia messages.
Generate MMS Controller:
Bash
nest generate controller mms --flat
This creates src/mms/mms.controller.ts.
Implement Controller Endpoint:
Define a POST endpoint, inject MmsService, use the DTO for validation, and add Swagger decorators (optional).
TypeScript
// src/mms/mms.controller.tsimport { Controller, Post, Body, UsePipes, ValidationPipe, HttpCode, HttpStatus, Logger } from'@nestjs/common';
import { MmsService } from'./mms.service';
import { SendMmsDto } from'./dto/send-mms.dto';
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from'@nestjs/swagger'; // Optional: For Swagger@ApiTags('MMS') // Optional: Group endpoints in Swagger UI@Controller('mms')
exportclassMmsController {
privatereadonly logger = newLogger(MmsController.name);
constructor(privatereadonlymmsService: MmsService) {}
@Post('send')
@HttpCode(HttpStatus.ACCEPTED) // Use 202 Accepted as processing is asynchronous@UsePipes(newValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))
// Optional: Swagger Decorators@ApiOperation({ summary: 'Send an MMS message via Sinch' })
@ApiBody({ type: SendMmsDto })
@ApiResponse({ status: 202, description: 'MMS request accepted by Sinch for processing.' })
@ApiResponse({ status: 400, description: 'Bad Request - Invalid input data or Sinch client error.' })
@ApiResponse({ status: 500, description: 'Internal Server Error - Sinch API error or server issue.' })
asyncsendMms(@Body() sendMmsDto: SendMmsDto) {
this.logger.log(`Received request to send MMS to ${sendMmsDto.to}`);
try {
// The service now returns the success response objectconst result = awaitthis.mmsService.sendMms(sendMmsDto);
// Return the tracking ID or the full success response from Sinchreturn result;
} catch (error) {
// Errors are handled and thrown by MmsService, NestJS exception filter catches themthis.logger.error(`Error processing send MMS request for ${sendMmsDto.to}: ${error.message}`);
throw error; // Re-throw the exception for NestJS to handle
}
}
}
Why ValidationPipe? Automatically validates the incoming request body against SendMmsDto rules. whitelist: true removes properties not defined in the DTO, forbidNonWhitelisted: true throws an error if extra properties are present, and transform: true attempts to convert plain objects to DTO instances.
Why HttpStatus.ACCEPTED? Sending an MMS is usually asynchronous. The API accepts the request, but delivery confirmation comes later (via webhooks, not covered in detail here). 202 reflects this.
ValidationPipe Security Benefits: The combination of whitelist: true and forbidNonWhitelisted: true provides critical security protection by preventing malicious users from injecting unexpected data into requests (mass assignment attacks). This configuration ensures only explicitly defined DTO properties are processed, reducing attack surface and maintaining application integrity (source: NestJS Official Documentation, verified January 2025).
Enable Global Validation Pipe and Swagger (Optional):
Modify src/main.ts to enable the validation pipe globally and set up Swagger.
TypeScript
// src/main.tsimport { NestFactory } from'@nestjs/core';
import { AppModule } from'./app.module';
import { ValidationPipe, Logger } from'@nestjs/common';
import { ConfigService } from'@nestjs/config';
import { SwaggerModule, DocumentBuilder } from'@nestjs/swagger'; // Optional: For Swaggerasyncfunctionbootstrap() {
const app = awaitNestFactory.create(AppModule);
const configService = app.get(ConfigService);
const port = configService.get<number>('PORT', 3000);
const logger = newLogger('Bootstrap');
// Global Prefix (Optional)// app.setGlobalPrefix('api/v1');// Global Validation Pipe (Alternative to @UsePipes in controller)
app.useGlobalPipes(
newValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true, // Enable automatic transformation from payload to DTO instancetransformOptions: {
enableImplicitConversion: true, // Allows conversion based on TS type (e.g., string 'true' to boolean true)// Be mindful: Ensure DTO types are strict to avoid unexpected coercion.
},
}),
);
// Enable CORS (Configure appropriately for production)
app.enableCors();
// Setup Swagger (Optional)const swaggerConfig = newDocumentBuilder()
.setTitle('Sinch MMS Sender API')
.setDescription('API for sending MMS messages via Sinch')
.setVersion('1.0')
.addBearerAuth() // If your API itself needs auth, not just Sinch
.addTag('MMS')
.build();
constdocument = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api-docs', app, document); // Access at /api-docsawait app.listen(port);
logger.log(`Application listening on port ${port}`);
logger.log(`API Documentation available at http://localhost:${port}/api-docs`); // Optional: Log Swagger URL
}
bootstrap();
enableImplicitConversion: This option in ValidationPipe can be convenient but may lead to unexpected type coercion if your DTO property types are not strictly defined (e.g., using any or union types). Ensure your DTOs are robust.
Test with curl or Postman:
Start your application: npm run start:dev
Use curl to test the endpoint (replace placeholders).
Bash
curl -X POST http://localhost:3000/mms/send \
-H "Content-Type: application/json" \
-d '{
"to": "+1RECIPIENTNUMBER",
"message-subject": "Your Awesome Update!",
"fallback-sms-text": "Check out this image: [Link will be added automatically if enabled]",
"slide": [
{
"image": {
"url": "https://YOUR_PUBLICLY_ACCESSIBLE_IMAGE_URL.jpg"
},
"message-text": "Here is the image you requested!"
},
{
"message-text": "And some additional text on a second slide."
}
],
"client-reference": "order_update_12345"
}'
Expected Response (Success - Status Code 202):
JSON
{"status":"success","to":"+1RECIPIENTNUMBER","tracking-id":"UNIQUE-TRANSACTION-ID","status-details":"MMS request accepted and queued for processing and delivery."}
Expected Response (Validation Error - Status Code 400):
JSON
{"statusCode":400,"message":["to must be a valid phone number"// or other validation errors],"error":"Bad Request"}
4. Configure Sinch API Credentials
This section details obtaining and configuring your Sinch credentials for MMS integration.
Obtain Sinch Credentials:
Service ID (Project ID / Campaign ID):
Navigate to your Sinch Customer Dashboard (e.g., dashboard.sinch.com).
Go to the SMS & MMS section or similar product area.
Find your Service Plan ID or Project ID. This is often displayed prominently on the overview page or API settings section for your MMS service. This is the SINCH_SERVICE_ID.
API Token (Bearer Token):
In the Sinch Dashboard, navigate to Settings → API Credentials or Access Keys.
Generate a new API token or use an existing one. Ensure it has permissions for the MMS API.
Securely copy the generated token. This is your SINCH_API_TOKEN. Treat it like a password.
MMS-Enabled 'From' Number:
Go to the Numbers section in your Sinch Dashboard.
Ensure you have a number (Short Code, Toll-Free, or 10DLC) that is configured and approved for MMS sending in the target regions (e.g., US, CA).
Copy the number in E.164 format (including the + and country code). This is SINCH_FROM_NUMBER.
Configure Environment Variables:
As done in Step 1.4, place these values into your .env file:
.env
# .env
SINCH_API_TOKEN=PASTE_YOUR_API_TOKEN_HERE
SINCH_SERVICE_ID=PASTE_YOUR_SERVICE_ID_HERE
# IMPORTANT: Verify this base URL and the API endpoint path against Sinch documentation for your specific region/account.
SINCH_BASE_URL=https://mms.api.sinch.com
SINCH_FROM_NUMBER=+1YOURSINCHNUMBER
PORT=3000
DATABASE_URL="YOUR_DATABASE_CONNECTION_STRING" # Optional
Verification: Verify both the SINCH_BASE_URL and the specific API endpoint path (e.g., /v1/projects/{serviceId}/messages) used in MmsService against the official Sinch documentation for the MMS JSON API (sendmms action) corresponding to your specific account and region. These values can vary.
Secure API Keys:
.gitignore: Ensure .env is listed in your .gitignore file to prevent accidental commits.
Environment Variables in Production: Use your deployment platform's mechanism for managing secrets (e.g., AWS Secrets Manager, Kubernetes Secrets, environment variables set in the hosting environment). Do not store sensitive keys directly in code or commit them to version control.
5. How to Handle Errors in Sinch MMS Integration
Implement robust error handling to catch and respond to Sinch API errors gracefully.
Common Sinch API Errors:
Authentication Errors: Invalid API token or Service ID (HTTP 401/403)
Use a process manager like PM2 for Node.js applications
Configure auto-scaling based on traffic patterns
Frequently Asked Questions
How do I send MMS messages with Sinch and NestJS?
To send MMS messages with Sinch and NestJS, create a NestJS service that uses the Sinch MMS JSON API with the sendmms action. Configure your Sinch credentials in environment variables, implement DTOs for request validation, and use Axios to make HTTP POST requests to the Sinch API endpoint with your message payload including recipient number, slides with media URLs, and optional parameters.
What is the Sinch MMS API endpoint?
The Sinch MMS API endpoint typically follows the pattern https://mms.api.sinch.com/v1/projects/{serviceId}/messages where {serviceId} is your Sinch Service ID or Campaign ID. Always verify the exact endpoint URL in the official Sinch documentation as it may vary by region and account type.
What file types are supported for Sinch MMS?
Sinch MMS supports various media types including images (JPEG, PNG, GIF), videos (MP4, 3GP), audio files (MP3, AAC), contacts (vCard), calendar events (iCal), and PDF documents. Each slide can contain one media file plus optional text. Keep files under 500 KB for optimal deliverability across all carriers.
How do I validate phone numbers for Sinch MMS?
Use the @IsPhoneNumber() decorator from class-validator in your NestJS DTO to validate phone numbers in E.164 format. E.164 format requires a plus sign (+), country code, and subscriber number with no spaces or special characters (e.g., +15551234567 for US numbers). This prevents Sinch API errors caused by invalid phone number formatting.
What is the maximum number of slides in a Sinch MMS?
Sinch MMS supports a maximum of 8 slides per message. Each slide can contain text (up to 5,000 characters) and one media file (image, video, audio, contact, calendar, or PDF). The total message size should stay under 500 KB for optimal deliverability across all US carriers.
How do I handle Sinch API authentication errors?
Sinch API authentication errors (HTTP 401/403) typically occur due to invalid API tokens or Service IDs. Verify your SINCH_API_TOKEN and SINCH_SERVICE_ID in your .env file match the credentials from your Sinch Dashboard. Ensure your API token has proper permissions for MMS messaging and hasn't expired.
What is E.164 phone number format?
E.164 is the international standard for phone number formatting defined by ITU-T. It requires numbers to start with +, followed by the country code (1-3 digits) and subscriber number (up to 12 digits), for a maximum of 15 digits total. No spaces, parentheses, or dashes are allowed. Examples: +14151231234 (US), +442012341234 (UK).
How do I test Sinch MMS integration locally?
Test your Sinch MMS integration locally by running npm run start:dev, then use curl or Postman to send POST requests to http://localhost:3000/mms/send with a valid payload including recipient number, slides array with media URLs, and optional parameters. Monitor the console logs for Sinch API responses and any errors.
Can I use Sinch MMS internationally?
Sinch MMS availability varies by country and carrier. Native MMS is primarily supported in the US and Canada. For international destinations, check Sinch documentation for specific country support and consider SMS fallback options which Sinch can automatically enable when MMS delivery fails.
How do I implement retry logic for failed Sinch MMS?
Implement retry logic using libraries like async-retry or queue systems like Bull/BullMQ. Only retry transient errors (network issues, temporary Sinch outages) and avoid retrying permanent failures (invalid numbers, authentication errors). Check Sinch error codes to determine if retrying is appropriate, and implement exponential backoff to prevent overwhelming the API.
Use the Sinch MMS JSON API integrated with a NestJS service. Create a NestJS service that interacts with the Sinch API, define DTOs for data validation, and expose an API endpoint to trigger sending MMS messages. Follow the steps in the guide to set up the project and dependencies, create the service and controller, and load Sinch credentials from environment variables.
It's a specific Sinch API endpoint ('sendmms' action) used for sending multimedia messages programmatically. The API allows detailed configuration of MMS content, including images, videos, audio, contacts, and more, along with options for fallback SMS and delivery settings.
NestJS provides a structured, scalable, and maintainable way to build server-side applications in Node.js using TypeScript. Its modular architecture, dependency injection, and built-in tooling make it ideal for integrating with third-party APIs like Sinch.
Obtain your Sinch API Token, Service ID, and MMS-enabled 'From' number from the Sinch Dashboard. Store these securely in a .env file. In your NestJS application, use the @nestjs/config package to load these credentials into the MmsService, where they will be used to authenticate and make requests to the Sinch API. Remember to never commit the .env file to version control.
Implement robust error handling in your NestJS MMS service. Catch potential errors during the API call using try-catch blocks. Use AxiosError type for handling errors specifically from the HTTP client. Log error details, including status codes, error messages, and potentially the failed request payload (with caution due to sensitive data).
Create a dedicated MMS module and service to encapsulate the interaction logic with Sinch. Inject HttpService and ConfigService. Define DTOs for request validation and transformation. Implement a sendMms method in the service to construct the API payload and make the HTTP request to Sinch. Handle API responses and errors gracefully.
DTOs (Data Transfer Objects) are classes that define the structure of data exchanged between layers of your application. They enhance code organization, validation, and data integrity. Use class-validator decorators to enforce rules on incoming data, ensuring requests to the Sinch API are correctly formatted.
class-validator provides decorators for data validation, ensuring data integrity and security. class-transformer facilitates transforming plain JavaScript objects into class instances (DTOs) and vice versa. These libraries help maintain the structure and correctness of data flowing through your application.
Use HTTP status code 202 (Accepted). Sending MMS is typically asynchronous; 202 signifies that Sinch has accepted the request, but delivery is not immediate and will be confirmed later (e.g., through webhooks).
Provide a fallback SMS message in case the recipient's device doesn't support MMS or if MMS delivery fails. Configure the 'fallback-sms-text' property in your API request to provide a short message. You can also control fallback behavior with 'disable-fallback-sms' and 'disable-fallback-sms-link' options.
It manages application configuration and environment variables securely. It loads variables from a .env file (which should never be committed to version control) and provides type-safe access to them via the ConfigService, keeping sensitive credentials out of your codebase.
Your Sinch Service ID (sometimes referred to as Project ID or Campaign ID) can be found in your Sinch Customer Dashboard. Navigate to the SMS & MMS section of your account. The ID is usually displayed prominently in the overview or API settings.
Yes, Prisma can be integrated for database logging of MMS attempts and tracking IDs. Add Prisma to your project and inject the PrismaService into the MmsService. You can then log details like recipient, tracking ID, status, and any errors that may occur, providing a history of MMS activity.
The 'client-reference' field is an optional parameter that allows you to include a custom identifier for the MMS message, such as an order ID or user ID. This helps you track messages and reconcile them with your internal systems.
Learn how to send MMS messages using Sinch API in your NestJS application. This comprehensive tutorial covers everything you need to build a production-ready multimedia messaging service: initial project setup, TypeScript implementation, request validation, error handling, security best practices, and deployment.
Whether you're building notification systems, marketing campaigns, or two-way multimedia messaging features, this guide shows you how to integrate Sinch MMS capabilities into your Node.js backend. By the end, you'll have a robust NestJS service that reliably sends MMS messages with images, videos, and rich media content.
What you'll learn:
What is Sinch MMS and Why Use It with NestJS?
Sinch MMS (Multimedia Messaging Service) enables you to send rich media messages—images, videos, audio files, and documents—to mobile devices via the Sinch platform. Unlike SMS, which is limited to text, MMS allows you to deliver engaging visual content that enhances user communication.
Why NestJS for Sinch MMS Integration?
NestJS is a progressive Node.js framework that provides a solid foundation for building scalable server-side applications. Its modular architecture, built-in dependency injection, TypeScript support, and extensive tooling make it ideal for implementing reliable Sinch API integrations with proper error handling and maintainability.
Project Overview and Goals
Goal: Create a NestJS backend service that exposes an API endpoint to send MMS messages via the Sinch platform.
Problem Solved: Programmatically send rich media messages (images, videos, audio, contacts) as part of your application's workflows – notifications, marketing campaigns, or user interactions – leveraging Sinch's MMS capabilities.
Technologies You'll Use:
sendmmsaction) designed for sending individual MMS messages with detailed configuration options.@nestjs/axios): A promise-based HTTP client for making requests to the Sinch API.@nestjs/config: Manages environment variables and application configuration securely.class-validator&class-transformer: Provides robust request data validation and transformation.@nestjs/swagger: Generates API documentation automatically.System Architecture:
Prerequisites:
Media File Size Best Practices: Keep media files under 500 KB to ensure reliable MMS delivery across all US carriers. While technical limits vary by carrier (AT&T: 1 MB, Verizon: 1.7 MB, T-Mobile: 3 MB), industry consensus recommends 300–500 KB for optimal deliverability (source: Bandwidth Support, verified January 2025). All carriers reliably handle messages up to 300 KB.
1. Set Up Your NestJS Project for Sinch MMS
Initialize a new NestJS project and install the necessary dependencies for Sinch API integration.
Create Your New NestJS Project: Open your terminal and run the NestJS CLI command:
npx @nestjs/cli new sinch-mms-sender cd sinch-mms-senderChoose your preferred package manager (npm or yarn) when prompted.
Install Dependencies: Install modules for HTTP requests, configuration management, validation, and optionally Swagger and Prisma.
# Core dependencies npm install @nestjs/axios axios @nestjs/config class-validator class-transformer # Optional for API documentation npm install @nestjs/swagger swagger-ui-express # Optional for database logging npm install @prisma/client npm install prisma --save-dev@nestjs/axios&axios: For making HTTP requests to the Sinch API.@nestjs/config: Manages environment variables securely.class-validator&class-transformer: For validating incoming request payloads.@nestjs/swagger&swagger-ui-express: (Optional) For generating OpenAPI documentation.@prisma/client&prisma: (Optional) For database interactions if you choose to log messages.Initialize Prisma (Optional): If you plan to add database logging:
npx prisma init --datasource-provider postgresql # Or your preferred DB providerThis creates a
prismadirectory with aschema.prismafile and a.envfile.Configure Environment Variables: Create or update the
.envfile in your project root. Never commit this file to version control. Add your Sinch credentials and other necessary configurations:E.164 Format Requirements: E.164 is the ITU-T international standard (ITU-T Recommendation E.164) for phone number formatting. Numbers must start with
+, followed by the country code (1–3 digits) and subscriber number (max 12 digits), for a maximum total of 15 digits. No spaces, parentheses, or dashes are allowed. Examples:+14151231234(US),+442012341234(UK). This format ensures correct international SMS/MMS routing (source: ITU-T E.164, Twilio E.164 Guide)..env? Keeps sensitive credentials out of your codebase and allows different configurations per environment (development, staging, production).SINCH_BASE_URLand the specific API endpoint path (used later inMmsService) against the official Sinch documentation for your account and region, as these can differ.Load Configuration: Modify
src/app.module.tsto load environment variables usingConfigModule.// src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MmsModule } from './mms/mms.module'; // We'll create this next // import { PrismaModule } from './prisma/prisma.module'; // Optional: Uncomment if using Prisma @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // Make ConfigService available globally envFilePath: '.env', }), MmsModule, // PrismaModule, // Optional: Uncomment if using Prisma ], controllers: [AppController], // Keep or remove the default AppController/Service providers: [AppService], // Keep or remove the default AppController/Service }) export class AppModule {}isGlobal: true: MakesConfigServiceinjectable in any module without needing to importConfigModuleexplicitly everywhere.Review Project Structure: After completing the next steps, your structure will resemble:
2. Implement Sinch MMS Service in NestJS
Create a dedicated module and service to handle Sinch API interaction logic for sending MMS messages.
Generate MMS Module and Service: Use the NestJS CLI:
nest generate module mms nest generate service mms --flat # Use --flat to avoid creating a subdirectoryThis creates
src/mms/mms.module.tsandsrc/mms/mms.service.ts.Define Data Transfer Objects (DTOs): Create DTOs to represent the data structure needed to send an MMS, including validation rules. Create a
src/mms/dtodirectory.// src/mms/dto/mms-media.dto.ts import { IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; // Optional: For Swagger export class MmsMediaDto { @ApiProperty({ description: 'URL of the media file (image, audio, video, contact, calendar, pdf)' }) @IsUrl() @IsNotEmpty() url: string; } export class MmsSlideContentDto { @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() image?: MmsMediaDto; @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() audio?: MmsMediaDto; @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() video?: MmsMediaDto; @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() contact?: MmsMediaDto; @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() calendar?: MmsMediaDto; @ApiPropertyOptional({ type: MmsMediaDto }) @IsOptional() pdf?: MmsMediaDto; }// src/mms/dto/mms-slide.dto.ts import { Type } from 'class-transformer'; import { IsNotEmptyObject, IsOptional, IsString, MaxLength, ValidateNested } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { MmsSlideContentDto } from './mms-media.dto'; export class MmsSlideDto extends MmsSlideContentDto { @ApiPropertyOptional({ description: 'Text message for the slide', maxLength: 5000 }) @IsString() @MaxLength(5000) @IsOptional() 'message-text'?: string; // Note: Use quotes for property names with hyphens }// src/mms/dto/send-mms.dto.ts import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsArray, IsBoolean, IsDateString, IsISO8601, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, MaxLength, ValidateNested, ArrayMinSize, ArrayMaxSize, } from 'class-validator'; import { MmsSlideDto } from './mms-slide.dto'; export class SendMmsDto { @ApiProperty({ description: 'Destination phone number in E.164 format (e.g., +15551234567)' }) @IsPhoneNumber(null) // Use null for international format validation @IsNotEmpty() to: string; @ApiProperty({ description: 'Array of MMS slides (1-8 slides)', type: [MmsSlideDto] }) @IsArray() @ArrayMinSize(1) @ArrayMaxSize(8) @ValidateNested({ each: true }) @Type(() => MmsSlideDto) @IsNotEmpty() slide: MmsSlideDto[]; @ApiPropertyOptional({ description: 'MMS subject text', maxLength: 80 }) @IsString() @MaxLength(80) @IsOptional() 'message-subject'?: string; @ApiPropertyOptional({ description: 'Text for fallback SMS if MMS fails or is unsupported', maxLength: 160 }) @IsString() @MaxLength(160) // Keep fallback SMS concise @IsOptional() // Required only if disable-fallback-sms is false (default) 'fallback-sms-text'?: string; @ApiPropertyOptional({ description: 'Set to true to disable sending fallback SMS link', default: false }) @IsBoolean() @IsOptional() 'disable-fallback-sms-link'?: boolean = false; @ApiPropertyOptional({ description: 'Set to true to disable sending fallback SMS entirely', default: false }) @IsBoolean() @IsOptional() 'disable-fallback-sms'?: boolean = false; @ApiPropertyOptional({ description: 'ISO8601 timestamp for fallback SMS link expiration (max 1 year)', format: 'date-time' }) @IsISO8601() @IsOptional() 'fallback-sms-link-expiration'?: string; @ApiPropertyOptional({ description: 'ISO8601 timestamp for MMS delivery expiration (max 3 days)', format: 'date-time' }) @IsISO8601() @IsOptional() 'mms-expiry-timestamp'?: string; @ApiPropertyOptional({ description: 'Client-specific reference ID (max 64 chars)', maxLength: 64 }) @IsString() @MaxLength(64) @IsOptional() 'client-reference'?: string; // Note: 'from-mask' is omitted as it's often restricted and requires specific carrier approval. // Note: 'cache-content' defaults to true and usually doesn't need to be overridden. }MMS Slide Technical Note: MMS messages use SMIL (Synchronized Multimedia Integration Language) to structure slide presentations. Each slide can contain up to two regions: an image/video region and a text region, plus optional audio. The 8-slide maximum is a common API provider limit; actual technical limits depend on total message size (typically 300–600 KB) rather than slide count (source: W3C SMIL, OMA MMS Specification).
class-validatordecorators for automatic validation, improving data integrity and security.@ApiPropertyis used for Swagger documentation.Implement MMS Service: Inject
HttpService(from@nestjs/axios) andConfigServiceintoMmsService. Implement thesendMmsmethod.// src/mms/mms.service.ts import { HttpService } from '@nestjs/axios'; import { Injectable, Logger, InternalServerErrorException, BadRequestException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import axios, { AxiosError } from 'axios'; // Import axios directly for isAxiosError import { firstValueFrom } from 'rxjs'; import { SendMmsDto } from './dto/send-mms.dto'; // import { PrismaService } from '../prisma/prisma.service'; // Optional: Uncomment if using Prisma // Define a specific interface for the expected Sinch success response structure interface SinchMmsSuccessResponse { status: string; to: string; 'tracking-id': string; 'status-details'?: string; // Add other potential fields based on Sinch documentation } // Define a specific interface for the expected Sinch error response structure interface SinchMmsErrorResponse { 'error-info'?: string; message?: string; 'error-code'?: string | number; // Add other potential fields based on Sinch documentation } @Injectable() export class MmsService { private readonly logger = new Logger(MmsService.name); private readonly sinchApiToken: string; private readonly sinchServiceId: string; private readonly sinchBaseUrl: string; private readonly sinchFromNumber: string; constructor( private readonly httpService: HttpService, private readonly configService: ConfigService, // private readonly prisma: PrismaService, // Optional: Inject PrismaService ) { // Load credentials securely from config this.sinchApiToken = this.configService.get<string>('SINCH_API_TOKEN'); this.sinchServiceId = this.configService.get<string>('SINCH_SERVICE_ID'); this.sinchBaseUrl = this.configService.get<string>('SINCH_BASE_URL'); this.sinchFromNumber = this.configService.get<string>('SINCH_FROM_NUMBER'); if (!this.sinchApiToken || !this.sinchServiceId || !this.sinchBaseUrl || !this.sinchFromNumber) { this.logger.error('Sinch API credentials or configuration missing in environment variables.'); throw new InternalServerErrorException('MMS Service configuration error.'); } } async sendMms(sendMmsDto: SendMmsDto): Promise<SinchMmsSuccessResponse> { // IMPORTANT: Verify this endpoint path against Sinch documentation for your specific region/account. const apiUrl = `${this.sinchBaseUrl}/v1/projects/${this.sinchServiceId}/messages`; // Construct the payload required by the Sinch API const payload = { action: 'sendmms', 'service-id': this.sinchServiceId, from: this.sinchFromNumber, ...sendMmsDto, // Spread the validated DTO properties }; // Default fallback text if not provided and fallback is enabled if (!payload['disable-fallback-sms'] && !payload['fallback-sms-text']) { payload['fallback-sms-text'] = 'View your MMS message:'; // Provide a sensible default // Alternatively, throw a BadRequestException if it's mandatory for your use case // throw new BadRequestException('Fallback SMS text is required when fallback is enabled.'); } const headers = { 'Content-Type': 'application/json; charset=utf-8', 'Authorization': `Bearer ${this.sinchApiToken}`, }; this.logger.log(`Attempting to send MMS to ${payload.to} from ${payload.from}`); try { const response = await firstValueFrom( this.httpService.post<SinchMmsSuccessResponse>(apiUrl, payload, { headers }), ); this.logger.log(`Sinch API Success Response for ${payload.to}: Status ${response.status}`); this.logger.debug(`Sinch Response Data: ${JSON.stringify(response.data)}`); return response.data; // Return the Sinch API response body } catch (error) { this.handleSinchApiError(error, payload.to, payload); // Pass payload for logging on error } } private handleSinchApiError(error: any, recipient: string, failedPayload: any): never { if (axios.isAxiosError(error)) { const axiosError = error as AxiosError<SinchMmsErrorResponse>; // Use the error response interface const status = axiosError.response?.status; const data = axiosError.response?.data; // Sinch often puts error details in 'error-info' or similar fields const errorMessage = data?.['error-info'] || data?.message || axiosError.message; const errorCode = data?.['error-code']; this.logger.error( `Sinch API Error for ${recipient}: Status ${status}, Code: ${errorCode}, Info: "${errorMessage}"`, axiosError.stack ); if (status >= 400 && status < 500) { // Client-side errors (e.g., bad request, auth failure) throw new BadRequestException(`Sinch API Error: ${errorMessage} (Code: ${errorCode})`); } else { // Server-side errors or network issues throw new InternalServerErrorException(`Sinch API Error: ${errorMessage} (Code: ${errorCode})`); } } else { // Non-Axios errors this.logger.error(`Unexpected error sending MMS to ${recipient}: ${error.message}`, error.stack); throw new InternalServerErrorException('An unexpected error occurred while sending MMS.'); } } }firstValueFromconverts the Observable returned byhttpServiceinto a Promise.sendMmsto returnPromise<SinchMmsSuccessResponse>and added interfaces for better type safety.Update MMS Module: Import and configure
HttpModuleand makeMmsServiceavailable for injection.// src/mms/mms.module.ts import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { ConfigModule, ConfigService } from '@nestjs/config'; // Import ConfigService import { MmsService } from './mms.service'; import { MmsController } from './mms.controller'; // We'll create this next // import { PrismaModule } from '../prisma/prisma.module'; // Optional: Import if using Prisma @Module({ imports: [ HttpModule.registerAsync({ // imports: [ConfigModule], // Only needed if ConfigModule is not global useFactory: async (configService: ConfigService) => ({ timeout: configService.get<number>('HTTP_TIMEOUT', 15000), // Example: configurable timeout (increased default) maxRedirects: 5, }), inject: [ConfigService], }), // ConfigModule, // Only needed if ConfigModule is not global // PrismaModule, // Optional: Import if using Prisma ], providers: [MmsService], controllers: [MmsController], // Add the controller exports: [MmsService], // Export if needed by other modules }) export class MmsModule {}HttpModule.registerAsync? Allows configuringHttpModuledynamically, potentially using values fromConfigService(like timeouts).3. Create NestJS API Endpoint for Sending MMS
Expose MMS sending functionality via a REST API endpoint that clients can call to send multimedia messages.
Generate MMS Controller:
This creates
src/mms/mms.controller.ts.Implement Controller Endpoint: Define a POST endpoint, inject
MmsService, use the DTO for validation, and add Swagger decorators (optional).// src/mms/mms.controller.ts import { Controller, Post, Body, UsePipes, ValidationPipe, HttpCode, HttpStatus, Logger } from '@nestjs/common'; import { MmsService } from './mms.service'; import { SendMmsDto } from './dto/send-mms.dto'; import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; // Optional: For Swagger @ApiTags('MMS') // Optional: Group endpoints in Swagger UI @Controller('mms') export class MmsController { private readonly logger = new Logger(MmsController.name); constructor(private readonly mmsService: MmsService) {} @Post('send') @HttpCode(HttpStatus.ACCEPTED) // Use 202 Accepted as processing is asynchronous @UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })) // Optional: Swagger Decorators @ApiOperation({ summary: 'Send an MMS message via Sinch' }) @ApiBody({ type: SendMmsDto }) @ApiResponse({ status: 202, description: 'MMS request accepted by Sinch for processing.' }) @ApiResponse({ status: 400, description: 'Bad Request - Invalid input data or Sinch client error.' }) @ApiResponse({ status: 500, description: 'Internal Server Error - Sinch API error or server issue.' }) async sendMms(@Body() sendMmsDto: SendMmsDto) { this.logger.log(`Received request to send MMS to ${sendMmsDto.to}`); try { // The service now returns the success response object const result = await this.mmsService.sendMms(sendMmsDto); // Return the tracking ID or the full success response from Sinch return result; } catch (error) { // Errors are handled and thrown by MmsService, NestJS exception filter catches them this.logger.error(`Error processing send MMS request for ${sendMmsDto.to}: ${error.message}`); throw error; // Re-throw the exception for NestJS to handle } } }ValidationPipe? Automatically validates the incoming request body againstSendMmsDtorules.whitelist: trueremoves properties not defined in the DTO,forbidNonWhitelisted: truethrows an error if extra properties are present, andtransform: trueattempts to convert plain objects to DTO instances.HttpStatus.ACCEPTED? Sending an MMS is usually asynchronous. The API accepts the request, but delivery confirmation comes later (via webhooks, not covered in detail here). 202 reflects this.ValidationPipe Security Benefits: The combination of
whitelist: trueandforbidNonWhitelisted: trueprovides critical security protection by preventing malicious users from injecting unexpected data into requests (mass assignment attacks). This configuration ensures only explicitly defined DTO properties are processed, reducing attack surface and maintaining application integrity (source: NestJS Official Documentation, verified January 2025).Enable Global Validation Pipe and Swagger (Optional): Modify
src/main.tsto enable the validation pipe globally and set up Swagger.// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; // Optional: For Swagger async function bootstrap() { const app = await NestFactory.create(AppModule); const configService = app.get(ConfigService); const port = configService.get<number>('PORT', 3000); const logger = new Logger('Bootstrap'); // Global Prefix (Optional) // app.setGlobalPrefix('api/v1'); // Global Validation Pipe (Alternative to @UsePipes in controller) app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, // Enable automatic transformation from payload to DTO instance transformOptions: { enableImplicitConversion: true, // Allows conversion based on TS type (e.g., string 'true' to boolean true) // Be mindful: Ensure DTO types are strict to avoid unexpected coercion. }, }), ); // Enable CORS (Configure appropriately for production) app.enableCors(); // Setup Swagger (Optional) const swaggerConfig = new DocumentBuilder() .setTitle('Sinch MMS Sender API') .setDescription('API for sending MMS messages via Sinch') .setVersion('1.0') .addBearerAuth() // If your API itself needs auth, not just Sinch .addTag('MMS') .build(); const document = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('api-docs', app, document); // Access at /api-docs await app.listen(port); logger.log(`Application listening on port ${port}`); logger.log(`API Documentation available at http://localhost:${port}/api-docs`); // Optional: Log Swagger URL } bootstrap();enableImplicitConversion: This option inValidationPipecan be convenient but may lead to unexpected type coercion if your DTO property types are not strictly defined (e.g., usinganyor union types). Ensure your DTOs are robust.Test with
curlor Postman: Start your application:npm run start:devUse
curlto test the endpoint (replace placeholders).curl -X POST http://localhost:3000/mms/send \ -H "Content-Type: application/json" \ -d '{ "to": "+1RECIPIENTNUMBER", "message-subject": "Your Awesome Update!", "fallback-sms-text": "Check out this image: [Link will be added automatically if enabled]", "slide": [ { "image": { "url": "https://YOUR_PUBLICLY_ACCESSIBLE_IMAGE_URL.jpg" }, "message-text": "Here is the image you requested!" }, { "message-text": "And some additional text on a second slide." } ], "client-reference": "order_update_12345" }'Expected Response (Success - Status Code 202):
{ "status": "success", "to": "+1RECIPIENTNUMBER", "tracking-id": "UNIQUE-TRANSACTION-ID", "status-details": "MMS request accepted and queued for processing and delivery." }Expected Response (Validation Error - Status Code 400):
{ "statusCode": 400, "message": [ "to must be a valid phone number" // or other validation errors ], "error": "Bad Request" }4. Configure Sinch API Credentials
This section details obtaining and configuring your Sinch credentials for MMS integration.
Obtain Sinch Credentials:
dashboard.sinch.com).SINCH_SERVICE_ID.SINCH_API_TOKEN. Treat it like a password.+and country code). This isSINCH_FROM_NUMBER.Configure Environment Variables: As done in Step 1.4, place these values into your
.envfile:SINCH_BASE_URLand the specific API endpoint path (e.g.,/v1/projects/{serviceId}/messages) used inMmsServiceagainst the official Sinch documentation for the MMS JSON API (sendmmsaction) corresponding to your specific account and region. These values can vary.Secure API Keys:
.gitignore: Ensure.envis listed in your.gitignorefile to prevent accidental commits.5. How to Handle Errors in Sinch MMS Integration
Implement robust error handling to catch and respond to Sinch API errors gracefully.
Common Sinch API Errors:
Error Handling Strategy:
The
MmsServiceimplements centralized error handling in thehandleSinchApiErrormethod:BadRequestExceptionfor client errors andInternalServerErrorExceptionfor server errorsBest Practices:
6. Validate MMS Requests with class-validator
Use
class-validatordecorators in your DTOs to ensure all incoming MMS requests are properly validated.Key Validation Rules:
@IsPhoneNumber()ensures E.164 format@IsUrl()validates media URLs@ArrayMinSize()and@ArrayMaxSize()enforce slide count limits@MaxLength()prevents oversized messages@IsNotEmpty()ensures critical fields are presentWhy Validation Matters:
7. Secure Your Sinch MMS API
Implement security best practices to protect your MMS service and Sinch credentials.
Security Checklist:
.envfiles to version controlwhitelist: trueandforbidNonWhitelisted: trueto prevent mass assignment attacks@nestjs/throttlerto prevent abuseRate Limiting Example:
// src/app.module.ts import { ThrottlerModule } from '@nestjs/throttler'; @Module({ imports: [ ThrottlerModule.forRoot([{ ttl: 60000, // 1 minute limit: 10, // 10 requests per minute }]), // ... other imports ], })8. Deploy Your NestJS MMS Application
Deploy your Sinch MMS service to production with Docker and proper configuration.
Build for Production:
Docker Deployment:
Create a
Dockerfile:Build and run:
docker build -t sinch-mms-api . docker run -p 3000:3000 --env-file .env sinch-mms-apiDeployment Considerations:
Frequently Asked Questions
How do I send MMS messages with Sinch and NestJS?
To send MMS messages with Sinch and NestJS, create a NestJS service that uses the Sinch MMS JSON API with the
sendmmsaction. Configure your Sinch credentials in environment variables, implement DTOs for request validation, and use Axios to make HTTP POST requests to the Sinch API endpoint with your message payload including recipient number, slides with media URLs, and optional parameters.What is the Sinch MMS API endpoint?
The Sinch MMS API endpoint typically follows the pattern
https://mms.api.sinch.com/v1/projects/{serviceId}/messageswhere{serviceId}is your Sinch Service ID or Campaign ID. Always verify the exact endpoint URL in the official Sinch documentation as it may vary by region and account type.What file types are supported for Sinch MMS?
Sinch MMS supports various media types including images (JPEG, PNG, GIF), videos (MP4, 3GP), audio files (MP3, AAC), contacts (vCard), calendar events (iCal), and PDF documents. Each slide can contain one media file plus optional text. Keep files under 500 KB for optimal deliverability across all carriers.
How do I validate phone numbers for Sinch MMS?
Use the
@IsPhoneNumber()decorator fromclass-validatorin your NestJS DTO to validate phone numbers in E.164 format. E.164 format requires a plus sign (+), country code, and subscriber number with no spaces or special characters (e.g., +15551234567 for US numbers). This prevents Sinch API errors caused by invalid phone number formatting.What is the maximum number of slides in a Sinch MMS?
Sinch MMS supports a maximum of 8 slides per message. Each slide can contain text (up to 5,000 characters) and one media file (image, video, audio, contact, calendar, or PDF). The total message size should stay under 500 KB for optimal deliverability across all US carriers.
How do I handle Sinch API authentication errors?
Sinch API authentication errors (HTTP 401/403) typically occur due to invalid API tokens or Service IDs. Verify your
SINCH_API_TOKENandSINCH_SERVICE_IDin your.envfile match the credentials from your Sinch Dashboard. Ensure your API token has proper permissions for MMS messaging and hasn't expired.What is E.164 phone number format?
E.164 is the international standard for phone number formatting defined by ITU-T. It requires numbers to start with
+, followed by the country code (1-3 digits) and subscriber number (up to 12 digits), for a maximum of 15 digits total. No spaces, parentheses, or dashes are allowed. Examples:+14151231234(US),+442012341234(UK).How do I test Sinch MMS integration locally?
Test your Sinch MMS integration locally by running
npm run start:dev, then use curl or Postman to send POST requests tohttp://localhost:3000/mms/sendwith a valid payload including recipient number, slides array with media URLs, and optional parameters. Monitor the console logs for Sinch API responses and any errors.Can I use Sinch MMS internationally?
Sinch MMS availability varies by country and carrier. Native MMS is primarily supported in the US and Canada. For international destinations, check Sinch documentation for specific country support and consider SMS fallback options which Sinch can automatically enable when MMS delivery fails.
How do I implement retry logic for failed Sinch MMS?
Implement retry logic using libraries like
async-retryor queue systems like Bull/BullMQ. Only retry transient errors (network issues, temporary Sinch outages) and avoid retrying permanent failures (invalid numbers, authentication errors). Check Sinch error codes to determine if retrying is appropriate, and implement exponential backoff to prevent overwhelming the API.Related Resources