code examples

Sent logo
Sent TeamMay 3, 2025 / code examples / Article

Send MMS Messages with NestJS and MessageBird: Complete Implementation Guide

Build a production-ready NestJS application to send MMS messages via MessageBird API. Includes code examples, error handling, security, testing, and deployment for US/Canada messaging.

Send MMS Messages with NestJS and MessageBird

This guide walks you through building a production-ready NestJS application that sends Multimedia Messaging Service (MMS) messages using the MessageBird API. You'll learn project setup, core implementation, error handling, security, testing, and deployment strategies.

By the end of this tutorial, you'll have a robust NestJS service and API endpoint to send MMS messages with media attachments to recipients in the US and Canada.

Target Audience: Developers familiar with Node.js, TypeScript, and NestJS. We assume basic REST API knowledge.

Technologies Used:

  • NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications
  • TypeScript: A typed superset of JavaScript that compiles to plain JavaScript
  • MessageBird SDK: The official Node.js library for interacting with the MessageBird API
  • dotenv: For managing environment variables
  • class-validator & class-transformer: For request payload validation

Project Overview and Goals

Goal: Create a reusable NestJS module that encapsulates MMS sending logic via MessageBird, exposed through a simple REST API endpoint.

Problem Solved: This structured, maintainable, and testable approach integrates MMS sending capabilities into any NestJS application, handling configuration, API interaction, and error management.

<!-- EXPAND: Could benefit from real-world use case examples showing when MMS is preferred over SMS (Type: Enhancement, Priority: Medium) -->

System Architecture:

+-------------+ +---------------------+ +-------------------+ +-----------------+ | Client | ---> | NestJS API Endpoint | ---> | NestJS MMS Service | ---> | MessageBird API | | (e.g., curl)| | (MMS Controller) | | (MessageBird SDK) | | (rest.messagebird.com/mms) | +-------------+ +---------------------+ +-------------------+ +-----------------+ | | | | POST /mms/send | Initializes SDK, | Sends MMS Request | (JSON Payload) | calls sendMms method | | | | Receives Response/Status |<--------------------|<----------------------------|<----------------- | | | | HTTP Response | Returns result/error | | (Success/Error) | | <!-- DEPTH: Architecture diagram lacks error flow paths and webhook integration points (Priority: Medium) -->

Prerequisites:

  • Node.js (LTS version recommended)
  • npm or yarn package manager
  • NestJS CLI installed (npm install -g @nestjs/cli)
  • A MessageBird account (Sign up here)
  • A MessageBird API Access Key (Live or Test)
  • An MMS-enabled virtual mobile number purchased from MessageBird (US/Canada only for MMS)
<!-- GAP: Missing version requirements for Node.js, NestJS CLI, and SDK compatibility matrix (Type: Critical, Priority: High) -->

1. Setting up the Project

Let's start by creating a new NestJS project and installing the necessary dependencies.

  1. Create a new NestJS project: Open your terminal and run:

    bash
    nest new nestjs-messagebird-mms
    cd nestjs-messagebird-mms
  2. Install Dependencies: We need the MessageBird SDK and modules for configuration and validation.

    bash
    npm install messagebird @nestjs/config dotenv class-validator class-transformer
    # or using yarn:
    # yarn add messagebird @nestjs/config dotenv class-validator class-transformer
<!-- GAP: Missing specific package version recommendations and compatibility notes (Type: Critical, Priority: High) -->
  1. Configure Environment Variables: Sensitive information like API keys should not be hardcoded. We'll use a .env file.

    • Create a file named .env in the project root directory.

    • Add the following variables, replacing the placeholder values with your actual MessageBird credentials:

      dotenv
      # .env
      
      # Obtain from MessageBird Dashboard: Developers -> API access -> Live API Key (or Test API Key)
      MESSAGEBIRD_ACCESS_KEY=YOUR_MESSAGEBIRD_ACCESS_KEY
      
      # Your MMS-enabled virtual number from MessageBird (E.164 format)
      # Obtain from MessageBird Dashboard: Numbers
      MESSAGEBIRD_ORIGINATOR=+1XXXXXXXXXX
    • Important: Add .env to your .gitignore file to prevent committing secrets.

      text
      # .gitignore (ensure this line exists or add it)
      .env
<!-- DEPTH: Environment configuration lacks guidance on multi-environment setup (dev/staging/prod) and secret management services (Priority: Medium) -->
  1. Set up Configuration Module: NestJS provides a ConfigModule to load environment variables.

    • Import and configure ConfigModule in your main application module (src/app.module.ts). Make it global so it's available everywhere.

      typescript
      // 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 will create this next
      
      @Module({
        imports: [
          ConfigModule.forRoot({
            isGlobal: true, // Make config available globally
            envFilePath: '.env', // Specify the env file path
          }),
          MmsModule, // Import our upcoming MMS module
        ],
        controllers: [AppController],
        providers: [AppService],
      })
      export class AppModule {}

    Why .env and ConfigModule? This approach separates configuration from code, making it easy to manage different environments (development, staging, production) and keeps sensitive credentials secure.


2. Implementing Core Functionality (MMS Service)

