code examples
code examples
Developer Guide: Implementing Bulk SMS Messaging with NestJS and AWS SNS
A comprehensive guide on building a system to send bulk SMS messages using NestJS and AWS Simple Notification Service (SNS), covering setup, implementation, error handling, and optional database logging.
This guide provides a complete walkthrough for building a robust system capable of sending bulk SMS messages using NestJS and AWS Simple Notification Service (SNS). We will cover everything from initial project setup to deployment and monitoring, enabling you to reliably send SMS messages at scale directly from your application.
This implementation addresses the need for programmatic, high-throughput SMS communication, often required for notifications, alerts, marketing campaigns, or multi-factor authentication. We chose NestJS for its structured, scalable architecture based on TypeScript and Node.js, and AWS SNS for its proven reliability, scalability, and cost-effectiveness in delivering SMS messages globally. By the end of this guide, you will have a functional API endpoint capable of accepting a list of phone numbers and a message, sending SMS to each recipient via SNS, and handling results and potential errors gracefully.
System Architecture:
+-------------+ +---------------------+ +-----------+ +-----------------+
| Client | ----> | NestJS API Server | ----> | AWS SNS | ----> | User Mobile Phones|
| (Web/Mobile)| | (Bulk SMS Endpoint) | | | | (SMS Recipients)|
+-------------+ +---------------------+ +-----------+ +-----------------+
| | |
| HTTP Request | AWS SDK Call | SMS Delivery
| (POST /sms/bulk-send) | (PublishCommand) |
| | |
v v v
+-------------+ +---------------------+ +-----------------+
| Request Body| | SnsService | | CloudWatch Logs |
| {numbers,msg}| | (Handles iteration | | & Metrics |
+-------------+ | & individual sends) | +-----------------+
+---------------------+
Prerequisites:
- An active AWS account with permissions to manage SNS and IAM.
- AWS Access Key ID and Secret Access Key configured for programmatic access. See AWS Docs: Managing access keys
- Node.js (LTS version recommended) and npm/yarn installed.
- Basic understanding of NestJS concepts (modules, controllers, services). See NestJS Docs
- Familiarity with TypeScript.
1. Project Setup
First, we establish the foundation for our NestJS application, installing necessary dependencies and configuring the environment.
-
Install NestJS CLI: If you don't have it, install the NestJS command-line interface globally.
bashnpm install -g @nestjs/cli -
Create New NestJS Project: Generate a new NestJS project.
bashnest new nestjs-sns-bulk-sms cd nestjs-sns-bulk-smsChoose your preferred package manager (npm or yarn) when prompted.
-
Install Dependencies: We need the AWS SDK v3 for SNS, configuration management, and validation.
bashnpm install @nestjs/config @aws-sdk/client-sns class-validator class-transformerOr using yarn:
bashyarn add @nestjs/config @aws-sdk/client-sns class-validator class-transformer@nestjs/config: Handles environment variables securely.@aws-sdk/client-sns: The official AWS SDK v3 module for interacting with SNS.class-validator&class-transformer: Used for validating incoming request data (DTOs).
-
Configure Environment Variables: Create a
.envfile in the project root to store AWS credentials and configuration. Never commit this file to version control.dotenv# .env # --- AWS Credentials --- # Replace these placeholder values with your actual AWS credentials. AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY AWS_REGION=us-east-1 # Or your preferred AWS region # --- SNS Configuration (Optional) --- # Default SNS message type (Transactional for high reliability, Promotional for lower cost) # See: https://docs.aws.amazon.com/sns/latest/dg/sms_publish-to-phone.html#sms_publish_sdk SNS_DEFAULT_SENDER_ID=MyCompany # Optional: Custom Sender ID (requires registration in some countries) SNS_DEFAULT_SMS_TYPE=Transactional # Or Promotional- Important: Replace
YOUR_AWS_ACCESS_KEY_IDandYOUR_AWS_SECRET_ACCESS_KEYwith your actual AWS credentials. - Choose the AWS region where you want to operate SNS. SMS sending availability and pricing vary by region.
- Important: Replace
-
Load Environment Variables Globally: Modify
src/app.module.tsto load the.envfile and make configuration accessible throughout the application.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 { SmsModule } from './sms/sms.module'; // We will create this next @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // Make ConfigModule globally available envFilePath: '.env', // Specify the env file }), SmsModule, // Import the module handling SMS logic ], controllers: [AppController], providers: [AppService], }) export class AppModule {} -
Enable Global Validation Pipe: Configure automatic validation for incoming request bodies using DTOs. Modify
src/main.ts.typescript// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; // Import ValidationPipe async function bootstrap() { const app = await NestFactory.create(AppModule); // Enable global validation pipe app.useGlobalPipes(new ValidationPipe({ whitelist: true, // Strip properties not defined in DTO transform: true, // Automatically transform payloads to DTO instances })); await app.listen(3000); console.log(`Application is running on: ${await app.getUrl()}`); } bootstrap(); -
Project Structure: We'll organize our SMS logic within a dedicated
smsmodule. Create the necessary folders:bashmkdir src/sms mkdir src/sms/dto
2. Implementing Core Functionality: The SNS Service
The SnsService will encapsulate the logic for interacting with AWS SNS, including initializing the client and sending messages.
-
Create the SNS Service File:
bashtouch src/sms/sns.service.ts -
Implement the
SnsService:typescript// src/sms/sns.service.ts import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SNSClient, PublishCommand, PublishCommandInput, PublishCommandOutput, SetSMSAttributesCommand, // Import command for setting attributes } from '@aws-sdk/client-sns'; @Injectable() export class SnsService { private readonly logger = new Logger(SnsService.name); private readonly snsClient: SNSClient; private readonly defaultSmsType: string; private readonly defaultSenderId?: string; constructor(private configService: ConfigService) { const region = this.configService.get<string>('AWS_REGION'); const accessKeyId = this.configService.get<string>('AWS_ACCESS_KEY_ID'); const secretAccessKey = this.configService.get<string>( 'AWS_SECRET_ACCESS_KEY', ); if (!region || !accessKeyId || !secretAccessKey || accessKeyId === 'YOUR_AWS_ACCESS_KEY_ID' || secretAccessKey === 'YOUR_AWS_SECRET_ACCESS_KEY') { throw new Error('AWS credentials or region not configured properly in .env. Ensure placeholders are replaced.'); } this.snsClient = new SNSClient({ region: region, credentials: { accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, }, }); this.defaultSmsType = this.configService.get<string>( 'SNS_DEFAULT_SMS_TYPE', 'Transactional', // Default to Transactional if not set ); this.defaultSenderId = this.configService.get<string>( 'SNS_DEFAULT_SENDER_ID', ); this.logger.log( `SNS Service initialized. Region: ${region}, Default SMS Type: ${this.defaultSmsType}`, ); // Optional: Set account-level SMS attributes on initialization if needed // this.setAccountSmsAttributes(); } // Optional: Method to set global SMS attributes like DefaultSMSType // Usually set once via AWS Console or CLI, but can be done programmatically. // async setAccountSmsAttributes() { // try { // const command = new SetSMSAttributesCommand({ // attributes: { // DefaultSMSType: this.defaultSmsType, // // Add other attributes like UsageReportS3Bucket etc. if needed // }, // }); // await this.snsClient.send(command); // this.logger.log(`Successfully set default SMS type to ${this.defaultSmsType}`); // } catch (error) { // this.logger.error('Failed to set account SMS attributes', error.stack); // } // } /** * Sends SMS messages to multiple phone numbers using AWS SNS. * Handles each send operation individually and returns a summary of results. * @param phoneNumbers Array of phone numbers in E.164 format (e.g., +12223334444). * @param message The text message content. * @returns A summary object with counts and lists of successful/failed sends. */ async sendBulkSms(phoneNumbers: string[], message: string) { this.logger.log( `Attempting to send SMS to ${phoneNumbers.length} numbers.`, ); const results = await Promise.allSettled( phoneNumbers.map((phoneNumber) => this.sendSingleSms(phoneNumber, message)), ); const successfulSends: { phoneNumber: string; messageId: string }[] = []; const failedSends: { phoneNumber: string; error: string }[] = []; results.forEach((result, index) => { const phoneNumber = phoneNumbers[index]; if (result.status === 'fulfilled') { successfulSends.push({ phoneNumber, messageId: result.value.MessageId }); this.logger.log(`Successfully sent SMS to ${phoneNumber}`); } else { const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason); failedSends.push({ phoneNumber, error: errorMessage }); this.logger.error( `Failed to send SMS to ${phoneNumber}: ${errorMessage}`, result.reason instanceof Error ? result.reason.stack : undefined, ); } }); this.logger.log( `Bulk SMS send complete. Success: ${successfulSends.length}, Failed: ${failedSends.length}`, ); return { successCount: successfulSends.length, failCount: failedSends.length, successful: successfulSends, failed: failedSends, }; } /** * Sends a single SMS message using AWS SNS. * @param phoneNumber The recipient phone number in E.164 format. * @param message The text message content. * @returns The PublishCommandOutput from AWS SNS upon success. */ private async sendSingleSms( phoneNumber: string, message: string, ): Promise<PublishCommandOutput> { const params: PublishCommandInput = { PhoneNumber: phoneNumber, Message: message, MessageAttributes: { 'AWS.SNS.SMS.SMSType': { DataType: 'String', StringValue: this.defaultSmsType, // Use Transactional or Promotional }, // Optionally add SenderID if configured and supported ...(this.defaultSenderId && { 'AWS.SNS.SMS.SenderID': { DataType: 'String', StringValue: this.defaultSenderId, } }), }, }; const command = new PublishCommand(params); try { const data = await this.snsClient.send(command); // this.logger.debug(`SNS Publish Response for ${phoneNumber}: ${JSON.stringify(data)}`); if (!data.MessageId) { throw new Error('SNS did not return a MessageId'); } return data; } catch (error) { // Log the detailed error but re-throw to be caught by Promise.allSettled this.logger.error(`SNS Publish error for ${phoneNumber}: ${error.message}`, error.stack); throw error; // Re-throw the error } } }SNSClientInitialization: We create an instance of theSNSClientusing credentials and region fromConfigService. An error is thrown if essential variables are missing or still contain placeholder values.sendBulkSmsMethod:- Accepts an array of
phoneNumbersand themessage. - Uses
Promise.allSettledto send messages concurrently. This is crucial because it allows individual sends to fail without stopping the entire batch, unlikePromise.all. - Calls
sendSingleSmsfor each number. - Iterates through the
resultsarray (Promise.allSettledreturns an array of objects describing the outcome of each promise:{ status: 'fulfilled', value: ... }or{ status: 'rejected', reason: ... }). - Builds
successfulSendsandfailedSendsarrays based on the outcomes. - Logs success or failure for each number.
- Returns a summary object containing counts and lists of successful and failed sends, including MessageIDs for successes and error reasons for failures.
- Accepts an array of
sendSingleSmsMethod:- Constructs the
PublishCommandInputincluding thePhoneNumber,Message, and crucialMessageAttributes. AWS.SNS.SMS.SMSType: Set toTransactional(default, optimizes for reliability) orPromotional(optimizes for cost) based on the.envvariable.AWS.SNS.SMS.SenderID: Optionally included ifSNS_DEFAULT_SENDER_IDis set in.env. Note that Sender ID support and registration requirements vary by country. See AWS Docs: Sender ID- Sends the command using
this.snsClient.send(). - Includes basic logging and error handling, re-throwing the error to be caught by
sendBulkSms.
- Constructs the
3. Building the API Layer
We need a controller and a Data Transfer Object (DTO) to handle incoming API requests for sending bulk SMS.
-
Create the DTO File:
bashtouch src/sms/dto/send-bulk-sms.dto.ts -
Define the
SendBulkSmsDto: Useclass-validatordecorators to enforce input validation.typescript// src/sms/dto/send-bulk-sms.dto.ts import { IsArray, IsString, IsNotEmpty, ArrayNotEmpty, Matches, MaxLength, } from 'class-validator'; export class SendBulkSmsDto { @IsArray() @ArrayNotEmpty() @IsString({ each: true }) // Ensure each element in the array is a string @Matches(/^\+[1-9]\d{1,14}$/, { // Basic E.164 format regex validation each: true, message: 'Each phone number must be in E.164 format (e.g., +12223334444)', }) phoneNumbers: string[]; @IsString() @IsNotEmpty() @MaxLength(1600) // SNS message length limit (check current limits) message: string; }phoneNumbers: Must be a non-empty array of strings, and each string must conform to a basic E.164 format regex.message: Must be a non-empty string with a maximum length (SNS has limits, usually around 1600 bytes, but SMS segments are smaller – see Caveats).
-
Create the Controller File:
bashtouch src/sms/sms.controller.ts -
Implement the
SmsController:typescript// src/sms/sms.controller.ts import { Controller, Post, Body, HttpCode, HttpStatus, Logger } from '@nestjs/common'; import { SnsService } from './sns.service'; import { SendBulkSmsDto } from './dto/send-bulk-sms.dto'; @Controller('sms') export class SmsController { private readonly logger = new Logger(SmsController.name); constructor(private readonly snsService: SnsService) {} @Post('bulk-send') @HttpCode(HttpStatus.ACCEPTED) // Use 202 Accepted as processing is asynchronous async sendBulkSms(@Body() sendBulkSmsDto: SendBulkSmsDto) { this.logger.log(`Received bulk SMS request for ${sendBulkSmsDto.phoneNumbers.length} numbers.`); // No explicit try-catch needed here if we want NestJS default exception filter // to handle errors from SnsService (like configuration issues) const result = await this.snsService.sendBulkSms( sendBulkSmsDto.phoneNumbers, sendBulkSmsDto.message, ); // Decide on response structure - maybe return Accepted and log details, // or return the summary if the client needs immediate feedback. // Returning summary here for clarity. return result; } }- Defines a route
POST /sms/bulk-send. - Injects
SnsService. - Uses the
SendBulkSmsDtowith the@Body()decorator, triggering validation via the globalValidationPipe. - Sets
HttpCodeto202 Accepted– this is appropriate as the request is accepted, but individual message delivery happens asynchronously and might fail later. - Calls
snsService.sendBulkSmswith validated data. - Returns the result summary from the service.
- Defines a route
-
Create and Configure the
SmsModule: Tie the controller and service together.bashtouch src/sms/sms.module.tstypescript// src/sms/sms.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; // Import ConfigModule if service needs it import { SnsService } from './sns.service'; import { SmsController } from './sms.controller'; @Module({ imports: [ConfigModule], // SnsService depends on ConfigService controllers: [SmsController], providers: [SnsService], }) export class SmsModule {}- Ensure this module is imported in
src/app.module.ts(as done in Step 1.5).
- Ensure this module is imported in
Testing the Endpoint:
You can now run the application (npm run start:dev) and test the endpoint using curl or a tool like Postman:
# Note: Ensure your shell correctly handles the quotes within the JSON data.
curl -X POST http://localhost:3000/sms/bulk-send \
-H "Content-Type: application/json" \
-d '{
"phoneNumbers": ["+15551112222", "+15553334444", "+invalidNumber"],
"message": "Hello from NestJS SNS Bulk Sender!"
}'Expected JSON Request:
{
"phoneNumbers": ["+15551112222", "+15553334444"],
"message": "This is a test message."
}Example JSON Response (Success):
{
"successCount": 2,
"failCount": 0,
"successful": [
{ "phoneNumber": "+15551112222", "messageId": "uuid-for-sms-1" },
{ "phoneNumber": "+15553334444", "messageId": "uuid-for-sms-2" }
],
"failed": []
}Example JSON Response (Partial Failure):
{
"successCount": 1,
"failCount": 1,
"successful": [
{
"phoneNumber": "+15551112222",
"messageId": "b1a2b3c4-abcd-efgh-ijkl-1234567890ab"
}
],
"failed": [
{
"phoneNumber": "+19998887777",
"error": "Invalid phone number format or destination is opted out"
}
]
}4. Integrating with AWS SNS
This section focuses on the specific integration points covered in the SnsService setup.
-
AWS Credentials:
- Obtaining Keys: Generate an Access Key ID and Secret Access Key for an IAM user in the AWS Management Console. Go to IAM > Users > [Your User] > Security credentials > Create access key.
- Secure Storage: Store these keys securely in the
.envfile in your project root. Do not hardcode them in your source code. Use environment variables in production environments (e.g., set via ECS Task Definitions, Lambda environment variables, EC2 instance profiles, or secrets management services like AWS Secrets Manager). Remember to replace the placeholder values in the.envfile. - Permissions: Ensure the IAM user associated with the keys has the necessary permissions. A minimal policy would be:
Optionally add permissions forjson
{ ""Version"": ""2012-10-17"", ""Statement"": [ { ""Effect"": ""Allow"", ""Action"": ""sns:Publish"", ""Resource"": ""*"" } ] }sns:SetSMSAttributesorsns:CheckIfPhoneNumberIsOptedOutif needed, restricting theResourceif possible. Apply the principle of least privilege.
-
Configuration (
.env):AWS_ACCESS_KEY_ID: Your IAM user's access key ID (must be replaced).AWS_SECRET_ACCESS_KEY: Your IAM user's secret access key (must be replaced).AWS_REGION: The AWS region where SNS will operate (e.g.,us-east-1,eu-west-1). Must match the region configured in theSNSClient.SNS_DEFAULT_SMS_TYPE: (Optional, defaults toTransactional) Sets theAWS.SNS.SMS.SMSTypemessage attribute.Transactionalprioritizes delivery speed and reliability, whilePromotionalprioritizes lower cost. Choose based on your use case.SNS_DEFAULT_SENDER_ID: (Optional) A custom ID (alphanumeric, up to 11 chars; or a phone number you own) displayed on the recipient's device. Support varies by country, and registration might be required. See AWS Docs
-
SNS Client Initialization (
SnsService): TheSNSClientfrom@aws-sdk/client-snsis instantiated with the region and credentials loaded viaConfigService. This ensures the SDK communicates with the correct AWS region and authenticates properly. -
Fallback Mechanisms: AWS SNS is highly available, but consider application-level resilience:
- Retries: Implement retries for transient errors (see Section 5).
- Cross-Region Failover: For critical systems, you could potentially configure a secondary
SNSClientinstance pointing to a different region and failover if the primary region experiences issues (adds complexity). - Alternative Providers: In extreme cases, integrating with a second SMS provider as a fallback could be considered, but significantly increases complexity.
5. Error Handling, Logging, and Retry Mechanisms
Robust error handling and logging are essential for a production system.
-
Consistent Error Handling Strategy:
- Service Level: The
SnsServicecatches errors during individualsnsClient.send()calls within thesendSingleSmsmethod. It logs the specific error and re-throws it.Promise.allSettledinsendBulkSmscaptures these rejections without halting the loop, and the results are processed to build the final summary. - Controller Level: The
SmsControllerrelies on NestJS's built-in exception filters for unhandled errors (e.g., configuration issues during service initialization). Validation errors are handled globally by theValidationPipe. - Logging: Use the built-in
Logger(@nestjs/common) for structured logging. Log key events: request received, bulk send initiated, individual send success/failure (with error details), bulk send completion summary.
- Service Level: The
-
Logging Levels and Formats:
- Use
this.logger.log()for informational messages. - Use
this.logger.error()for errors, including the error message and stack trace (error.stack). - Use
this.logger.warn()for potential issues (e.g., high failure rate). - Use
this.logger.debug()for verbose information useful during development (e.g., full SNS responses). - Consider using a dedicated logging library (like
pinowithnestjs-pino) in production for better performance and structured JSON logging, which integrates well with log aggregation services (CloudWatch Logs, Datadog, Splunk).
- Use
-
Retry Strategies:
- SNS Internal Retries: SNS itself has some internal retry mechanisms for transient delivery issues.
- Application-Level Retries: The current implementation doesn't include explicit application-level retries for the
PublishCommand. For transient AWS SDK errors (e.g., network timeouts, throttling), you could add a simple retry loop withinsendSingleSms:This adds complexity; evaluate if necessary based on observed errors.typescript// Inside sendSingleSms, before the catch block let attempts = 0; const maxAttempts = 3; while (attempts < maxAttempts) { try { const data = await this.snsClient.send(command); if (!data.MessageId) { throw new Error('SNS did not return a MessageId'); } return data; // Success, exit loop } catch (error) { attempts++; const isRetryable = error.name === 'ThrottlingException' || error.name === 'EndpointConnectionError'; // Add other retryable error names if (isRetryable && attempts < maxAttempts) { const delay = Math.pow(2, attempts) * 100; // Exponential backoff this.logger.warn(`Attempt ${attempts} failed for ${phoneNumber}, retrying in ${delay}ms... Error: ${error.message}`); await new Promise(resolve => setTimeout(resolve, delay)); } else { if (isRetryable) { // Log max attempts reached only for retryable errors this.logger.warn(`SNS Publish failed after ${maxAttempts} attempts for ${phoneNumber}: ${error.message}`); } // Non-retryable error or max attempts reached throw error; } } } // Should not be reached if maxAttempts > 0, but needed for TS/logic completion throw new Error(`Retry logic failed unexpectedly after ${maxAttempts} attempts for ${phoneNumber}.`); - Queue-Based Retries (Advanced): For maximum robustness, especially with very large batches or frequent transient errors, decouple sending. The API endpoint could push each SMS job (phone number + message) onto an SQS queue. A separate NestJS worker process would consume jobs from the queue, attempt to send via SNS, and implement sophisticated retry logic (including dead-letter queues for persistent failures). This prevents API timeouts and handles failures more gracefully.
-
Testing Error Scenarios:
- Provide invalid E.164 numbers in the request.
- Provide a message exceeding length limits.
- Temporarily revoke
sns:Publishpermissions from the IAM user to test authorization errors. - Send to known opted-out numbers (if available for testing).
- Simulate throttling (hard to do reliably without high volume, but be aware of the
ThrottlingException). - Unit test error paths by mocking
snsClient.send()to throw specific errors.
-
Log Analysis: Use CloudWatch Logs Insights or your chosen log aggregation tool to query logs. Examples:
- Find all failed sends:
fields @timestamp, @message | filter @message like /Failed to send SMS/ - Count errors per phone number:
fields @timestamp, phoneNumber, error | filter @message like /Failed to send SMS to/ | parse @message /Failed to send SMS to (?<phoneNumber>\+\S+) *: (?<error>.*)/ | stats count(*) by phoneNumber, error - Monitor overall success/failure rates:
fields successCount, failCount | filter @message like /Bulk SMS send complete/ | parse @message /Success: (?<successCount>\d+), Failed: (?<failCount>\d+)/ | stats sum(successCount), sum(failCount)
- Find all failed sends:
6. Creating a Database Schema and Data Layer (Optional Enhancement)
While not strictly necessary for sending, storing logs of sent messages provides valuable auditing, tracking, and debugging capabilities.
-
Rationale: A database log allows you to:
- Audit exactly which messages were sent to whom and when.
- Track the AWS SNS
MessageIdfor correlation with delivery status reports (if configured). - Analyze send success/failure trends over time.
- Provide users with a history of sent messages.
-
Technology Choice: PostgreSQL, MySQL, or even a NoSQL database like DynamoDB could be used. For simplicity, we'll outline a TypeORM setup with SQLite (suitable for development/small scale). For production, use PostgreSQL or MySQL.
-
Install Dependencies (if not already present):
bashnpm install @nestjs/typeorm typeorm sqlite3 # Or pg, mysql2 etc. for other DBsOr using yarn:
bashyarn add @nestjs/typeorm typeorm sqlite3 -
Entity Relationship Diagram (Simple):
+--------------------+ | SmsLog | +--------------------+ | id (PK) | --> Auto-incrementing ID (or UUID) | phoneNumber | --> Recipient phone number (string, indexed) | messageContent | --> The message text (text) | status | --> 'SUCCESS' | 'FAILED' (string, indexed) | snsMessageId (nullable) | --> AWS SNS Message ID (string, indexed, unique if desired) | failureReason (nullable) | --> Error message if status is 'FAILED' (text) | createdAt | --> Timestamp (indexed) +--------------------+ -
Create
SmsLogEntity:typescript// src/sms/entities/sms-log.entity.ts import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm'; export enum SmsStatus { SUCCESS = 'SUCCESS', FAILED = 'FAILED', } @Entity('sms_logs') export class SmsLog { @PrimaryGeneratedColumn('uuid') // Use UUID for better distribution id: string; @Index() @Column() phoneNumber: string; @Column('text') messageContent: string; @Index() @Column({ type: 'enum', enum: SmsStatus, }) status: SmsStatus; @Index() // Index for looking up by SNS ID @Column({ nullable: true, unique: false }) // Can be null if send failed before getting ID. Not strictly unique across retries? snsMessageId?: string; @Column('text', { nullable: true }) failureReason?: string; @CreateDateColumn() @Index() createdAt: Date; } -
Configure TypeORM: Update
src/app.module.ts(replace SQLite with your production DB config).typescript// src/app.module.ts (add TypeOrmModule imports) import { TypeOrmModule } from '@nestjs/typeorm'; import { SmsLog } from './sms/entities/sms-log.entity'; // ... other imports @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }), TypeOrmModule.forRoot({ // Replace with your DB config type: 'sqlite', database: 'sms_logs.db', entities: [SmsLog], synchronize: true, // DEV only! Use migrations in production }), // Make S
Frequently Asked Questions
How to send bulk SMS with NestJS?
You can send bulk SMS messages by creating a NestJS application that integrates with AWS SNS. The application uses the AWS SDK to interact with the SNS service, sending messages to specified phone numbers. You'll need an AWS account and configure credentials within your NestJS project.
What is AWS SNS used for in bulk SMS?
AWS SNS (Simple Notification Service) is used as the messaging platform to deliver SMS messages to recipients. It handles the complexities of sending messages reliably at scale. The NestJS application acts as an interface to interact with SNS.
Why use NestJS for sending bulk SMS?
NestJS provides a structured and scalable architecture based on TypeScript and Node.js. This makes it well-suited for building robust applications capable of handling the demands of bulk SMS messaging.
How to set up AWS credentials for NestJS SMS?
Create an IAM user in your AWS account with permissions to use SNS, generate access keys, and store them securely in a `.env` file in your project's root directory. Never commit this file to version control.
What is the role of a DTO in NestJS bulk SMS?
A Data Transfer Object (DTO) defines the structure of incoming requests. In this case, the `SendBulkSmsDto` ensures that the API receives valid phone numbers and message content, using validation decorators from `class-validator`.
How to handle errors when sending bulk SMS?
The provided code uses `Promise.allSettled` to handle individual SMS failures without stopping the entire batch. Logging is implemented using NestJS's built-in `Logger`, and the code includes a detailed explanation of how to implement retry mechanisms for transient errors.
What is the purpose of the SnsService in NestJS?
The `SnsService` encapsulates the logic for interacting with AWS SNS. It initializes the SNS client, sets default message attributes, and provides methods to send single and bulk SMS messages. It also handles errors and provides a summary of sent messages.
How to structure a bulk SMS API endpoint in NestJS?
Create a controller with a POST route that accepts a `SendBulkSmsDto` object. This DTO should contain an array of phone numbers and the message content. The controller then uses the `SnsService` to send the messages.
What is the recommended HTTP status code for bulk SMS sending?
Use HTTP status code 202 (Accepted), as the actual SMS delivery is asynchronous. This acknowledges that the request has been received and is being processed, even if individual messages fail later.
How to validate phone numbers in a NestJS bulk SMS API?
Use the `class-validator` library. The `SendBulkSmsDto` demonstrates how to validate phone numbers using decorators like `@IsArray`, `@ArrayNotEmpty`, `@IsString`, and a regex `@Matches` to enforce E.164 format.
What are the prerequisites for implementing bulk SMS with NestJS and AWS SNS?
You'll need an AWS account with SNS permissions, AWS access keys, Node.js and npm/yarn, a basic understanding of NestJS and TypeScript, and optionally, a database for logging.
When should I use Transactional vs. Promotional SMS type?
Use 'Transactional' for critical messages requiring high reliability, like one-time passwords. 'Promotional' is suitable for marketing messages where lower cost is preferred. This is configurable in the '.env' file and used by the `SnsService`.
Can I customize the sender ID for bulk SMS messages?
Yes, you can set `SNS_DEFAULT_SENDER_ID` in your `.env` file. However, support for custom sender IDs varies by country and may require registration with AWS. See AWS documentation for details.
How to log sent SMS messages for auditing and analysis?
The article suggests using a database to log sent messages, including details like phone number, message content, status, SNS message ID, and errors. It outlines a TypeORM setup with SQLite as an example.