messaging channels

Sent logo
Sent TeamMar 8, 2026 / messaging channels / Plivo

How to Send SMS with Plivo in NestJS: Complete TypeScript Integration Guide

Step-by-step guide to integrate Plivo SMS API with NestJS using TypeScript. Learn to send SMS messages, handle validation, manage errors, and deploy production-ready Node.js messaging features with the Plivo SDK.

This comprehensive guide shows you how to send SMS messages using Plivo's API integrated with NestJS. You'll build a production-ready REST API endpoint that sends text messages programmatically using the Plivo Node.js SDK and TypeScript. Perfect for adding SMS notifications, alerts, two-factor authentication (2FA), or verification codes to your NestJS applications.

Whether you're building user authentication flows, automated alerts, or customer engagement features, this tutorial covers everything from initial setup through production deployment with proper error handling and security best practices.

Technologies Used:

  • NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications. Its modular architecture and built-in features (like configuration management and validation pipes) make integration straightforward.
  • Plivo: A cloud communications platform providing APIs for SMS, voice, and more. We'll use their Node.js SDK (specifically v3, as indicated by the API usage in this guide).
  • Node.js: The underlying JavaScript runtime environment.
  • TypeScript: Superset of JavaScript used by NestJS for strong typing.
  • dotenv: For managing environment variables locally.

System Architecture:

text
+-------------+       +---------------------+       +-----------------+       +--------------+
|  Client     |------>|  NestJS API         |------>|  Plivo Service  |------>|  Plivo API   |
| (e.g., curl,|       |  (Controller)       |       |  (NestJS Service|       |  (SMS Gateway)|
| Postman)    |       |  - POST /sms/send   |       |   Wrapper)      |       +--------------+
|             |       |  - Request Validation |       |   - Plivo SDK   |
+-------------+       +---------------------+       +-----------------+
                         | Uses ConfigService  |
                         +---------------------+
                         | Reads .env vars     |
                         +---------------------+

(Note: Ensure the ASCII diagram renders correctly in your Markdown viewer; fixed-width fonts are required.)

What You'll Learn:

By the end of this guide, you will have a running NestJS application with a single API endpoint (POST /sms/send) capable of sending SMS messages via Plivo. You'll understand how to configure Plivo credentials, structure the integration within NestJS, handle basic requests, and manage essential configurations securely.

Prerequisites:

  • Node.js and npm/yarn: Ensure you have Node.js (LTS version recommended) and a package manager installed. (Node.js Downloads)
  • NestJS CLI: Install the NestJS command-line tool globally: npm install -g @nestjs/cli
  • Plivo Account: Sign up for a Plivo account. (Plivo Signup)
  • Basic TypeScript/JavaScript Knowledge: Familiarity with modern JavaScript (ES6+) and TypeScript basics.
  • Terminal/Command Line Access: Comfortable using the command line for project setup and running commands.

How to Set Up Your NestJS Project for Plivo SMS Integration

Let's start by creating a new NestJS project and installing necessary dependencies for Plivo SMS functionality.

  1. Create a New NestJS Project: Open your terminal and run the NestJS CLI command to generate a new project. We'll call it plivo-sms-app.

    bash
    nest new plivo-sms-app

    Choose your preferred package manager (npm or yarn) when prompted.

  2. Navigate into Project Directory: Change into the newly created project folder.

    bash
    cd plivo-sms-app
  3. Install Plivo Node.js SDK: Add the official Plivo Node.js helper library to your project. This guide assumes usage compatible with Plivo Node SDK v3.x.

    bash
    npm install plivo
    # or
    yarn add plivo
  4. Install Configuration Module: NestJS provides a dedicated module for handling environment variables and configuration, which is crucial for managing API keys securely.

    bash
    npm install @nestjs/config
    # or
    yarn add @nestjs/config
  5. Install Validation Dependencies: We'll use class-validator and class-transformer for request body validation.

    bash
    npm install class-validator class-transformer
    # or
    yarn add class-validator class-transformer

Project Structure Explanation:

  • src/: Contains your application's core TypeScript code.
    • app.module.ts: The root module of the application.
    • main.ts: The entry point file that bootstraps the application.
    • Other modules, controllers, services will reside here.
  • .env: (We will create this) Stores environment variables locally (DO NOT commit this file to version control).
  • package.json: Lists project dependencies and scripts.
  • tsconfig.json: TypeScript compiler configuration.