We'll create a dedicated service to handle the interaction with the MessageBird API.

  1. Generate the MMS Module and Service: Use the NestJS CLI to scaffold the necessary files.

    bash
    nest generate module mms
    nest generate service mms --no-spec # --no-spec skips generating a test file for now

    This creates src/mms/mms.module.ts and src/mms/mms.service.ts.

  2. Implement the MmsService: This service will initialize the MessageBird client and contain the logic for sending MMS messages.

    • Inject ConfigService to access environment variables.
    • Import the messagebird library.
    • Create a method sendMms that accepts recipient(s), message body, media URLs, and an optional subject.
    typescript
    // src/mms/mms.service.ts
    import { Injectable, Logger, InternalServerErrorException, BadRequestException } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import * as MessageBird from 'messagebird'; // Use namespace import
    
    @Injectable()
    export class MmsService {
      private readonly logger = new Logger(MmsService.name);
      private messagebird: MessageBird.MessageBird; // Use the correct type (Verify against SDK)
      private originator: string;
    
      constructor(private configService: ConfigService) {
        const accessKey = this.configService.get<string>('MESSAGEBIRD_ACCESS_KEY');
        this.originator = this.configService.get<string>('MESSAGEBIRD_ORIGINATOR');
    
        if (!accessKey || !this.originator) {
          this.logger.error('MessageBird Access Key or Originator not configured in .env file');
          throw new InternalServerErrorException('MMS service configuration is incomplete.');
        }
    
        // Initialize MessageBird client using initClient method
        this.messagebird = MessageBird.initClient(accessKey);
        this.logger.log('MessageBird client initialized.');
      }
    
      /**
       * Sends an MMS message using the MessageBird API.
       * @param recipients - Array of recipient phone numbers in E.164 format.
       * @param body - The text body of the MMS.
       * @param mediaUrls - Array of publicly accessible URLs for media attachments.
       * @param subject - (Optional) The subject line of the MMS.
       * @param reference - (Optional) A client reference string.
       * @returns The response object from the MessageBird API.
       * @throws BadRequestException if input validation fails.
       * @throws InternalServerErrorException if the API call fails.
       */
      async sendMms(
        recipients: string[],
        body: string,
        mediaUrls: string[],
        subject?: string,
        reference?: string,
      ): Promise<any> { // TODO: Define a specific TypeScript interface for the MessageBird MMS response instead of 'any'
    
        // Basic input validation (complementary to DTO validation)
        if (!body && (!mediaUrls || mediaUrls.length === 0)) {
           throw new BadRequestException('MMS must have either a body or at least one mediaUrl.');
        }
        if (mediaUrls && mediaUrls.length > 10) {
           // Note: DTO validation should ideally catch this first
           throw new BadRequestException('MMS cannot have more than 10 media attachments.');
        }
    
        const params: MessageBird.MmsParams = {
          originator: this.originator,
          recipients: recipients,
          body: body,
          mediaUrls: mediaUrls,
          subject: subject, // Optional
          reference: reference, // Optional
        };
    
        this.logger.log(`Attempting to send MMS to ${recipients.join(', ')} from ${this.originator}`);
    
        return new Promise((resolve, reject) => {
          // Note: Verify the exact method (e.g., `mms.create` or potentially `messages.create` with specific options)
          // against the current MessageBird Node.js SDK documentation, as SDKs can evolve.
          // This code assumes `mms.create` exists.
          this.messagebird.mms.create(params, (err, response) => {
            if (err) {
              this.logger.error('MessageBird API error:', err);
              // Provide more specific error handling based on err type if possible
              reject(new InternalServerErrorException(`Failed to send MMS: ${err.message || 'Unknown API error'}`));
            } else {
              this.logger.log(`MMS sent successfully. Message ID: ${response.id}`);
              resolve(response);
            }
          });
        });
      }
    }

    Why this structure? Encapsulating the MessageBird logic in a dedicated service makes the code modular, easier to test (by mocking the service), and reusable across different parts of your application (e.g., controllers, background jobs).

<!-- DEPTH: Service implementation lacks detailed error code handling from MessageBird API responses (Priority: High) --> <!-- GAP: Missing connection pooling configuration and SDK timeout settings (Type: Substantive, Priority: Medium) -->
  1. Update the MmsModule: Ensure the MmsService is provided and exported by the module.
    typescript
    // src/mms/mms.module.ts
    import { Module } from '@nestjs/common';
    import { MmsService } from './mms.service';
    // Import MmsController later when created
    // import { MmsController } from './mms.controller';
    
    @Module({
      // controllers: [MmsController], // Uncomment when controller is added
      providers: [MmsService],
      exports: [MmsService], // Export if needed by other modules
    })
    export class MmsModule {}

3. Building the API Layer (MMS Controller)

