Efficiently sending SMS messages to large audiences is a common requirement for applications needing notifications, alerts, or marketing outreach. Sending messages individually is inefficient and slow for large volumes. MessageBird's Batch SMS API provides a powerful solution, enabling you to send multiple unique messages to various recipients in a single API request.
This guide will walk you through building a robust bulk SMS sending system using the NestJS framework and the MessageBird Batch API. We'll cover everything from project setup and core implementation to error handling, security, and deployment. By the end, you'll have a production-ready service capable of handling bulk SMS broadcasting efficiently.
What We'll Build:
- A NestJS application with a dedicated API endpoint for initiating bulk SMS sends.
- Integration with MessageBird's Batch SMS API for efficient message dispatch.
- Request validation, secure API key handling, and basic logging.
- A simple database layer (using Prisma) to log bulk requests and initial responses.
- Error handling and rate limiting.
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 validation, configuration management) make it ideal.
- MessageBird: A communication platform providing APIs for SMS, voice, and more. We'll specifically use their Batch SMS API.
- Prisma: A modern database toolkit for Node.js and TypeScript, simplifying database access, migrations, and type safety.
- TypeScript: For type safety and improved developer experience.
- Node.js: The underlying runtime environment.
System Architecture:
graph LR
A[Client / API Caller] -- POST /messages/bulk --> B(NestJS API);
B -- Uses --> C{Messaging Service};
C -- Uses --> D{MessageBird Config};
C -- Makes HTTP Request --> E(MessageBird Batch SMS API);
E -- Returns Results --> C;
C -- Logs Request/Response --> F[(Prisma ORM)];
F -- Interacts with --> G[(Database)];
B -- Returns Response --> A;
style B fill:#f9f,stroke:#333,stroke-width:2px
style E fill:#ccf,stroke:#333,stroke-width:2px
style G fill:#ddd,stroke:#333,stroke-width:2px
(Ensure your publishing platform correctly renders Mermaid diagrams)
Prerequisites:
- Node.js (v16 or later recommended) and npm/pnpm/yarn installed.
- A MessageBird account (Sign up here) with API credentials (Access Key).
- Basic understanding of TypeScript, NestJS concepts, and REST APIs.
- A database (PostgreSQL recommended for this guide, but Prisma supports others).
- Docker (optional, for containerized database and deployment).
Expected Outcome:
A functional NestJS API endpoint (POST /messages/bulk
) that accepts an array of message details (recipient, body, originator) and uses the MessageBird Batch API to send them. Requests and initial responses will be logged to a database. The endpoint should be appropriately secured.
1. Setting up the Project
Let's initialize our NestJS project and install the necessary dependencies.
-
Create NestJS Project: Open your terminal and run the NestJS CLI command:
nest new nestjs-messagebird-bulk cd nestjs-messagebird-bulk
Choose your preferred package manager (npm, pnpm, or yarn).
-
Install Dependencies: We need modules for configuration, HTTP requests, validation, rate limiting, and database interaction.
# Using npm: npm install @nestjs/config @nestjs/axios axios class-validator class-transformer @nestjs/throttler @prisma/client messagebird npm install prisma --save-dev # Using pnpm: pnpm add @nestjs/config @nestjs/axios axios class-validator class-transformer @nestjs/throttler @prisma/client messagebird pnpm add prisma -D # Using yarn: yarn add @nestjs/config @nestjs/axios axios class-validator class-transformer @nestjs/throttler @prisma/client messagebird yarn add prisma -D
@nestjs/config
: For managing environment variables.@nestjs/axios
: Wrapper around Axios for making HTTP requests.axios
: Peer dependency for@nestjs/axios
.class-validator
&class-transformer
: For request data validation and transformation using DTOs.@nestjs/throttler
: For API rate limiting.@prisma/client
&prisma
: For database ORM and migrations.messagebird
: The official Node.js SDK (though we'll use direct HTTP for the Batch API initially, users should check latest SDK features).
-
Initialize Prisma: Set up Prisma in your project.
npx prisma init --datasource-provider postgresql
This creates a
prisma
directory with aschema.prisma
file and a.env
file (if one doesn't exist) for your database connection string. -
Configure Environment Variables: Open the
.env
file created by Prisma (or create one in the project root) and add your database connection URL and MessageBird API key..env
# Prisma # Example: postgresql://user:password@host:port/database?schema=public DATABASE_URL=postgresql://your_db_user:your_db_password@localhost:5432/messagebird_bulk?schema=public # MessageBird # Obtain from your MessageBird Dashboard: Developers > API access MESSAGEBIRD_ACCESS_KEY=YOUR_MESSAGEBIRD_ACCESS_KEY # Replace with your actual key (Live or Test) MESSAGEBIRD_API_ENDPOINT=https://rest.messagebird.com # Default MessageBird API endpoint # Application Port PORT=3000
Important: Replace the placeholder values with your actual database credentials and MessageBird Access Key. Never commit your
.env
file to version control. Add.env
to your.gitignore
file. Note that quotes are typically not needed around values in.env
files unless they contain spaces or special characters. -
Set up Configuration Module: Integrate the
@nestjs/config
module to load environment variables.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 other modules later @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // Make ConfigService available globally envFilePath: '.env', // Specify the env file path }), // Other modules will be added here ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
ConfigModule.forRoot({ isGlobal: true })
makes theConfigService
available throughout the application without needing to importConfigModule
in every feature module. -
Project Structure: Your initial structure should look something like this:
. ├── .env ├── .gitignore ├── node_modules/ ├── prisma/ │ └── schema.prisma ├── src/ │ ├── app.controller.ts │ ├── app.module.ts │ ├── app.service.ts │ └── main.ts ├── test/ ├── nest-cli.json ├── package.json ├── README.md ├── tsconfig.build.json └── tsconfig.json
We will add modules for messaging, database interaction, etc., as we proceed.
2. Implementing Core Functionality (Messaging Service)
We'll create a dedicated module and service to handle interactions with the MessageBird API. We'll use NestJS's HttpModule
for direct requests, as the Node.js SDK might not offer the most convenient interface for the batch endpoint (users should verify against the latest SDK version).
-
Create Messaging Module & Service: Use the NestJS CLI to generate the module and service.
nest generate module messaging nest generate service messaging
This creates
src/messaging/messaging.module.ts
andsrc/messaging/messaging.service.ts
. -
Configure
MessagingModule
: ImportHttpModule
so the service can make requests, and registerConfigService
.src/messaging/messaging.module.ts
import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { MessagingService } from './messaging.service'; // Import PrismaModule later if needed for direct DB ops in service @Module({ imports: [ HttpModule.registerAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => ({ baseURL: configService.get<string>('MESSAGEBIRD_API_ENDPOINT'), headers: { 'Authorization': `AccessKey ${configService.get<string>('MESSAGEBIRD_ACCESS_KEY')}`, 'Content-Type': 'application/json', }, timeout: 10000, // Optional: Increase timeout for potentially long batch requests }), }), ], providers: [MessagingService], exports: [MessagingService], // Export if needed by other modules (like the API controller) }) export class MessagingModule {}
- We use
registerAsync
to injectConfigService
and dynamically configureHttpModule
with the base URL and authorization header read from environment variables. This avoids hardcoding credentials.
- We use
-
Implement
MessagingService
: This service will contain the logic to send bulk messages.src/messaging/messaging.service.ts
import { Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { firstValueFrom } from 'rxjs'; import axios, { AxiosError } from 'axios'; // Import axios itself for type checking // Define interfaces for clarity (match MessageBird Batch API structure) interface MessageBirdMessageInput { recipients: string[]; originator: string; body: string; // Add other optional MessageBird parameters here if needed // e.g., reference?: string; scheduledDatetime?: string; typeDetails?: object; } interface MessageBirdBatchPayload { messages: MessageBirdMessageInput[]; } // Define expected response structure. // Note: This is a simplified representation. The actual MessageBird response // might contain more fields or nested structures. Refer to the official API docs. interface MessageBirdBatchResponseItem { id: string; href: string; direction?: string; type?: string; originator?: string; body?: string; reference?: string; // ... other potential top-level fields recipients: { totalCount: number; totalSentCount: number; totalDeliveredCount?: number; // May not be present immediately totalDeliveryFailedCount?: number; // May not be present immediately items: { recipient: number; // MessageBird often uses internal numeric format here status: string; statusDatetime: string; messagePartCount?: number; // ... other potential recipient item fields }[]; }; } @Injectable() export class MessagingService { private readonly logger = new Logger(MessagingService.name); private readonly batchApiEndpoint = '/messages/batches'; // Relative to base URL private readonly maxRetries = 3; private readonly initialRetryDelay = 1000; // 1 second constructor( private readonly httpService: HttpService, // ConfigService is injected into HttpModule, no need to inject here unless used elsewhere ) {} private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Sends multiple SMS messages using the MessageBird Batch API. * @param messages An array of message objects to send. Validation (e.g., max 100) should happen before calling this. * @returns The response array from MessageBird API. * @throws HttpException if the API call fails after retries or for non-retryable errors. */ async sendBulkSms( messages: MessageBirdMessageInput[], ): Promise<MessageBirdBatchResponseItem[]> { if (!messages || messages.length === 0) { // This check is a safeguard; primary validation should be in the controller/DTO. this.logger.error('Attempted to send bulk SMS with an empty message array.'); throw new HttpException('No messages provided for bulk sending.', HttpStatus.BAD_REQUEST); } // Max length check (e.g., 100) is handled by the DTO validation in the controller layer. const payload: MessageBirdBatchPayload = { messages }; let attempts = 0; while (attempts < this.maxRetries) { attempts++; this.logger.log(`Attempt ${attempts}/${this.maxRetries}: Sending bulk SMS request with ${messages.length} messages.`); // Avoid logging full payload in production if 'body' contains sensitive PII // this.logger.debug(`Payload (Attempt ${attempts}):`_ JSON.stringify(payload)); try { const response = await firstValueFrom( this.httpService.post<MessageBirdBatchResponseItem[]>( this.batchApiEndpoint, payload, // Headers are set globally via HttpModule.registerAsync ), ); this.logger.log(`Attempt ${attempts}: Successfully received response from MessageBird API for ${response.data?.length ?? 0} messages.`); // this.logger.debug('Response Data:', response.data); return response.data; // Success, return result } catch (error) { this.logger.warn(`Attempt ${attempts} failed. Error: ${error.message}`); let shouldRetry = false; let statusCode: number | undefined = undefined; let responseData: any = null; if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; statusCode = axiosError.response?.status; responseData = axiosError.response?.data; this.logger.error(`Axios Error: ${axiosError.message}, Status: ${statusCode ?? 'N/A'}`); if (responseData) { this.logger.error(`Response Data: ${JSON.stringify(responseData)}`); } // Retry on 5xx server errors or network errors (no response/status) if (!statusCode || (statusCode >= 500 && statusCode <= 599)) { shouldRetry = true; this.logger.warn(`Identified potentially transient error (Status: ${statusCode ?? 'N/A'}).`); } else { this.logger.error(`Non-retryable Axios Error (Status: ${statusCode}). Will not retry.`); } } else { // Log non-Axios errors_ typically don't retry these unless specifically identified this.logger.error('Non-Axios error encountered.'_ error.stack); } if (shouldRetry && attempts < this.maxRetries) { const delay = this.initialRetryDelay * Math.pow(2_ attempts - 1); // Exponential backoff this.logger.log(`Retrying in ${delay}ms...`); await this.sleep(delay); } else { // Max retries reached or non-retryable error this.logger.error(`Failed after ${attempts} attempts or encountered non-retryable error. Propagating error.`); // Throw a NestJS HttpException for consistent error handling upstream const status = statusCode ?? HttpStatus.INTERNAL_SERVER_ERROR; const message = responseData ? (responseData.errors || responseData) : (error instanceof Error ? error.message : 'Failed to communicate with MessageBird API'); throw new HttpException( { status_ error: message }_ status_ { cause: error } ); } } } // Should theoretically not be reached due to return/throw inside loop_ but satisfy TS compiler throw new HttpException('Exited retry loop unexpectedly.'_ HttpStatus.INTERNAL_SERVER_ERROR); } }
- Interfaces define request/response structures. Added a note about the response interface simplification.
sendBulkSms
constructs the payload and useshttpService.post
.- Basic logging and Axios error handling are included.
- Removed the redundant
messages.length > 100
check as it's handled by DTO validation. - Integrated retry logic. It now throws
HttpException
on failure.
-
Update
AppModule
: Import theMessagingModule
and configureThrottlerModule
.src/app.module.ts
import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; // Import ThrottlerModule & Guard import { APP_GUARD } from '@nestjs/core'; // Import APP_GUARD import { AppController } from './app.controller'; import { AppService } from './app.service'; import { MessagingModule } from './messaging/messaging.module'; // Import MessagingModule // Import PrismaModule later @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env', }), ThrottlerModule.forRoot([{ // Configure basic rate limiting globally ttl: 60000, // Time-to-live (milliseconds) - 1 minute limit: 30, // Max requests per TTL per IP (adjust as needed) }]), MessagingModule, // Add MessagingModule // PrismaModule will be added here ], controllers: [AppController], providers: [ AppService, { // Provide ThrottlerGuard globally provide: APP_GUARD, useClass: ThrottlerGuard, } ], }) export class AppModule {}
- Added
ThrottlerModule
configuration and providedThrottlerGuard
globally usingAPP_GUARD
.
- Added
3. Building a Complete API Layer
Now, let's create the API endpoint that clients will use to trigger the bulk SMS sending process.
-
Create DTOs (Data Transfer Objects): Define the expected request body shape and validation rules.
Create a
dto
directory insidesrc/messaging
.src/messaging/dto/create-message.dto.ts
import { IsArray, IsNotEmpty, IsPhoneNumber, IsString, Length, MaxLength, ArrayNotEmpty, ArrayMinSize } from 'class-validator'; export class CreateMessageDto { @IsArray() @ArrayNotEmpty() @ArrayMinSize(1) @IsPhoneNumber(undefined, { each: true, message: 'Each recipient must be a valid phone number in E.164 format (e.g., +14155552671)' }) recipients: string[]; // Array of phone numbers in E.164 format @IsString() @IsNotEmpty() @MaxLength(11, { message: 'Alphanumeric originator cannot exceed 11 characters. Use E.164 for numeric originators.' }) originator: string; // Sender ID (phone number or alphanumeric - check MessageBird rules) @IsString() @IsNotEmpty() @Length(1, 1600, { message: 'Message body must be between 1 and 1600 characters.' }) // Max length for multi-part SMS body: string; // Message content // Add other optional MessageBird fields here with validation if needed // e.g., @IsOptional() @IsString() reference?: string; // e.g., @IsOptional() @IsISO8601() scheduledDatetime?: string; // e.g., @IsOptional() @IsObject typeDetails?: Record<string_ any>; }
src/messaging/dto/bulk-messages.dto.ts
import { Type } from 'class-transformer'; import { IsArray, ValidateNested, ArrayMaxSize, ArrayMinSize } from 'class-validator'; import { CreateMessageDto } from './create-message.dto'; export class BulkMessagesDto { @IsArray() @ValidateNested({ each: true }) // Validate each object in the array @ArrayMinSize(1, { message: 'The messages array cannot be empty.'}) @ArrayMaxSize(100, { message: 'Cannot process more than 100 messages per batch request.' }) // Match API limit @Type(() => CreateMessageDto) // Required for nested validation messages: CreateMessageDto[]; }
CreateMessageDto
: Defines a single message structure with validation.BulkMessagesDto
: Defines the top-level request body, ensuring it's an array (messages
) containingCreateMessageDto
objects, with array size validation.
-
Create API Controller: Generate a controller within the
messaging
module.nest generate controller messaging
This creates
src/messaging/messaging.controller.ts
. -
Implement Controller Endpoint: Define the
POST /messages/bulk
endpoint.src/messaging/messaging.controller.ts
import { Controller, Post, Body, UsePipes, ValidationPipe, Logger, UseGuards, HttpException, HttpStatus, HttpCode } from '@nestjs/common'; // ThrottlerGuard is applied globally via AppModule import { MessagingService } from './messaging.service'; import { BulkMessagesDto } from './dto/bulk-messages.dto'; // Import PrismaService and DTOs for DB logging later // Import custom API Key Guard later // **CRITICAL SECURITY NOTE:** This endpoint MUST be protected in a real application. // Implement an API Key Guard, JWT Guard, or other authentication mechanism. // Example: @UseGuards(ApiKeyGuard) -- Assuming ApiKeyGuard is created @Controller('messages') // Route prefix: /messages export class MessagingController { private readonly logger = new Logger(MessagingController.name); constructor( private readonly messagingService: MessagingService, // Inject PrismaService later ) {} @Post('bulk') // Route: POST /messages/bulk @HttpCode(HttpStatus.CREATED) // Set response code to 201 Created on success // @UseGuards(ApiKeyGuard) // <-- Add your security guard here! // ValidationPipe is applied globally via main.ts async sendBulkMessages(@Body() bulkMessagesDto: BulkMessagesDto) { this.logger.log(`Received request to send ${bulkMessagesDto.messages.length} bulk messages.`); try { // --- Database Logging (Implemented in Section 6) --- // const loggedRequest = await this.prismaService.bulkSendRequest.create({ ... }); // ----------------------------------------------------- const results = await this.messagingService.sendBulkSms(bulkMessagesDto.messages); // --- Update Database Log with Response (Implemented in Section 6) --- // await this.prismaService.bulkSendRequest.update({ where: { id: loggedRequest.id }_ data: { ... }}); // ------------------------------------------------------ this.logger.log(`Successfully initiated bulk send for ${results.length} messages via MessageBird.`); return { message: `Successfully initiated bulk send request for ${results.length} messages.`_ // requestId: loggedRequest.id_ // Include if DB logging is added data: results_ // Return the direct response from MessageBird }; } catch (error) { // Errors from MessagingService should already be HttpExceptions if (error instanceof HttpException) { this.logger.error(`Error processing bulk message request: ${error.getStatus()} - ${JSON.stringify(error.getResponse())}`_ error.stack); throw error; // Re-throw the HttpException } else { // Catch any unexpected errors that weren't HttpExceptions this.logger.error(`Unexpected error processing bulk message request: ${error.message}`_ error.stack); throw new HttpException( 'An unexpected internal server error occurred.'_ HttpStatus.INTERNAL_SERVER_ERROR_ { cause: error } ); } } } }
- Added
@HttpCode(HttpStatus.CREATED)
to return201
on success. - Removed
@UseGuards(ThrottlerGuard)
as it's now global. - Removed
@UsePipes(ValidationPipe)
as it will be global. - Added a CRITICAL SECURITY NOTE emphasizing the need for endpoint protection (e.g._
@UseGuards(ApiKeyGuard)
). - Error handling now expects
HttpException
from the service and re-throws it_ with a fallback for unexpected errors. - Placeholder comments remain for database logging integration.
- Added
-
Update
MessagingModule
: Make sure the controller is listed.src/messaging/messaging.module.ts
import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { MessagingService } from './messaging.service'; import { MessagingController } from './messaging.controller'; // Import Controller // Import PrismaModule later @Module({ imports: [ HttpModule.registerAsync({ inject: [ConfigService]_ useFactory: (configService: ConfigService) => ({ baseURL: configService.get<string>('MESSAGEBIRD_API_ENDPOINT'), headers: { 'Authorization': `AccessKey ${configService.get<string>('MESSAGEBIRD_ACCESS_KEY')}`, 'Content-Type': 'application/json', }, timeout: 10000, }), }), // PrismaModule later ], controllers: [MessagingController], // Add Controller providers: [MessagingService], exports: [MessagingService], }) export class MessagingModule {}
-
Enable Global Validation Pipe: Enable the
ValidationPipe
globally insrc/main.ts
.src/main.ts
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe, Logger } from '@nestjs/common'; // Import Logger import { ConfigService } from '@nestjs/config'; // Import ConfigService // Import global exception filter later async function bootstrap() { const app = await NestFactory.create(AppModule); const configService = app.get(ConfigService); // Get ConfigService instance const logger = new Logger('Bootstrap'); // Create logger instance app.enableCors(); // Optional: Enable CORS if requests come from browsers app.useGlobalPipes(new ValidationPipe({ whitelist: true, // Strip properties not defined in DTO forbidNonWhitelisted: true, // Throw error if extra properties are present transform: true, // Automatically transform payload to DTO instance transformOptions: { enableImplicitConversion: true, // Allow basic type conversions (e.g., string -> number for path/query params) }, disableErrorMessages: false, // Set to true in production if you don't want detailed validation errors sent to client })); // Apply global exception filter later (Section 5) // app.useGlobalFilters(new AllExceptionsFilter()); const port = configService.get<number>('PORT', 3000); // Use env var or default await app.listen(port); logger.log(`Application listening on port ${port}`); logger.log(`API endpoint available at http://localhost:${port}/messages/bulk`); } bootstrap();
- The
ValidationPipe
is now applied globally to all incoming requests.
- The
-
Testing the Endpoint: Start the application:
npm run start:dev
(orpnpm run start:dev
/yarn start:dev
)Use
curl
or Postman to send a request:curl -X POST http://localhost:3000/messages/bulk \ -H 'Content-Type: application/json' \ -d '{ "messages": [ { "recipients": ["+14155550100"], "originator": "MyApp", "body": "Hello from NestJS Batch! (Msg 1)" }, { "recipients": ["+14155550101", "+14155550102"], "originator": "+12015550123", "body": "This is the second message for multiple recipients." } ] }'
Expected Success Response (JSON, Status 201 Created):
{ "message": "Successfully initiated bulk send request for 2 messages.", "data": [ { "id": "some_message_id_1", "href": "https://rest.messagebird.com/messages/some_message_id_1", "direction": "mt", "type": "sms", "originator": "MyApp", "body": "Hello from NestJS Batch! (Msg 1)", // ... other fields returned by MessageBird ... "recipients": { "totalCount": 1, "totalSentCount": 1, "totalDeliveredCount": 0, // Example, may not be present initially "totalDeliveryFailedCount": 0, // Example, may not be present initially "items": [ { "recipient": 14155550100, // Note: MessageBird often returns numbers in internal format, not E.164 "status": "sent", // Or 'scheduled', etc. "statusDatetime": "2025-04-20T10:30:00+00:00" // ... potentially other fields like messagePartCount } ] } }, { "id": "some_message_id_2", "href": "https://rest.messagebird.com/messages/some_message_id_2", // ... other fields ... "recipients": { "totalCount": 2, "totalSentCount": 2, "items": [ // ... item for +14155550101 ... // ... item for +14155550102 ... ] } } ] }
Example Error Response (Validation Failed, Status 400 Bad Request):
{ "message": [ "messages[0].recipients[0] must be a valid phone number in E.164 format (e.g., +14155552671)", "messages[1].originator must be shorter than or equal to 11 characters" ], "error": "Bad Request", "statusCode": 400 }
4. Integrating with Third-Party Services (MessageBird Deep Dive)
We've set up the basic integration. Let's refine the configuration and detail MessageBird specifics.
-
Obtaining the MessageBird API Key:
- Log in to your MessageBird Dashboard.
- Navigate to the ""Developers"" section in the left-hand sidebar.
- Click on ""API access"".
- You will see options for ""Live API Key"" and ""Test API Key"".
- Test Key: Use this during development. It simulates API calls without actually sending messages or incurring costs. Responses mimic real ones.
- Live Key: Use this for production when you want to send real messages. Requires adding credit to your account.
- Copy the desired key (either Live or Test).
-
Securely Storing the API Key: As done in Step 1, store this key only in your
.env
file under the variableMESSAGEBIRD_ACCESS_KEY
. Ensure.env
is in your.gitignore
..env
# ... other variables ... MESSAGEBIRD_ACCESS_KEY=YOUR_COPIED_ACCESS_KEY # Use 'test_...' or 'live_...' key MESSAGEBIRD_API_ENDPOINT=https://rest.messagebird.com
-
Configuration Access (
ConfigService
): OurMessagingModule
usesConfigService
injected intoHttpModule.registerAsync
to securely fetch the API key and endpoint URL:src/messaging/messaging.module.ts
(Relevant snippet)HttpModule.registerAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => ({ baseURL: configService.get<string>('MESSAGEBIRD_API_ENDPOINT'), headers: { 'Authorization': `AccessKey ${configService.get<string>('MESSAGEBIRD_ACCESS_KEY')}`, // Securely fetched 'Content-Type': 'application/json', }, timeout: 10000, }), }),
This ensures the key is read from the environment at runtime and not hardcoded.
-
Environment Variables Explained:
DATABASE_URL
: Specifies the connection string for your database (used by Prisma). Format depends on the database type.MESSAGEBIRD_ACCESS_KEY
: Your secret API key from MessageBird (Test or Live). Used for authenticating requests.MESSAGEBIRD_API_ENDPOINT
: The base URL for the MessageBird REST API. Defaults tohttps://rest.messagebird.com
.PORT
: The port number on which your NestJS application will listen. Defaults to3000
if not set.