Configuration Choices:

  • Using @nestjs/config allows us to load environment variables from a .env file during development and directly from the environment in production, providing a consistent way to access configuration values like API keys.
  • Using class-validator and class-transformer integrates seamlessly with NestJS's ValidationPipe for automatic request payload validation based on Data Transfer Objects (DTOs).

How to Get Plivo API Credentials and Phone Numbers

Before writing code, you need your Plivo API credentials and an SMS-enabled phone number.

  1. Find Auth ID and Auth Token:

    • Log in to your Plivo Console.
    • On the main dashboard homepage, you'll find your Auth ID and Auth Token. These are like your username and password for the Plivo API. Keep them secure.
  2. Get a Plivo Phone Number:

    • You need a Plivo phone number capable of sending SMS messages. For sending to US/Canada numbers, a Plivo number is mandatory. For other countries, you might use an Alphanumeric Sender ID (configurable in the Plivo console), but a number often provides better deliverability and enables two-way communication.
    • Navigate to Phone Numbers -> Buy Numbers in the Plivo console.
    • Search for numbers based on country, features (ensure SMS is checked), and type.
    • Purchase a number suitable for your needs. Note this number down.
    • Trial Account Limitation: If you are using a Plivo trial account, you can typically only send SMS messages to phone numbers that you have explicitly verified within the Plivo console (Phone Numbers -> Sandbox Numbers).
  3. Configure Environment Variables:

    • Create a file named .env in the root directory of your plivo-sms-app project.
    • Add your Plivo credentials and the Plivo phone number (or Sender ID) you obtained:
    dotenv
    # .env
    PLIVO_AUTH_ID=YOUR_PLIVO_AUTH_ID
    PLIVO_AUTH_TOKEN=YOUR_PLIVO_AUTH_TOKEN
    PLIVO_SENDER_ID=YOUR_PLIVO_PHONE_NUMBER_OR_SENDER_ID
    • Replace YOUR_PLIVO_AUTH_ID, YOUR_PLIVO_AUTH_TOKEN, and YOUR_PLIVO_PHONE_NUMBER_OR_SENDER_ID with your actual values.

    • Important: Add .env to your .gitignore file to prevent accidentally committing sensitive credentials to version control.

      text
      # .gitignore (add this line if not present)
      .env

Environment Variable Explanation:

  • PLIVO_AUTH_ID: Your main account identifier for Plivo API authentication.
  • PLIVO_AUTH_TOKEN: Your secret token for Plivo API authentication.
  • PLIVO_SENDER_ID: The phone number (in E.164 format, e.g., +14155551212) or Alphanumeric Sender ID (3-11 alphanumeric characters, no spaces) that will appear as the sender of the SMS.

How to Create a Plivo Service in NestJS