Now, let's expose the sendMms functionality through a REST API endpoint.

  1. Generate the MMS Controller:

    bash
    nest generate controller mms --no-spec

    This creates src/mms/mms.controller.ts.

  2. Create a Data Transfer Object (DTO) for Validation: DTOs define the expected shape of the request body and enable automatic validation using class-validator.

    • Create a directory src/mms/dto and a file send-mms.dto.ts inside it.
    typescript
    // src/mms/dto/send-mms.dto.ts
    import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; // For Swagger documentation
    import { IsArray, IsString, IsNotEmpty, IsPhoneNumber, ArrayMaxSize, ArrayMinSize, IsOptional, MaxLength, IsUrl, ValidateIf } from 'class-validator';
    
    export class SendMmsDto {
      @ApiProperty({
        description: 'Array of recipient phone numbers in E.164 format (e.g., +12223334444). Max 50.',
        example: ['+15551234567', '+15559876543'],
        type: [String],
      })
      @IsArray()
      @ArrayMinSize(1)
      @ArrayMaxSize(50) // Verify this limit with MessageBird docs
      @IsPhoneNumber(undefined, { each: true, message: 'Each recipient must be a valid phone number in E.164 format' })
      recipients: string[];
    
      @ApiPropertyOptional({
        description: 'The text body of the MMS message. Required if mediaUrls is empty or not provided. Max 2000 chars.',
        example: 'Check out this cool logo!',
        maxLength: 2000, // Verify this limit with MessageBird docs
      })
      @IsOptional()
      @ValidateIf(o => !o.mediaUrls || o.mediaUrls.length === 0) // Require body if mediaUrls is absent/empty
      @IsNotEmpty({ message: 'Body cannot be empty if mediaUrls is not provided or empty.' })
      @IsString()
      @MaxLength(2000)
      body?: string;
    
      @ApiPropertyOptional({
        description: 'Array of publicly accessible URLs for media attachments. Required if body is empty or not provided. Max 10 URLs.',
        example: ['https://developers.messagebird.com/img/logos/mb-400.jpg'],
        type: [String],
        maxLength: 10,
      })
      @IsOptional()
      @ValidateIf(o => !o.body) // Require mediaUrls if body is absent/empty
      @IsNotEmpty({ message: 'mediaUrls cannot be empty if body is not provided or empty.' })
      @IsArray()
      @ArrayMaxSize(10) // Verify this limit with MessageBird docs
      @IsUrl({}, { each: true, message: 'Each media URL must be a valid URL' })
      mediaUrls?: string[];
    
      @ApiPropertyOptional({
        description: 'The subject line of the MMS message. Max 256 chars.',
        example: 'Company Logo',
        maxLength: 256, // Verify this limit with MessageBird docs
      })
      @IsOptional()
      @IsString()
      @MaxLength(256)
      subject?: string;
    
      @ApiPropertyOptional({
        description: 'A client reference string for tracking.',
        example: 'campaign-xyz-123',
      })
      @IsOptional()
      @IsString()
      reference?: string;
    
      // Note: The validation logic to ensure *at least one* of body or mediaUrls is present
      // is now handled by the @ValidateIf decorators above. The service layer provides a fallback check.
    }

    Why DTOs? They provide clear contracts for your API, enable automatic request validation (preventing invalid data from reaching your service), and integrate well with tools like Swagger for API documentation. The @ValidateIf decorators allow conditional validation.

<!-- GAP: DTO lacks validation for media URL accessibility and sanitization patterns (Type: Substantive, Priority: Medium) -->
  1. Implement the MmsController:

    • Inject the MmsService.
    • Create a POST endpoint /mms/send.
    • Use the SendMmsDto with the @Body() decorator and enable validation globally.
    typescript
    // src/mms/mms.controller.ts
    import { Controller, Post, Body, ValidationPipe, UsePipes, Logger, HttpException, HttpStatus } from '@nestjs/common';
    import { MmsService } from './mms.service';
    import { SendMmsDto } from './dto/send-mms.dto';
    import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; // For Swagger
    
    @ApiTags('MMS') // Group endpoints in Swagger UI
    @Controller('mms')
    export class MmsController {
      private readonly logger = new Logger(MmsController.name);
    
      constructor(private readonly mmsService: MmsService) {}
    
      @Post('send')
      @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) // Enable validation
      @ApiOperation({ summary: 'Send an MMS message' }) // Swagger description
      @ApiBody({ type: SendMmsDto }) // Describe the body for Swagger
      @ApiResponse({ status: 201, description: 'MMS sent successfully, returns MessageBird response.' })
      @ApiResponse({ status: 400, description: 'Bad Request - Invalid input data.' })
      @ApiResponse({ status: 500, description: 'Internal Server Error - Failed to send MMS.' })
      async sendMms(@Body() sendMmsDto: SendMmsDto) {
        this.logger.log(`Received request to send MMS: ${JSON.stringify(sendMmsDto)}`);
        try {
          // DTO validation handles most cases, service layer handles the rest.
          const result = await this.mmsService.sendMms(
            sendMmsDto.recipients,
            sendMmsDto.body ?? '', // Provide empty string if body is undefined (should be validated by DTO)
            sendMmsDto.mediaUrls ?? [], // Provide empty array if mediaUrls is undefined (should be validated by DTO)
            sendMmsDto.subject,
            sendMmsDto.reference,
          );
          // Consider mapping the result to a cleaner response DTO if needed
          return result; // NestJS automatically sets status to 201 for POST
        } catch (error) {
          this.logger.error(`Error sending MMS: ${error.message}`, error.stack);
          if (error instanceof HttpException) {
            throw error; // Re-throw known HTTP exceptions (like BadRequestException from service)
          }
          // Throw a generic server error for unknown issues (e.g., SDK internal errors)
          throw new HttpException('Failed to send MMS due to an internal error.', HttpStatus.INTERNAL_SERVER_ERROR);
        }
      }
    }
