code examples
code examples
Send MMS with Plivo and NestJS: Complete 2025 Developer Guide
Learn how to send MMS messages using Plivo API in NestJS. Complete tutorial with validation, delivery tracking, media optimization, and production deployment best practices.
How to Send MMS Messages with Plivo API and NestJS
Learn how to send MMS (Multimedia Messaging Service) messages using the Plivo API within a NestJS application. This guide walks you through the complete setup – from installing dependencies to implementing advanced features like error handling, media validation, and delivery tracking.
Prerequisites for Plivo MMS in NestJS
Before you begin, ensure you have:
- Node.js v20+ (LTS version recommended) and npm/yarn installed. NestJS 11 requires Node.js v20 or higher as of 2025, following the deprecation of Node.js v16 and v18.
- A Plivo account with API credentials (Auth ID and Auth Token) from plivo.com.
- The Plivo Node.js SDK installed via
npm install plivo. The SDK is actively maintained with regular updates. - A Plivo phone number capable of sending MMS – verify MMS support in your account dashboard.
- Basic understanding of NestJS concepts: modules, services, controllers, and dependency injection.
Understanding MMS with Plivo
What Is MMS?
MMS extends traditional SMS by allowing you to send multimedia content:
- Images: JPEG, PNG, GIF
- Videos: MP4, 3GP
- Audio: MP3, AMR
- vCards: Contact information
- PDF Documents: (Carrier support varies)
Media Types and Size Limits for Plivo MMS
Per official Plivo documentation, the following limits apply:
- Outbound MMS Limits:
- Up to 10 attachments per message (combination of media_urls and media_ids)
- Maximum 2 MB per attachment
- Total message size (body text + all attachments) must be less than 5 MB
- Message body can be up to 1,600 characters (4.8 KB)
- Messages exceeding 5 MB will fail with Plivo error code 120
- Carrier-Specific Recommendations (source):
- For US carriers: Use attachments no larger than 600 KB for non-JPEG/PNG/GIF files
- AT&T long code: 0.675 MB limit; T-Mobile: 1.5 MB limit; Verizon: 0.675 MB limit
- For Canada: Keep all MMS attachments smaller than 1 MB across all networks
- Media Retention (source): Plivo saves media sent to customers for 1 year. Unused media (uploaded but not sent) is deleted after 6 hours.
- Supported Formats:
- Images: JPEG, JPG, PNG, GIF, BMP
- Video: MP4, 3GP, AVI, MOV
- Audio: MP3, WAV, AMR, OGG
Set Up a NestJS Project for Plivo MMS
Step 1: Create a New NestJS Application
Open your terminal and run:
npm i -g @nestjs/cli
nest new plivo-mms-app
cd plivo-mms-appChoose your preferred package manager when prompted (npm or yarn).
Step 2: Install Plivo Node.js SDK
Install the official Plivo SDK:
npm install plivo
# or
yarn add plivoStep 3: Configure Environment Variables
Create a .env file in your project root:
PLIVO_AUTH_ID=your_auth_id
PLIVO_AUTH_TOKEN=your_auth_token
PLIVO_PHONE_NUMBER=your_plivo_phone_numberInstall the config package to manage environment variables:
npm install @nestjs/config
# or
yarn add @nestjs/configUpdate app.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // Makes ConfigService available throughout the app
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Create an MMS Service in NestJS
Generate a Service
Run this command to generate a new service:
nest generate service mmsThis creates mms.service.ts and mms.service.spec.ts in the src folder.
Implement Basic MMS Functionality
Update src/mms/mms.service.ts:
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as plivo from 'plivo';
@Injectable()
export class MmsService {
private readonly logger = new Logger(MmsService.name);
private client: plivo.Client;
private fromNumber: string;
constructor(private configService: ConfigService) {
const authId = this.configService.get<string>('PLIVO_AUTH_ID');
const authToken = this.configService.get<string>('PLIVO_AUTH_TOKEN');
this.fromNumber = this.configService.get<string>('PLIVO_PHONE_NUMBER');
this.client = new plivo.Client(authId, authToken);
this.logger.log('Plivo MMS Service initialized');
}
async sendMMS(
to: string,
text: string,
mediaUrls: string[],
): Promise<any> {
try {
const response = await this.client.messages.create({
src: this.fromNumber,
dst: to,
text: text,
type: 'mms',
media_urls: mediaUrls,
});
this.logger.log(`MMS sent successfully: ${JSON.stringify(response)}`);
return response;
} catch (error) {
this.logger.error(`Failed to send MMS: ${error.message}`);
throw error;
}
}
}Key Points:
- Dependency Injection: The
ConfigServiceinjects environment variables. - Client Initialization: Initialize the Plivo client once in the constructor.
- Error Handling: The try-catch block captures errors and logs them.
- Type Safety: TypeScript provides better IDE support and catches errors at compile time.
Create an MMS Controller to Handle Requests
Generate a Controller
Run:
nest generate controller mmsImplement the Controller
Update src/mms/mms.controller.ts:
import { Controller, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { MmsService } from './mms.service';
interface SendMmsDto {
to: string;
text: string;
mediaUrls: string[];
}
@Controller('mms')
export class MmsController {
constructor(private readonly mmsService: MmsService) {}
@Post('send')
async sendMms(@Body() sendMmsDto: SendMmsDto) {
try {
const response = await this.mmsService.sendMMS(
sendMmsDto.to,
sendMmsDto.text,
sendMmsDto.mediaUrls,
);
return {
success: true,
messageUuid: response.messageUuid,
message: 'MMS sent successfully',
};
} catch (error) {
throw new HttpException(
{
success: false,
message: 'Failed to send MMS',
error: error.message,
},
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}Best Practices:
- DTOs (Data Transfer Objects): Define the shape of incoming request data.
- Error Handling: Use
HttpExceptionto return proper HTTP status codes. - Service Separation: Keep business logic in services, not controllers.
Test Your Plivo MMS Implementation
Using cURL
Test your endpoint with this command:
curl -X POST http://localhost:3000/mms/send \
-H "Content-Type: application/json" \
-d '{
"to": "+1234567890",
"text": "Hello! This is an MMS with an image.",
"mediaUrls": ["https://example.com/image.jpg"]
}'Using Postman
- Method: POST
- URL:
http://localhost:3000/mms/send - Headers:
Content-Type: application/json - Body (raw JSON):
{
"to": "+1234567890",
"text": "Check out this image!",
"mediaUrls": ["https://example.com/sample.png"]
}Expected Response
{
"success": true,
"messageUuid": "abc123-def456-ghi789",
"message": "MMS sent successfully"
}Add Advanced Features to Your Plivo MMS Service
1. Input Validation with class-validator
Install validation packages:
npm install class-validator class-transformer
# or
yarn add class-validator class-transformerCreate a DTO file src/mms/dto/send-mms.dto.ts:
import { IsString, IsArray, IsNotEmpty, ArrayMinSize, ArrayMaxSize, Matches, MaxLength } from 'class-validator';
export class SendMmsDto {
@IsString()
@IsNotEmpty()
@Matches(/^\+[1-9]\d{1,14}$/, {
message: 'Phone number must be in E.164 format (e.g., +1234567890)',
})
to: string;
@IsString()
@MaxLength(1600, {
message: 'Message text cannot exceed 1,600 characters',
})
text: string;
@IsArray()
@ArrayMinSize(1, { message: 'At least one media URL is required' })
@ArrayMaxSize(10, { message: 'Maximum 10 media URLs allowed' })
@IsString({ each: true })
mediaUrls: string[];
}Enable global validation in main.ts:
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // Strip properties that don't have decorators
forbidNonWhitelisted: true, // Throw error if non-whitelisted properties exist
transform: true, // Auto-transform payloads to DTO instances
}));
await app.listen(3000);
}
bootstrap();Update the controller to use the DTO:
import { Controller, Post, Body } from '@nestjs/common';
import { MmsService } from './mms.service';
import { SendMmsDto } from './dto/send-mms.dto';
@Controller('mms')
export class MmsController {
constructor(private readonly mmsService: MmsService) {}
@Post('send')
async sendMms(@Body() sendMmsDto: SendMmsDto) {
// Validation happens automatically
const response = await this.mmsService.sendMMS(
sendMmsDto.to,
sendMmsDto.text,
sendMmsDto.mediaUrls,
);
return {
success: true,
messageUuid: response.messageUuid,
};
}
}Troubleshoot Common MMS Issues
Why Is My MMS Not Being Delivered?
Symptoms: Message status shows "failed" or "undelivered"
Solutions:
- Check Phone Number: Verify you're using E.164 format (e.g., +1234567890).
- Verify MMS Support: Ensure the recipient's carrier supports MMS.
- Check Media URLs: Confirm all media URLs are publicly accessible (HTTPS recommended).
- Size Limits: Verify total message size is under 5 MB (2 MB per attachment).
- Carrier Routing: Some carriers have restrictions – check Plivo's carrier coverage.
Why Is Media Not Displaying in My MMS?
Symptoms: Recipient receives text but no media
Solutions:
- File Format: Ensure media uses supported formats (JPEG, PNG, GIF, MP4).
- URL Accessibility: Test media URLs in a browser to confirm they're publicly accessible.
- HTTPS: Use HTTPS URLs for better carrier compatibility.
- Content-Type Headers: Ensure your media server sends correct Content-Type headers.
- File Size: Keep files under recommended carrier limits (600 KB for US, 1 MB for Canada).
Why Are My MMS Messages Taking Too Long to Send?
Symptoms: Messages take longer than expected to send
Solutions:
- Media Hosting: Host media on a CDN for faster access.
- Async Processing: Implement queue systems (Bull, RabbitMQ) for high-volume sending.
- Connection Pooling: Reuse Plivo client instances instead of creating new ones.
- Batch Processing: Group multiple messages when possible.
Why Is My Webhook Not Receiving Delivery Events?
Symptoms: Delivery status callback never fires
Solutions:
- Public URL: Ensure your webhook URL is publicly accessible (use ngrok for local development).
- HTTPS: Plivo requires HTTPS for production webhooks.
- Response Time: Return a 200 status code within 2 seconds.
- Signature Validation: Confirm you're not blocking requests due to invalid signatures.
Best Practices for Production Plivo MMS
1. Error Handling
Implement comprehensive error handling:
async sendMMS(to: string, text: string, mediaUrls: string[]): Promise<any> {
try {
await this.validateMediaUrls(mediaUrls);
const response = await this.client.messages.create({
src: this.fromNumber,
dst: to,
text,
type: 'mms',
media_urls: mediaUrls,
});
return response;
} catch (error) {
// Log detailed error information
this.logger.error(`MMS Send Error: ${error.message}`, {
to,
mediaUrls,
errorCode: error.code,
errorDetails: error.response?.data,
});
// Re-throw with user-friendly message
if (error.code === 120) {
throw new BadRequestException('Message size exceeds 5 MB limit');
}
throw new InternalServerErrorException('Failed to send MMS');
}
}2. Rate Limiting
Implement rate limiting to avoid overwhelming the Plivo API:
npm install @nestjs/throttlerUpdate app.module.ts:
import { ThrottlerModule } from '@nestjs/throttler';
@Module({
imports: [
ThrottlerModule.forRoot([{
ttl: 60000, // 60 seconds
limit: 10, // 10 requests per minute
}]),
// ... other imports
],
})
export class AppModule {}Apply to controller:
import { UseGuards } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
@Controller('mms')
@UseGuards(ThrottlerGuard)
export class MmsController {
// ... controller methods
}3. Media Optimization
Optimize media before sending:
import sharp from 'sharp';
async optimizeImage(imageBuffer: Buffer): Promise<Buffer> {
return await sharp(imageBuffer)
.resize(1200, 1200, {
fit: 'inside',
withoutEnlargement: true,
})
.jpeg({ quality: 80 })
.toBuffer();
}Install sharp:
npm install sharp4. Logging and Monitoring
Implement structured logging:
this.logger.log({
event: 'mms_sent',
messageUuid: response.messageUuid,
to,
mediaCount: mediaUrls.length,
timestamp: new Date().toISOString(),
});Consider integrating with monitoring tools:
- Sentry: Error tracking
- Datadog: Performance monitoring
- CloudWatch: AWS infrastructure monitoring
5. Testing
Create comprehensive tests:
// src/mms/mms.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { MmsService } from './mms.service';
import { ConfigService } from '@nestjs/config';
describe('MmsService', () => {
let service: MmsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MmsService,
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const config = {
PLIVO_AUTH_ID: 'test_auth_id',
PLIVO_AUTH_TOKEN: 'test_auth_token',
PLIVO_PHONE_NUMBER: '+1234567890',
};
return config[key];
}),
},
},
],
}).compile();
service = module.get<MmsService>(MmsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('sendMMS', () => {
it('should send MMS successfully', async () => {
const mockResponse = { messageUuid: 'test-uuid-123' };
jest.spyOn(service['client'].messages, 'create').mockResolvedValue(mockResponse as any);
const result = await service.sendMMS(
'+1234567890',
'Test message',
['https://example.com/image.jpg'],
);
expect(result).toEqual(mockResponse);
});
it('should throw error for invalid media URL', async () => {
await expect(
service.sendMMS(
'+1234567890',
'Test',
['invalid-url'],
),
).rejects.toThrow();
});
});
});Run tests:
npm run test
# or
yarn testProduction Deployment Checklist
Before deploying to production:
- Environment Variables: Store credentials securely (use AWS Secrets Manager, Azure Key Vault, etc.)
- HTTPS: Enable HTTPS for webhook endpoints
- Error Tracking: Set up Sentry or similar service
- Rate Limiting: Implement throttling to prevent abuse
- Monitoring: Configure alerts for failed messages
- Database Backups: Automate regular backups of message records
- Load Testing: Test your application under expected traffic
- Logging: Set up centralized logging (ELK stack, CloudWatch)
- Documentation: Create API documentation (use Swagger/OpenAPI)
- Security: Implement webhook signature validation
- Cost Monitoring: Track Plivo usage and costs
- Retry Logic: Implement exponential backoff for failed sends
Frequently Asked Questions About Plivo MMS with NestJS
What is the maximum file size for Plivo MMS attachments?
Plivo supports up to 2 MB per attachment with a maximum of 10 attachments per message. The total message size (body text plus all attachments) must be less than 5 MB (source). Messages exceeding this limit will fail with Plivo error code 120. For optimal carrier delivery, keep attachments under 600 KB for US carriers and 1 MB for Canadian carriers (source).
Does Plivo support MMS in all countries?
No, MMS availability varies by country and carrier. Plivo primarily supports MMS for US and Canadian phone numbers. Check the Plivo messaging coverage page for specific country support. For international messaging, use SMS with shortened URLs linking to media content.
How long does Plivo store MMS media files?
Plivo stores media sent to customers for 1 year from the send date. Media uploaded via the Media API but not used in a message is automatically deleted after 6 hours (source). For long-term storage, implement your own media management system using cloud storage services like AWS S3 or Google Cloud Storage.
What NestJS version is required for Plivo integration?
You need NestJS 11 which requires Node.js v20 or higher (source). Node.js v16 and v18 are no longer supported as of NestJS 11. The Plivo Node.js SDK is compatible with these versions. Ensure your development environment meets these requirements before starting implementation.
How do you handle MMS delivery failures in NestJS?
Implement comprehensive error handling with try-catch blocks, validate media URLs before sending, set up webhook endpoints to receive delivery receipts, and store message status in a database for tracking. Use TypeORM or Prisma to persist delivery status updates. Consider implementing retry logic with exponential backoff for temporary failures.
Can you send MMS with multiple images using Plivo?
Yes, Plivo supports up to 10 attachments per MMS message (source). Pass an array of media URLs to the media_urls parameter when creating a message. Each attachment must be under 2 MB, and the total message size must remain under 5 MB. Ensure all URLs are publicly accessible via HTTPS for best carrier compatibility.
What image formats does Plivo MMS support?
Plivo supports JPEG, JPG, PNG, GIF, and BMP for images. For video, supported formats include MP4, 3GP, AVI, and MOV. Audio formats include MP3, WAV, AMR, and OGG. Always use standard formats and compress files appropriately to ensure maximum carrier compatibility and faster delivery times.
How do you validate Plivo webhook signatures in NestJS?
Generate an HMAC SHA1 signature using your Plivo auth token, the webhook URI, and sorted request parameters. Compare the computed signature with the X-Plivo-Signature header. This prevents unauthorized webhook requests. Use Node.js crypto module to create the HMAC and validate before processing delivery receipts.
What is the cost of sending MMS with Plivo?
MMS pricing varies by destination country and carrier. US MMS typically costs $0.005–$0.01 per message segment, with additional media hosting fees. Check your Plivo account dashboard for specific pricing for your use case. MMS messages with multiple attachments may incur higher costs than SMS.
How do you implement rate limiting for Plivo MMS in NestJS?
Use the @nestjs/throttler package to implement rate limiting at the controller level. Configure TTL (time to live) and request limits based on your application needs. This prevents API abuse and helps manage Plivo API costs. Consider implementing queue systems like Bull or BullMQ for high-volume message sending to better control throughput.
Conclusion
You now have a robust foundation for sending MMS messages with Plivo and NestJS. This guide covered:
- Setting up your NestJS project with Plivo SDK
- Implementing basic MMS sending functionality
- Adding advanced features: validation, delivery tracking, scheduling
- Database integration for message persistence
- Best practices for production deployment
- Troubleshooting common issues
Next Steps
- Explore Plivo's Other APIs: Voice calls, Verify API for 2FA
- Implement Message Templates: Create reusable message templates
- Add User Interface: Build a frontend with React/Angular/Vue
- Scale Your Application: Implement queues and worker processes for high volume
- Monitor Performance: Set up comprehensive monitoring and alerting
Additional Resources
Frequently Asked Questions
How to send MMS messages with NestJS and Plivo?
Integrate the Plivo Node.js SDK into your NestJS application. Create a Plivo service to handle API interactions, a controller with a POST endpoint, and a DTO for request validation. This allows you to securely send MMS messages using Plivo's robust infrastructure.
What is the Plivo Node.js SDK used for in NestJS?
The Plivo Node.js SDK simplifies interaction with the Plivo REST API within a NestJS application. It provides convenient methods to send MMS messages, manage phone numbers, and other Plivo functionalities, streamlining the integration process.
Why use NestJS for sending Plivo MMS messages?
NestJS provides a structured, modular architecture with dependency injection and TypeScript support, making it ideal for building robust and maintainable applications that interact with external APIs like Plivo.
When should I use media_urls versus media_ids with Plivo?
Use `media_urls` when your media (images, GIFs) are publicly accessible via URLs. Use `media_ids` if you've pre-uploaded media to Plivo's servers, referencing their unique identifiers. Uploading to Plivo first is useful for private or larger media files.
What are the Plivo MMS size and type limits?
Plivo generally allows around 2MB per media file and a maximum of 10 attachments per message. Supported types include common image formats like JPEG, PNG, and GIF. Check Plivo's official documentation for the most current limits and supported media types, as these can change.
How to set up environment variables for Plivo credentials in NestJS?
Create a `.env` file in your project root and store your Plivo Auth ID, Auth Token, and sender number. Import and configure the `@nestjs/config` module in your `app.module.ts` to load these variables securely. Add `.env` to your `.gitignore` file.
How to validate incoming MMS requests in NestJS?
Create a Data Transfer Object (DTO) and use `class-validator` decorators (e.g., `@IsPhoneNumber`, `@IsUrl`) to define validation rules. Apply the `ValidationPipe` in your controller or globally to automatically validate incoming requests.
How to handle Plivo API errors in a NestJS application?
Implement a `try...catch` block around your Plivo API calls in the service. Log the error details and throw a NestJS exception (e.g., `InternalServerErrorException`) to handle errors gracefully without exposing sensitive information to the client.
What is the purpose of the 202 Accepted status code?
The 202 Accepted status code signifies that the request to send the MMS has been accepted and is being processed asynchronously. It does not guarantee immediate delivery but confirms that the message is queued for sending.
Can I implement retry mechanisms for sending Plivo MMS?
Yes, using libraries like `async-retry` can help implement retry logic with exponential backoff. This improves the reliability of MMS delivery by handling transient network or API issues.
Why is MMS sending to other countries restricted with Plivo?
Plivo primarily offers MMS for US and Canadian numbers. Support for other countries may be limited or result in fallback to SMS. Verify Plivo's documentation for the latest list of supported countries.
How to improve performance when sending MMS with Plivo?
Leverage NestJS's asynchronous nature, initialize the Plivo client once, and avoid blocking operations. While caching is less applicable to sending, it could help with related tasks like user profile retrieval if required.
How to secure my Plivo MMS sending endpoint in NestJS?
Use environment variables for credentials, input validation with `ValidationPipe`, and implement rate limiting with `@nestjs/throttler`. If your API is public, add authentication/authorization using Guards.
What are the Plivo trial account limitations for sending MMS?
Trial accounts usually restrict MMS sending to verified numbers in your Plivo sandbox. Ensure you have added the recipient numbers to your verified list within the Plivo console.