We'll create a dedicated NestJS service to encapsulate the logic for interacting with the Plivo API. This promotes modularity and reusability.

  1. Generate the Service: Use the NestJS CLI to generate a service named plivo.

    bash
    nest g service plivo --no-spec

    This creates src/plivo/plivo.service.ts.

  2. Implement the Plivo Service: Open src/plivo/plivo.service.ts and modify it as follows:

    typescript
    // src/plivo/plivo.service.ts
    import { Injectable, Logger, InternalServerErrorException, BadRequestException } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import * as plivo from 'plivo';
    
    @Injectable()
    export class PlivoService {
      private readonly logger = new Logger(PlivoService.name);
      private client: plivo.Client;
      private senderId: string;
    
      constructor(private configService: ConfigService) {
        const authId = this.configService.get<string>('PLIVO_AUTH_ID');
        const authToken = this.configService.get<string>('PLIVO_AUTH_TOKEN');
        this.senderId = this.configService.get<string>('PLIVO_SENDER_ID');
    
        if (!authId || !authToken || !this.senderId) {
          this.logger.error('Plivo credentials or Sender ID not configured in environment variables.');
          // Throwing an error during startup is appropriate if core config is missing.
          throw new Error('Plivo configuration is missing. Check PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, PLIVO_SENDER_ID environment variables.');
        }
    
        // This guide uses the Plivo Node SDK v3 syntax
        this.client = new plivo.Client(authId, authToken);
        this.logger.log('Plivo client initialized.');
      }
    
      /**
       * Sends an SMS message using the Plivo API.
       * @param to The recipient's phone number in E.164 format (e.g., +12025551234).
       * @param text The content of the SMS message.
       * @returns The response from the Plivo API.
       * @throws BadRequestException if input validation fails (to, senderId format).
       * @throws InternalServerErrorException if the message fails to send via Plivo API.
       */
      async sendSms(to: string, text: string): Promise<plivo.MessageCreateResponse> {
        this.logger.log(`Attempting to send SMS to ${to}`);
    
        // Basic E.164 format check for recipient (see E.164 phone format guide for details)
        if (!/^\+[1-9]\d{1,14}$/.test(to)) {
             this.logger.warn(`Invalid 'to' phone number format provided: ${to}. Expected E.164.`);
             throw new BadRequestException(`Invalid recipient phone number format: ${to}. Use E.164 format (e.g., +1xxxxxxxxxx).`);
        }
    
        // Validate Sender ID format (E.164 OR Alphanumeric)
        const isE164 = /^\+[1-9]\d{1,14}$/.test(this.senderId);
        const isAlphanumeric = /^[a-zA-Z0-9]{3,11}$/.test(this.senderId); // 3-11 Alphanumeric chars, no spaces
    
        if (!isE164 && !isAlphanumeric) {
            this.logger.warn(`Invalid 'senderId' format configured: ${this.senderId}. Expected E.164 (e.g., +1...) or Alphanumeric (3-11 chars, no spaces). Check PLIVO_SENDER_ID.`);
            // Throw BadRequest as it's an input configuration issue detectable before API call
            throw new BadRequestException(`Invalid sender ID format: ${this.senderId}. Use E.164 format or Alphanumeric Sender ID (3-11 chars, no spaces).`);
        }
    
    
        try {
          // Using Plivo Node SDK v3 style: client.messages.create(...)
          const response = await this.client.messages.create(
            this.senderId, // src
            to,           // dst
            text,         // text
          );
    
          // Plivo API V3 returns messageUuid as an array of strings
          this.logger.log(`SMS submitted successfully to ${to}. Message UUID: ${response.messageUuid[0]}`);
          // console.log('Plivo API Response:', response); // Optional: Log full response for debugging
          return response;
    
        } catch (error) {
          this.logger.error(`Failed to send SMS to ${to}: ${error.message}`, error.stack);
          // Log the detailed error from Plivo if available (often in error.response.data for axios errors)
          if (error.response && error.response.data) {
            this.logger.error(`Plivo API Error Details: ${JSON.stringify(error.response.data)}`);
          } else {
             this.logger.error(`Plivo Error Object: ${JSON.stringify(error)}`); // Log the error object itself
          }
          // Throw a NestJS specific exception for consistent API error handling
          throw new InternalServerErrorException(`Failed to send SMS via Plivo: ${error.message}`);
        }
      }
    }

Code Explanation:

  • Dependencies: We import necessary modules from @nestjs/common (including BadRequestException), @nestjs/config, and the plivo SDK.
  • Logger: A NestJS Logger instance is created for logging service activity.
  • Constructor:
    • Injects ConfigService to access environment variables.
    • Retrieves PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, and PLIVO_SENDER_ID.
    • Includes a crucial check to ensure these variables are present, throwing an error during startup if not.
    • Initializes the plivo.Client with the fetched credentials. Notes that this uses Plivo Node SDK v3.
  • sendSms Method:
    • Takes the recipient number (to) and message text as arguments.
    • Logs the attempt.
    • Includes validation for the to number format (E.164) and senderId format (E.164 or Alphanumeric 3-11 chars). Throws BadRequestException if formats are invalid, preventing unnecessary API calls.
    • Uses async/await to call this.client.messages.create() (v3 SDK method).
      • src: The sender ID/number configured in .env.
      • dst: The recipient number passed to the method.
      • text: The message content.
    • Logs the success message, including the messageUuid returned by Plivo (useful for tracking; v3 returns it as an array).
    • Includes a try...catch block for robust error handling:
      • Logs detailed error information, including the specific error from the Plivo SDK if possible.
      • Throws a NestJS InternalServerErrorException, which results in a standard 500 error response from our API if sending fails during the Plivo API interaction.

How to Build the SMS API Endpoint with Controller and DTO