<!-- EXPAND: Could benefit from response DTO example showing sanitized MessageBird response fields (Type: Enhancement, Priority: Low) -->
  1. Enable Global Validation Pipe: To make the ValidationPipe work automatically for all incoming requests with DTOs, add it in src/main.ts.

    typescript
    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { ValidationPipe, Logger } from '@nestjs/common';
    import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; // Import Swagger
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      const logger = new Logger('Bootstrap');
    
      // Enable global validation pipe
      app.useGlobalPipes(new ValidationPipe({
        whitelist: true, // Strip properties not defined in DTO
        transform: true, // Automatically transform payloads to DTO instances
        forbidNonWhitelisted: true, // Throw error if non-whitelisted properties are provided
        // Consider adding `disableErrorMessages: process.env.NODE_ENV === 'production'`
        // to avoid leaking validation details in production errors.
      }));
    
      // --- Swagger Setup ---
      const config = new DocumentBuilder()
        .setTitle('NestJS MessageBird MMS API')
        .setDescription('API for sending MMS messages via MessageBird')
        .setVersion('1.0')
        .addTag('MMS') // Match the tag used in the controller
        .build();
      const document = SwaggerModule.createDocument(app, config);
      SwaggerModule.setup('api-docs', app, document); // Serve Swagger UI at /api-docs
      logger.log('Swagger UI available at /api-docs');
      // --- End Swagger Setup ---
    
      const port = process.env.PORT || 3000;
      await app.listen(port);
      logger.log(`Application listening on port ${port}`);
    }
    bootstrap();
  2. Register the Controller: Add MmsController to the controllers array in src/mms/mms.module.ts.

    typescript
    // src/mms/mms.module.ts
    import { Module } from '@nestjs/common';
    import { MmsService } from './mms.service';
    import { MmsController } from './mms.controller'; // Import the controller
    
    @Module({
      imports: [], // ConfigModule is global, no need to import here
      controllers: [MmsController], // Add the controller
      providers: [MmsService],
      exports: [MmsService],
    })
    export class MmsModule {}

4. Integrating with Third-Party Services (MessageBird)

This section revisits the specifics of the MessageBird integration.

  • Configuration: Done via .env and ConfigModule (Section 1).
  • API Keys: Stored securely in .env and accessed via ConfigService.
    • How to obtain:
      1. Log in to your MessageBird Dashboard.
      2. Navigate to Developers in the left sidebar (Verify this path in the current Dashboard UI).
      3. Click on API access (Verify this path).
      4. Copy your Live API Key (or switch to Test mode for the Test API Key). Use the Live key for actual sending.
<!-- DEPTH: Lacks step-by-step screenshots or detailed navigation guidance for Dashboard (Priority: Low) -->
  • Originator Number: Stored in .env.
    • How to obtain/ensure MMS enabled:
      1. In the MessageBird Dashboard, navigate to Numbers (Verify this path).
      2. Purchase a US or Canadian number if you don't have one.
      3. Ensure the number's capabilities include MMS. Not all numbers support MMS. You might need to specifically look for or request MMS enablement for a number. Verify this process in the Dashboard.
<!-- GAP: Missing pricing information for MMS-enabled numbers and MMS message costs (Type: Substantive, Priority: Medium) -->
  • SDK Initialization: Handled in the MmsService constructor (Section 2).
  • API Interaction: Encapsulated within the MmsService.sendMms method (Section 2). Remember to verify the SDK method (mms.create) against current documentation.
  • Fallback Mechanisms: This simple example doesn't include automatic fallback (e.g., sending an SMS if MMS fails or is unsupported). For production, you might consider:
    • Checking recipient capabilities beforehand (if possible via MessageBird's Lookup API - separate integration).
    • Implementing logic to attempt SMS via messagebird.messages.create if the MMS call fails with specific error codes indicating incompatibility.
    • MMS Fallback Feature: MessageBird provides an MMS fallback toggle that, when enabled, automatically converts MMS to SMS with a URL to the original content when sending to non-US/Canada destinations. This can be configured in your MessageBird account settings.
<!-- EXPAND: Could benefit from code example showing SMS fallback implementation (Type: Enhancement, Priority: Medium) -->

5. Error Handling, Logging, and Retry Mechanisms

  • Consistent Error Strategy: We use NestJS's built-in HttpException for API-level errors (like validation failures handled by ValidationPipe or explicit BadRequestException throws in the service) and InternalServerErrorException for service-level or unexpected SDK/API errors. Custom Exception Filters can be created for more advanced, application-wide error formatting.
<!-- GAP: Missing comprehensive list of MessageBird error codes and corresponding handling strategies (Type: Critical, Priority: High) -->
  • Logging: NestJS's built-in Logger is used in the service and controller to log key events (initialization, requests, success, errors). For production, consider integrating more advanced logging libraries (like Pino or Winston) and sending logs to a centralized platform (e.g., Datadog, Logstash, CloudWatch).
    • Log Analysis: In a real system, you'd filter logs by context (MmsService, MmsController), timestamp, and severity (Error, Log, Warn) to trace requests and diagnose issues. Searching for specific MessageBird error messages or transaction IDs logged would be crucial.
<!-- DEPTH: Logging section lacks structured logging format examples and correlation ID implementation (Priority: Medium) -->
  • Retry Mechanisms:
    • MessageBird Internal Retries: MessageBird itself handles retries for delivering the message to the carrier network if temporary issues occur.
    • Application-Level Retries: Retrying the API call from our application might be necessary for transient network errors between our server and MessageBird. This example does not implement retries for the messagebird.mms.create call. For critical applications, you could add this using:
      • A library like nestjs-retry or async-retry.
      • Custom logic with exponential backoff within the MmsService.sendMms method's error handling block, carefully deciding which errors are retryable (e.g., network timeouts vs. invalid recipient errors).
<!-- GAP: Missing code example for retry implementation with exponential backoff (Type: Substantive, Priority: Medium) -->

6. Database Schema and Data Layer (Optional)

While not strictly required for sending an MMS, storing message details is vital for tracking, reporting, and correlating status updates (if receiving webhooks).

  • Why add a database?
    • Log every outgoing MMS attempt.
    • Store the messageId returned by MessageBird.
    • Record recipient, content, and timestamp.
    • Update the status based on MessageBird webhooks (requires implementing a webhook receiver endpoint).
<!-- GAP: Missing webhook implementation guide for status updates (Type: Critical, Priority: High) -->
  • Suggested Schema (using TypeORM entities as an example):

    typescript
    // src/mms/entities/mms-log.entity.ts (Example using TypeORM)
    import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index, UpdateDateColumn } from 'typeorm';
    
    // Note: Verify these statuses against actual MessageBird webhook documentation if implementing status updates.
    export enum MmsStatus {
      PENDING = 'pending', // Initial state before sending API call
      SENT = 'sent', // API call successful, MB accepted it
      DELIVERED = 'delivered', // Confirmed delivery via webhook
      FAILED = 'failed', // API call failed or delivery failed via webhook
      BUFFERED = 'buffered', // Status from MB
      EXPIRED = 'expired', // Status from MB
      REJECTED = 'rejected', // Status from MB
      // Add other relevant MessageBird statuses as needed
    }
    
    @Entity('mms_logs')
    export class MmsLog {
      @PrimaryGeneratedColumn('uuid')
      id: string;
    
      @Index()
      @Column({ nullable: true }) // Store the ID returned by MessageBird
      messageBirdId?: string;
    
      @Column('simple-array')
      recipients: string[];
    
      @Column()
      originator: string;
    
      @Column({ type: 'text', nullable: true })
      subject?: string;
    
      @Column({ type: 'text', nullable: true })
      body?: string;
    
      @Column('simple-array', { nullable: true })
      mediaUrls?: string[];
    
      @Column({ nullable: true })
      reference?: string;
    
      @Index()
      @Column({
        type: 'enum',
        enum: MmsStatus,
        default: MmsStatus.PENDING,
      })
      status: MmsStatus;
    
      @Column({ type: 'text', nullable: true }) // Store error details if sending failed
      errorMessage?: string;
    
      @CreateDateColumn()
      createdAt: Date;
    
      @UpdateDateColumn() // Automatically update timestamp on modification
      updatedAt: Date;
    
      @Column({ nullable: true }) // Store status update time from webhook explicitely if needed
      statusUpdatedAt?: Date;
    }
  • Implementation:

    1. Set up a database connection (e.g., using @nestjs/typeorm and pg or mysql2).
    2. Define the MmsLog entity.
    3. Inject the Repository<MmsLog> into MmsService.
    4. In sendMms:
      • Create and save an MmsLog entity with status: MmsStatus.PENDING before calling the MessageBird API.
      • If the API call succeeds, update the log entry with the messageBirdId and set status: MmsStatus.SENT.
      • If the API call fails, update the log entry with status: MmsStatus.FAILED and store the errorMessage.
  • Migrations: Use TypeORM migrations or Prisma Migrate to manage schema changes.

<!-- DEPTH: Database section lacks query optimization strategies and indexing best practices (Priority: Low) -->

7. Security Features

  • Input Validation: Handled by class-validator DTOs and the global ValidationPipe (Section 3). This prevents malformed data and potential injection attacks through request payloads. The whitelist: true and forbidNonWhitelisted: true options are crucial.
  • API Key Security: Storing the MESSAGEBIRD_ACCESS_KEY in .env and adding .env to .gitignore prevents accidental exposure (Section 1). Use environment variable management features of your deployment platform.
<!-- GAP: Missing guidance on API key rotation strategies and secret management services (AWS Secrets Manager, HashiCorp Vault) (Type: Substantive, Priority: High) -->
  • Rate Limiting: Protect your API from abuse. Use @nestjs/throttler to limit the number of requests per IP address or user.
    bash
    npm install @nestjs/throttler
    Configure it in app.module.ts:
    typescript
    // src/app.module.ts
    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
    import { APP_GUARD } from '@nestjs/core';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { MmsModule } from './mms/mms.module';
    
    @Module({
      imports: [
        ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
        ThrottlerModule.forRoot([{
          ttl: 60000, // Time-to-live in milliseconds (e.g., 60 seconds)
          limit: 10, // Max requests per TTL per IP (adjust as needed)
        }]),
        MmsModule,
        // HealthModule if added
      ],
      controllers: [AppController], // Add HealthController if added
      providers: [
        AppService,
        {
          provide: APP_GUARD, // Apply rate limiting globally
          useClass: ThrottlerGuard,
        },
      ],
    })
    export class AppModule {}