Now, let's create the API endpoint that external clients can call to trigger the SMS sending process.

  1. Generate the Controller: Use the NestJS CLI to generate a controller named sms.

    bash
    nest g controller sms --no-spec

    This creates src/sms/sms.controller.ts.

  2. Create the Data Transfer Object (DTO): DTOs define the expected shape of the request body and enable automatic validation. Create a file src/sms/dto/send-sms.dto.ts:

    typescript
    // src/sms/dto/send-sms.dto.ts
    import { IsString, IsNotEmpty, Matches, MaxLength } from 'class-validator';
    
    export class SendSmsDto {
      @IsString()
      @IsNotEmpty()
      @Matches(/^\+[1-9]\d{1,14}$/, { // E.164 format regex
          message: 'Recipient phone number must be in E.164 format (e.g., +12025551234).',
      })
      to: string;
    
      @IsString()
      @IsNotEmpty()
      @MaxLength(1600) // Plivo allows long messages (~1600 chars), but good practice to have a limit
      text: string;
    }
    • @IsString(): Ensures the value is a string.
    • @IsNotEmpty(): Ensures the value is not empty.
    • @Matches(): Validates the to field against the E.164 regex pattern.
    • @MaxLength(): Sets a maximum length for the message text.
  3. Implement the SMS Controller: Open src/sms/sms.controller.ts and modify it:

    typescript
    // src/sms/sms.controller.ts
    import { Controller, Post, Body, Logger, ValidationPipe, UsePipes, HttpException } from '@nestjs/common';
    import { PlivoService } from '../plivo/plivo.service';
    import { SendSmsDto } from './dto/send-sms.dto';
    
    @Controller('sms') // Route prefix: /sms
    export class SmsController {
      private readonly logger = new Logger(SmsController.name);
    
      constructor(private readonly plivoService: PlivoService) {}
    
      @Post('send') // Full route: POST /sms/send
      // Apply validation pipe (can also be applied globally in main.ts)
      @UsePipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }))
      async sendSms(@Body() sendSmsDto: SendSmsDto) {
        this.logger.log(`Received request to send SMS to: ${sendSmsDto.to}`);
    
        try {
          const result = await this.plivoService.sendSms(sendSmsDto.to, sendSmsDto.text);
          return {
            message: 'SMS submitted successfully.',
            // Only return necessary info, avoid leaking full Plivo response unless needed
            // Access the first element as Plivo v3 returns messageUuid as an array
            plivoMessageId: result.messageUuid[0],
          };
        } catch (error) {
          // Log the error within the controller context
          this.logger.error(`Error in sendSms endpoint: ${error.message}`, error.stack);
    
          // If the error is already an HttpException (like BadRequestException or
          // InternalServerErrorException thrown from the service), re-throw it directly.
          // Otherwise, wrap it or let NestJS handle it (which might default to 500).
          if (error instanceof HttpException) {
            throw error;
          }
          // For unexpected errors not originating from our explicit throws in the service
          throw new HttpException(error.message || 'An unexpected error occurred', 500);
        }
      }
    }

Code Explanation:

  • @Controller('sms'): Defines the base route for this controller as /sms.
  • Constructor: Injects the PlivoService we created earlier.
  • @Post('send'): Decorator marks the sendSms method to handle HTTP POST requests to the /sms/send endpoint.
  • @Body() sendSmsDto: SendSmsDto: This decorator tells NestJS to parse the request body and assign it to the sendSmsDto parameter. Crucially, it expects the body to conform to the SendSmsDto structure.
  • @UsePipes(new ValidationPipe(...)): This applies the validation pipe directly to this endpoint. The ValidationPipe uses the decorators in SendSmsDto (class-validator) to validate the incoming request body before the method logic runs.
    • whitelist: true: Strips any properties from the request body that are not defined in the DTO.
    • forbidNonWhitelisted: true: Throws an error if extra properties (not in the DTO) are present.
    • transform: true: Attempts to transform the plain JS object payload into an instance of the DTO class.
  • Logic:
    • Logs the incoming request.
    • Calls this.plivoService.sendSms with the validated to and text from the DTO.
    • Returns a simple success message along with the Plivo message UUID (accessing the first element for v3).
    • Includes a try...catch block:
      • Logs errors that occur during the service call.
      • Re-throws HttpException instances (like BadRequestException or InternalServerErrorException thrown from the service) directly, allowing NestJS's exception filter to handle them correctly.
      • Catches any other unexpected errors and throws a generic HttpException with status 500.

How to Configure NestJS Modules for Plivo Integration

We need to ensure NestJS knows about our new service, controller, and the configuration module.

  1. Update App Module (src/app.module.ts): Modify your main application module to import ConfigModule and register the PlivoService and SmsController.

    typescript
    // src/app.module.ts
    import { Module } from '@nestjs/common';
    import { ConfigModule } from '@nestjs/config';
    import { AppController } from './app.controller'; // Default controller
    import { AppService } from './app.service';       // Default service
    import { PlivoService } from './plivo/plivo.service';
    import { SmsController } from './sms/sms.controller';
    
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true, // Makes ConfigService available globally
          envFilePath: '.env', // Specify the env file path
          // You might add validation schema here for required env vars
        }),
      ],
      controllers: [
        AppController, // Keep or remove default controller as needed
        SmsController, // Register our SMS controller
      ],
      providers: [
        AppService,    // Keep or remove default service as needed
        PlivoService,  // Register our Plivo service
      ],
    })
    export class AppModule {}

Explanation:

  • ConfigModule.forRoot({...}): Initializes the configuration module.
    • isGlobal: true: Makes the ConfigService available for injection in any module without needing to import ConfigModule everywhere.
    • envFilePath: '.env': Tells the module to load variables from the .env file. In production, it will automatically pick up system environment variables if the file isn't found.
  • controllers: [..., SmsController]: Registers our SmsController so NestJS maps routes to it.
  • providers: [..., PlivoService]: Registers our PlivoService as an injectable provider, making it available for dependency injection (e.g., in SmsController).
  1. Enable Global Validation Pipe (Optional but Recommended): Instead of applying @UsePipes to every controller method, you can enable the ValidationPipe globally in src/main.ts. This ensures all incoming requests that use DTOs are automatically validated.

    typescript
    // src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { Logger, ValidationPipe } from '@nestjs/common'; // Import ValidationPipe
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      const logger = new Logger('Bootstrap'); // Create logger instance for bootstrap
    
      // Enable global validation pipe
      app.useGlobalPipes(
        new ValidationPipe({
          whitelist: true, // Strip properties not in DTO
          forbidNonWhitelisted: true, // Throw error if extra properties are sent
          transform: true, // Automatically transform payloads to DTO instances
          transformOptions: {
            enableImplicitConversion: true, // Allow basic type conversion (e.g., string query param to number if expected)
          },
        }),
      );
    
      const port = process.env.PORT || 3000; // Use PORT from env or default to 3000
      await app.listen(port);
      logger.log(`Application listening on port ${port}`); // Use logger
    }
    bootstrap();

    If you enable it globally here, you can remove the @UsePipes(...) decorator from the SmsController method.


How to Test Your Plivo SMS Integration