<!-- EXPAND: Rate limiting could benefit from Redis-based distributed rate limiting example (Type: Enhancement, Priority: Medium) -->
  • Authentication/Authorization: This guide assumes the API endpoint is internal or protected by other means (e.g., API Gateway, firewall). If exposed directly to end-users, implement proper authentication (e.g., JWT, OAuth) using NestJS Guards to ensure only authorized clients can send MMS.
<!-- GAP: Missing JWT/OAuth implementation examples for API endpoint protection (Type: Substantive, Priority: Medium) -->
  • HTTPS: Always deploy your NestJS application behind a reverse proxy (like Nginx or Caddy) configured for HTTPS, or use a PaaS that handles TLS termination.

8. Handling Special Cases

  • Media Attachments:
    • URL Accessibility: URLs provided in mediaUrls must be publicly accessible over the internet. MessageBird needs to fetch them.
    • Size Limits:
      • Total message size: Maximum 900 KB (including all attachments)
      • Individual attachment: Maximum 600 KB after resizing
      • Auto-resize support: Bird automatically resizes images and GIFs up to 5 MB (submitted size) to ensure delivery, though this may impact quality
    • File Types: MessageBird supports extensive MIME types including:
      • Images: JPEG, GIF, PNG, BMP
      • Video: MP4, QuickTime, WebM
      • Audio: MP3, WAV, OGG, and others
      • Text/Documents: vCard, CSV, RTF, calendar files, PDF
      • Unsupported media types will fail with error code UNSUPPORTED_MEDIA_TYPE: 14004
    • Count Limit: Maximum 10 media URLs per message (verified)
    • Fetch Timeout: MessageBird must fetch the media within 5 seconds (verified). Ensure your media hosting is fast and reliable.
<!-- DEPTH: Media handling lacks CDN configuration recommendations and media validation strategies (Priority: Medium) -->
  • Body and Media: At least one of body or mediaUrls must be provided. The DTO validation enforces this.
  • Character Limits: Subject (256 chars), Body (2000 chars) (verified). The DTO enforces this.
  • Recipient Format: Must be E.164 (e.g., +15551234567). The DTO validator checks this. Max 50 recipients per request (verified).
  • Originator: Must be an MMS-enabled number from your MessageBird account, formatted in E.164. Using an invalid or non-MMS originator will result in errors.
  • US/Canada Only: Sending MMS via MessageBird is currently restricted to these countries (verified). Attempting to send elsewhere will fail unless MMS fallback is enabled, which converts the message to SMS with a URL to the original content.
<!-- GAP: Missing international compliance considerations (GDPR, TCPA, carrier-specific requirements) (Type: Critical, Priority: High) -->

9. Performance Optimizations

  • Batching Recipients: The MessageBird API allows sending to up to 50 recipients (verified) in a single /mms POST request. If sending the same message to multiple users, batch them into arrays of up to 50 for the recipients parameter in your DTO/service call. This significantly reduces the number of API calls.
<!-- EXPAND: Could benefit from code example showing recipient batching implementation (Type: Enhancement, Priority: Medium) -->
  • Asynchronous Processing: For high-volume sending, consider moving the mmsService.sendMms call to a background job queue (e.g., using BullMQ and @nestjs/bull). The API endpoint would simply queue the job and return immediately (HTTP 202 Accepted), improving responsiveness. The background worker then handles the actual API interaction.
<!-- GAP: Missing complete BullMQ integration example with job queue configuration (Type: Substantive, Priority: High) -->

10. Monitoring, Observability, and Analytics

  • Health Checks: Implement a health check endpoint using @nestjs/terminus. This allows load balancers or monitoring systems to verify the application's status.
    bash
    npm install @nestjs/terminus @nestjs/axios # @nestjs/axios provides HttpModule
    Create a simple health controller:
    typescript
    // src/health/health.controller.ts
    import { Controller, Get } from '@nestjs/common';
    import { HealthCheckService, HttpHealthIndicator, HealthCheck, HealthCheckResult } from '@nestjs/terminus';
    import { ConfigService } from '@nestjs/config';
    import { Public } from '../auth/decorators/public.decorator'; // Example if you have global auth guard
    
    @Controller('health')
    export class HealthController {
      constructor(
        private health: HealthCheckService,
        private http: HttpHealthIndicator,
        private configService: ConfigService,
      ) {}
    
      @Get()
      @HealthCheck()
      // @Public() // Use if you have a global AuthGuard and need this public
      check(): Promise<HealthCheckResult> {
        // Check if the app itself is reachable (basic check on loopback)
        // Use the actual host/port if running behind proxy in production checks
        const url = `http://localhost:${this.configService.get('PORT', 3000)}`;
        return this.health.check([
          () => this.http.pingCheck('nestjs-app', url, { timeout: 2000 }),
          // Add other checks here, e.g., database connectivity, MessageBird API status (if available)
          // () => this.db.pingCheck('database'), // Example if using TypeOrmHealthIndicator
        ]);
      }
    }
    Remember to create a HealthModule, import TerminusModule and HttpModule, provide the HealthController, and import HealthModule into your AppModule.
<!-- DEPTH: Health checks lack custom MessageBird connectivity check implementation (Priority: Medium) -->
  • Structured Logging: As mentioned in Section 5, use structured logging (JSON format) with correlation IDs to trace requests across services.
  • Metrics: Integrate a metrics library (like prom-client for Prometheus) to expose key performance indicators (KPIs) such as request latency, error rates, MMS sent count, MessageBird API response times, and queue lengths (if using background jobs).
<!-- GAP: Missing Prometheus metrics implementation example with custom MMS metrics (Type: Substantive, Priority: Medium) -->
  • Distributed Tracing: For complex systems, implement distributed tracing (e.g., using OpenTelemetry) to visualize the entire lifecycle of a request as it flows through your NestJS app, queues, and external services like MessageBird.
  • Alerting: Set up alerts based on logs and metrics (e.g., high error rates, high latency, health check failures, low queue throughput) using tools like Prometheus Alertmanager, Grafana Alerting, or Datadog Monitors.
<!-- EXPAND: Could benefit from alerting rule examples for common MMS sending scenarios (Type: Enhancement, Priority: Low) -->

11. Testing Strategies

Testing is crucial for ensuring the reliability of your MMS sending functionality.

  • Unit Tests:
    • MmsService: Mock the ConfigService and the messagebird client. Test the sendMms method logic:
      • Verify it calls messagebird.mms.create with the correct parameters based on input.
      • Test input validation logic (e.g., throwing BadRequestException if body and media are missing).
      • Test handling of successful API responses (resolving the promise).
      • Test handling of API errors (rejecting the promise with InternalServerErrorException).
    • MmsController: Mock the MmsService. Test the sendMms endpoint:
      • Verify it calls mmsService.sendMms with data from the validated DTO.
      • Test successful response handling.
      • Test error handling (re-throwing HttpException or wrapping errors).
    • DTOs: Unit tests for custom class-validator decorators are possible but often covered by integration/e2e tests.
<!-- GAP: Missing complete unit test examples with mock implementations (Type: Substantive, Priority: High) -->
  • Integration Tests:
    • Test the interaction between the MmsController and MmsService without mocking the service entirely, but potentially still mocking the external MessageBird SDK call within the service. This ensures the controller correctly invokes the service and handles its responses/errors.
    • If using a database (Section 6), test the service's interaction with the MmsLog repository (e.g., verifying logs are created/updated correctly). Use a test database.
<!-- DEPTH: Integration testing lacks test database setup and teardown strategies (Priority: Medium) -->
  • End-to-End (E2E) Tests:
    • Use NestJS's built-in E2E testing framework (@nestjs/testing with Supertest).
    • Send actual HTTP requests to your running application's /mms/send endpoint.
    • Mocking External API: Crucially, mock the MessageBird API at the HTTP level (e.g., using nock or msw) or by providing a mock implementation of the messagebird client during test setup. Do not hit the actual MessageBird API in automated tests to avoid costs and side effects.
    • Test various scenarios: valid requests, invalid requests (triggering DTO validation errors), requests causing service errors (mocking MessageBird SDK errors), etc.
    • Assert the HTTP status codes and response bodies.
<!-- GAP: Missing E2E test examples with nock/msw setup for MessageBird API mocking (Type: Substantive, Priority: High) -->

12. Deployment Considerations

  • Environment Configuration: Use environment variables (.env locally, platform-specific configuration in production) for MESSAGEBIRD_ACCESS_KEY, MESSAGEBIRD_ORIGINATOR, PORT, database credentials, etc. Never commit .env files.
  • Build Process: Use npm run build to compile TypeScript to JavaScript (dist folder).
  • Running in Production: Run the compiled application using node dist/main.js. Use a process manager like PM2 or rely on your container orchestrator (Docker, Kubernetes) to manage the Node.js process (restarts, scaling).
<!-- DEPTH: Production deployment lacks PM2 ecosystem file configuration example (Priority: Low) -->
  • Containerization (Docker):
    • Create a Dockerfile to build an image containing your application code and dependencies.
    • Use a multi-stage build to keep the final image small.
    • Example Dockerfile:
      dockerfile
      # Stage 1: Build the application
      FROM node:18-alpine AS builder
      WORKDIR /usr/src/app
      COPY package*.json ./
      RUN npm ci --only=production --ignore-scripts --prefer-offline
      COPY . .
      RUN npm run build
      
      # Stage 2: Create the final production image
      FROM node:18-alpine
      WORKDIR /usr/src/app
      # Copy only necessary files from builder stage
      COPY --from=builder /usr/src/app/node_modules ./node_modules
      COPY --from=builder /usr/src/app/dist ./dist
      # Add package.json for metadata if needed, but node_modules are already copied
      # COPY --from=builder /usr/src/app/package.json ./
      
      # Expose the port the app runs on
      EXPOSE 3000
      
      # Command to run the application
      CMD ["node", "dist/main"]
    • Create a .dockerignore file similar to .gitignore to exclude unnecessary files from the build context.
<!-- GAP: Missing docker-compose.yml example for local development with database and Redis (Type: Substantive, Priority: Medium) -->
  • Platform Choices:
    • PaaS (Heroku, Vercel (for serverless functions), Render): Simpler deployment, often handle scaling, HTTPS, and environment variables.
    • IaaS (AWS EC2, Google Compute Engine): More control, requires manual setup of infrastructure (load balancers, databases, networking).
    • Container Orchestration (Kubernetes, AWS ECS, Google Cloud Run): Best for complex, scalable applications using containers.