With the code in place, let's run the application and test the endpoint.

  1. Start the Development Server: Run the following command in your terminal from the project root directory (plivo-sms-app):

    bash
    npm run start:dev
    # or
    yarn start:dev

    This command starts the NestJS application in watch mode, meaning it will automatically recompile and restart when you save code changes. You should see logs indicating the application has started, the Plivo client initialized, and it's listening on a port (default 3000). Ensure there are no startup errors related to missing Plivo configuration.

  2. Test with cURL or Postman: Open another terminal window or use a tool like Postman to send a POST request to your running application.

    Using cURL:

    bash
    curl -X POST http://localhost:3000/sms/send \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+1TARGET_PHONE_NUMBER",
      "text": "Hello from NestJS and Plivo! This is a test message."
    }'
    • Replace +1TARGET_PHONE_NUMBER with a valid phone number in E.164 format. Remember: If using a Plivo trial account, this number must be verified in your Plivo console's Sandbox Numbers section.
    • Make sure the Content-Type header is set correctly to application/json.
    • The -d flag provides the JSON request body.

    Expected Success Response (JSON, Status 201 Created or 200 OK):

    json
    {
      "message": "SMS submitted successfully.",
      "plivoMessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }

    You should also see logs in your NestJS application terminal indicating the request was received and the SMS was submitted successfully. The target phone should receive the SMS shortly after (delivery depends on Plivo and carrier networks).

    Testing Validation (assuming global pipe or @UsePipes is active):

    • Missing Field:

      bash
      curl -X POST http://localhost:3000/sms/send -H "Content-Type: application/json" -d '{"text": "Missing recipient"}'

      Expected Error Response (400 Bad Request): (Messages might vary slightly based on validator versions)

      json
      {
        "statusCode": 400,
        "message": [
          "Recipient phone number must be in E.164 format (e.g., +12025551234).",
          "to should not be empty",
          "to must be a string"
        ],
        "error": "Bad Request"
      }
    • Invalid Phone Format:

      bash
      curl -X POST http://localhost:3000/sms/send -H "Content-Type: application/json" -d '{"to": "12345", "text": "Invalid number format"}'

      Expected Error Response (400 Bad Request):

      json
      {
        "statusCode": 400,
        "message": [
          "Recipient phone number must be in E.164 format (e.g., +12025551234)."
        ],
        "error": "Bad Request"
      }
    • Extra Field (if forbidNonWhitelisted: true):

      bash
      curl -X POST http://localhost:3000/sms/send -H "Content-Type: application/json" -d '{"to": "+1TARGET_PHONE_NUMBER", "text": "Test", "extraField": "should_fail"}'

      Expected Error Response (400 Bad Request):

      json
      {
        "statusCode": 400,
        "message": [
          "property extraField should not exist"
        ],
        "error": "Bad Request"
      }

Best Practices for Error Handling and Logging

We've implemented layered error handling:

  • Request Validation Errors: Handled automatically by ValidationPipe. NestJS returns a 400 Bad Request response with detailed error messages based on the DTO decorators.
  • Input Format Errors (Service Level): The PlivoService checks to and senderId formats before calling the API. If invalid, it throws a BadRequestException, resulting in a 400 Bad Request response.
  • Plivo API Errors: Caught within the PlivoService.
    • A detailed error is logged using Logger.error, including Plivo's response data if available.
    • An InternalServerErrorException is thrown.
    • NestJS's default exception filter catches this and returns a 500 Internal Server Error response. The response body usually includes the status code and a generic error message unless detailed error exposure is enabled (not recommended for production).
  • Configuration Errors: Checked during PlivoService initialization. If critical config (Auth ID/Token/Sender ID) is missing, an Error is thrown, which typically stops the application startup process – this is desirable as the core function cannot operate.
  • Logging: The built-in @nestjs/common Logger is used.
    • Logs key events like initialization, request reception, SMS sending attempts, successes (with Message UUID), and failures (with error messages and stack traces).
    • In production, configure more robust logging:
      • Log Levels: Control verbosity (e.g., INFO, WARN, ERROR).
      • Log Format: JSON for easier parsing by log aggregation tools (e.g., Datadog, Splunk, ELK).
      • Log Destinations: Files, standard output/error, or external services. Libraries like pino or winston integrated with NestJS are common.

Testing Error Scenarios:

  • Invalid Credentials: Temporarily change PLIVO_AUTH_ID or PLIVO_AUTH_TOKEN in .env and restart. Send a request – it should fail with a 500 response, and logs should show Plivo authentication errors.
  • Invalid Sender ID Format: Set PLIVO_SENDER_ID to an invalid format (e.g., INVALID SENDER or +123) in .env and restart. Send a request – it should fail with a 400 response due to the service-level validation.
  • Trial Account Limitation: Using a trial account, try sending to a non-verified number. Plivo should reject it, resulting in an error logged by the service and a 500 response from the API.

Security Best Practices for Production SMS APIs

For production environments, enhance security:

  • Credential Management:
    • NEVER hardcode credentials in source code.
    • Use environment variables managed securely by your deployment platform (e.g., AWS Secrets Manager, GCP Secret Manager, Kubernetes Secrets, Heroku Config Vars).
    • Ensure .env is in .gitignore.
  • Input Validation:
    • Robust DTO validation (ValidationPipe) is essential.
    • Service-level validation (like senderId format) adds another layer.
  • Rate Limiting:
  • Authentication/Authorization:
    • Protect the /sms/send endpoint. Only authorized clients/users should be able to trigger SMS. Use API Keys, JWT, OAuth, etc., leveraging NestJS modules like @nestjs/passport or @nestjs/jwt.
  • HTTPS: Always serve your application over HTTPS in production.

Common Issues and How to Fix Them

  • Trial Account Limitations: SMS only deliverable to verified "Sandbox Numbers". Check Plivo console.
  • Incorrect Credentials: Double-check PLIVO_AUTH_ID / PLIVO_AUTH_TOKEN in environment variables match Plivo Console exactly. Expect 401 errors from Plivo (leading to 500 from API).
  • Invalid Sender ID (src number): Ensure PLIVO_SENDER_ID is a valid, owned Plivo number (E.164) or registered Alphanumeric Sender ID. Format errors should be caught by service validation (400 response); Plivo API might reject otherwise (500 response).
  • Invalid Recipient Number (dst number): Ensure to is E.164. DTO validation should catch most format issues (400 response). Plivo API performs further checks.
  • Carrier Filtering: SMS marked as sent by Plivo but not delivered. Check Plivo logs for delivery status. May involve carrier spam filters, content issues, or A2P 10DLC compliance (US). Contact Plivo support if persistent.
  • Rate Limits Exceeded: Plivo API returns 429 errors. Implement application-level rate limiting and potentially retry logic with backoff in PlivoService.
  • Network Issues: Ensure server has outbound connectivity to api.plivo.com (port 443/HTTPS). Check firewalls.

Deploying Your NestJS Plivo SMS Application

  • Environment Variables: Configure PLIVO_AUTH_ID, PLIVO_AUTH_TOKEN, PLIVO_SENDER_ID securely in the production environment. Do not deploy .env.
  • Build: Use npm run build or yarn build to create the dist folder.
  • Run: Start with node dist/main.js. Use a process manager like pm2 or rely on platform features (Docker, Kubernetes, PaaS).
  • HTTPS: Use a reverse proxy (Nginx, Caddy) or platform features for SSL/TLS.
  • Logging & Monitoring: Integrate production-grade tools.

(CI/CD setup would involve steps like linting, testing, building, artifact storage, and deployment.)


Frequently Asked Questions About Plivo NestJS Integration

Q: Can I use Plivo with other Node.js frameworks besides NestJS? A: Yes, the Plivo Node.js SDK works with Express, Fastify, Koa, and other frameworks. See our guides for Plivo with Express and Plivo with Fastify.

Q: How much does it cost to send SMS with Plivo? A: Plivo pricing varies by destination country and message volume. Check Plivo's pricing page for current rates. Trial accounts include free credits.

Q: What's the difference between sending SMS to US numbers vs international numbers? A: US/Canada SMS typically requires 10DLC registration for application-to-person messaging. International SMS may use Alphanumeric Sender IDs in some countries for better brand recognition.

Q: How do I handle incoming SMS messages in NestJS? A: Configure a webhook endpoint in your NestJS application to receive incoming message callbacks from Plivo. See our guide on two-way SMS messaging with NestJS.

Q: Can I send MMS (multimedia messages) with this setup? A: Yes, the Plivo SDK supports MMS. Check our MMS sending guide for adding image and video support.



Pre-Launch Verification Checklist

  • Project Setup: NestJS created, dependencies (plivo, @nestjs/config, class-validator, class-transformer) installed.
  • Plivo Account: Active Plivo account.
  • Credentials: Auth ID/Token correctly retrieved.
  • Sender ID: Valid Plivo Number (E.164) or Alphanumeric Sender ID obtained.
  • Environment: .env created locally with correct PLIVO_* vars. .env in .gitignore. (Prod env vars configured separately).
  • Configuration Module: ConfigModule.forRoot({...}) in AppModule.
  • Plivo Service: PlivoService created, plivo.Client initialized (v3 noted), sendSms implemented with try/catch, logging, BadRequestException for format validation, InternalServerErrorException for API errors.
  • DTO: SendSmsDto created with class-validator decorators (IsString, IsNotEmpty, Matches for E.164, MaxLength).
  • SMS Controller: SmsController created, PlivoService injected, POST /sms/send endpoint defined, @Body() uses SendSmsDto, ValidationPipe applied (globally or locally), success response formatted, error handling re-throws HttpException.
  • Module Registration: SmsController and PlivoService registered in AppModule.
  • Local Test (Success): Ran npm run start:dev, sent valid POST request via cURL/Postman, received 2xx response with plivoMessageId, SMS received on target (verified) phone.
  • Local Test (Validation Failure): Sent invalid request (missing field, bad format), received 400 response with validation errors.
  • Local Test (Config/API Failure): Tested invalid credentials/sender ID/trial number, observed expected 400/500 errors and logs.
  • Security: .env in .gitignore, considered rate limiting/auth for production.