<!-- EXPAND: Platform choices could benefit from cost comparison and use-case recommendations (Type: Enhancement, Priority: Low) -->
  • CI/CD: Implement a Continuous Integration/Continuous Deployment pipeline (e.g., using GitHub Actions, GitLab CI, Jenkins) to automate testing, building, and deploying your application on code changes.
<!-- GAP: Missing GitHub Actions workflow example for CI/CD pipeline (Type: Substantive, Priority: Medium) -->

Conclusion

You have successfully built a NestJS application capable of sending MMS messages using the MessageBird API. We covered:

  • Project setup with NestJS CLI and dependency management.
  • Secure configuration using .env and ConfigModule.
  • Modular service design (MmsService) for API interaction.
  • Robust API endpoint creation (MmsController) with DTO validation.
  • Integration specifics with MessageBird (API keys, originator).
  • Strategies for error handling, logging, and optional retries.
  • Optional database integration for logging and tracking.
  • Security best practices (validation, API keys, rate limiting, HTTPS).
  • Handling special cases and limitations of MMS/MessageBird.
  • Performance optimization techniques (batching, async processing).
  • Monitoring and observability considerations (health checks, metrics).
  • Comprehensive testing strategies (unit, integration, E2E).
  • Deployment considerations (Docker, platforms, CI/CD).

This foundation provides a reliable and maintainable way to incorporate MMS functionality into your NestJS projects. Remember to consult the official MessageBird API documentation for the latest details on limits, supported features, and error codes.

<!-- DEPTH: Conclusion lacks next steps guidance and links to advanced topics (Priority: Low) --> <!-- EXPAND: Could benefit from FAQ section addressing common implementation questions (Type: Enhancement, Priority: Medium) -->

Frequently Asked Questions

What is the role of ValidationPipe in MmsController?

ValidationPipe, used with DTOs like SendMmsDto, automatically validates incoming requests against specified constraints, ensuring data integrity and security.

How to send MMS messages with NestJS?

Use the MessageBird API and NestJS framework. Create a dedicated MmsService to interact with the API, a MmsController with DTO validation to create the API endpoint, and secure your API keys using .env and ConfigModule. Consider testing and error handling in a production environment.

What is MessageBird used for in NestJS?

MessageBird is a third-party service that handles sending MMS messages through its API. In NestJS, the MessageBird Node.js SDK is used within an MmsService to encapsulate API interaction logic.

Why use .env files for MessageBird API keys?

Storing API keys in .env files, separate from your code, enhances security. The ConfigModule loads these variables, ensuring sensitive credentials aren't hardcoded. This approach is also suitable for different environments, such as production and staging servers.

When to add database for NestJS MMS application?

While not strictly required for *sending* MMS, integrating a database (e.g., PostgreSQL or MySQL) using an ORM like TypeORM is recommended for tracking, reporting, and updating status, especially if you plan on using MessageBird webhooks to confirm message status.

How to install MessageBird SDK in NestJS?

Use npm or yarn. After creating a new NestJS project, run 'npm install messagebird @nestjs/config dotenv class-validator class-transformer' or 'yarn add messagebird @nestjs/config dotenv class-validator class-transformer' in your terminal.

What is the purpose of MmsService in NestJS?

MmsService encapsulates the core logic for interacting with the MessageBird API. It initializes the MessageBird client, includes the sendMms method, handles error management, and promotes modularity and testability.

What is class-validator used for with SendMmsDto?

Class-validator automatically validates request payloads defined by the SendMmsDto against specified constraints (phone number format, maximum lengths, etc.). This prevents malformed data from reaching your service.

How to validate request payload with class-validator?

Define a Data Transfer Object (DTO), such as SendMmsDto, using decorators from class-validator (e.g., IsString, IsPhoneNumber, MaxLength). Use the ValidationPipe in your controller or globally in main.ts to automatically validate incoming requests based on the DTO.

Why does NestJS use DTOs for MMS sending?

DTOs (Data Transfer Objects) define clear data structures for API requests, enabling automatic validation with class-validator and tools like Swagger documentation.

When should I use background jobs for sending MMS?

For high-volume MMS sending, offload the MmsService.sendMms call to a background job queue (e.g., using BullMQ and @nestjs/bull) to improve API responsiveness and handle processing asynchronously.

How to set up rate limiting for MMS API endpoint?

Use the @nestjs/throttler module. Import and configure it in app.module.ts, specifying TTL and request limits. Apply the ThrottlerGuard globally to protect your API from abuse by limiting the number of requests per IP or user.

How can I test MessageBird integration in NestJS?

Implement unit, integration, and E2E tests. Mock the MessageBird API for E2E tests using nock or msw, and use Supertest to send real HTTP requests and mock the API for unit and integration tests to avoid costs.

What deployment options are available for a NestJS MMS app?

Several platforms are suitable: PaaS (e.g., Heroku, Render) offer simpler deployment and scaling; IaaS (e.g., AWS EC2) provides more control but requires manual setup; and container orchestration (e.g., Kubernetes) is best for complex, scalable containerized deployments.

Can I send MMS to countries other than US/Canada?

Currently, sending MMS via MessageBird is limited to the US and Canada. This restriction may change, so refer to MessageBird's official documentation for updated regional availability.