This guide provides a complete walkthrough for building a production-ready NestJS application capable of sending SMS messages using the Vonage Messages API. We will cover everything from project setup and core implementation to error handling, security considerations, and deployment.
By the end of this tutorial, you will have a functional API endpoint that accepts a recipient phone number and a message body, then uses Vonage to deliver the SMS. This solves the common need for applications to send transactional or notification-based text messages programmatically.
Project Overview and Goals
- Goal: Create a simple, robust NestJS API endpoint to send SMS messages via Vonage.
- Problem Solved: Enables applications to integrate SMS sending capabilities for notifications, alerts, verification codes, or other communications.
- Technologies:
- Node.js: JavaScript runtime environment.
- NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications using TypeScript. Chosen for its structured architecture, dependency injection, and built-in support for modules, controllers, and services, promoting maintainable code.
- Vonage Messages API: A unified API from Vonage for sending messages across various channels, including SMS. We use this for its reliability and developer-friendly SDK.
- TypeScript: Superset of JavaScript adding static types, enhancing developer productivity and code quality.
- Prerequisites:
- Node.js (LTS version recommended) and npm (or yarn) installed.
- A Vonage API account (Sign up here).
- Access to your Vonage API Dashboard to obtain credentials.
- A text editor or IDE (like VS Code).
- Basic understanding of TypeScript, Node.js, and REST APIs.
- (Optional but Recommended) Vonage CLI installed (
npm install -g @vonage/cli
).
- Final Outcome: A NestJS application with a POST endpoint (
/sms/send
) that takes a phone number and message, sends the SMS using Vonage, and returns a success or error response.
System Architecture
The basic flow is straightforward:
[Client Application] --(HTTP POST Request)--> [NestJS API (/sms/send)]
|
| (Sends SMS via Vonage SDK)
v
[Vonage Messages API] --(Delivers SMS)--> [Recipient's Phone]
|
| (Returns Response/Status)
v
[NestJS API] --(HTTP Response)--> [Client Application]
1. Setting up the project
We'll start by creating a new NestJS project using the Nest CLI and installing necessary dependencies.
-
Install NestJS CLI (if you haven't already):
npm install -g @nestjs/cli
-
Create a new NestJS project: Choose your preferred package manager (npm or yarn) when prompted.
nest new vonage-sms-sender
-
Navigate into the project directory:
cd vonage-sms-sender
-
Install the Vonage Node.js Server SDK: This SDK provides convenient methods for interacting with Vonage APIs.
npm install @vonage/server-sdk
-
Install the NestJS Config module: We'll use this for managing environment variables securely.
npm install @nestjs/config
Project Structure and Configuration
The NestJS CLI scaffolds a standard project structure:
src/
: Contains your application source code.main.ts
: The application entry point, bootstrapping the NestJS app.app.module.ts
: The root module of the application.app.controller.ts
: A basic example controller.app.service.ts
: A basic example service.
.env
: (We will create this) File to store environment variables like API keys.tsconfig.json
: TypeScript compiler configuration.package.json
: Project dependencies and scripts.
Configuration Choice: Using @nestjs/config
and a .env
file is a standard and secure way to manage sensitive credentials like API keys and application IDs, preventing them from being hardcoded in the source code.
Create the .env
file in the project root:
# .env
# Vonage Credentials (Get these from your Vonage Dashboard)
VONAGE_APPLICATION_ID=YOUR_VONAGE_APPLICATION_ID
VONAGE_PRIVATE_KEY_PATH=./private.key # Path relative to project root
# Vonage Sender Number (Must be a Vonage number linked to your app or approved Alphanumeric Sender ID)
VONAGE_FROM_NUMBER=YOUR_VONAGE_NUMBER_OR_SENDER_ID
# Server Port (Optional, defaults usually work)
PORT=3000
Important: Add .env
to your .gitignore
file to prevent committing sensitive credentials. Create a .env.example
file with placeholder values to guide other developers.
Create the .env.example
file in the project root:
# .env.example
# Vonage Credentials (Replace with your actual values in .env)
VONAGE_APPLICATION_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
VONAGE_PRIVATE_KEY_PATH=./private.key
# Vonage Sender Number (Replace with your actual value in .env)
VONAGE_FROM_NUMBER=14155550100
# Server Port (Optional)
PORT=3000
2. Implementing core functionality
Let's create a dedicated module and service for handling SMS logic.
-
Generate an
Sms
module and service:nest generate module sms nest generate service sms --no-spec # --no-spec skips test file generation for now
This creates
src/sms/sms.module.ts
andsrc/sms/sms.service.ts
. -
Configure the Vonage Client in
SmsService
: Modifysrc/sms/sms.service.ts
to initialize the Vonage client and implement thesendSms
method.// src/sms/sms.service.ts import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Vonage } from '@vonage/server-sdk'; import { MessageSendRequest } from '@vonage/messages'; @Injectable() export class SmsService { private readonly logger = new Logger(SmsService.name); private vonage: Vonage; private vonageFromNumber: string; constructor(private configService: ConfigService) { // Retrieve Vonage credentials and settings from environment variables const applicationId = this.configService.get<string>('VONAGE_APPLICATION_ID'); const privateKeyPath = this.configService.get<string>('VONAGE_PRIVATE_KEY_PATH'); this.vonageFromNumber = this.configService.get<string>('VONAGE_FROM_NUMBER'); if (!applicationId || !privateKeyPath || !this.vonageFromNumber) { this.logger.error('Vonage configuration missing in environment variables.'); // In a real app, you might throw an error or handle this more gracefully throw new Error('Vonage configuration incomplete.'); } // Initialize Vonage client // The Vonage SDK reads the private key file content automatically this.vonage = new Vonage({ applicationId: applicationId, privateKey: privateKeyPath, // Provide the path to the key file }); } /** * Sends an SMS message using the Vonage Messages API. * @param to The recipient's phone number (E.164 format recommended). * @param text The message content. * @returns The message UUID on success. * @throws Error if sending fails. */ async sendSms(to: string, text: string): Promise<string> { this.logger.log(`Attempting to send SMS to ${to}`); const messageRequest: MessageSendRequest = { message_type: 'text', to: to, from: this.vonageFromNumber, // Use the configured sender number/ID channel: 'sms', text: text, }; try { const response = await this.vonage.messages.send(messageRequest); this.logger.log(`SMS sent successfully to ${to}. Message UUID: ${response.message_uuid}`); return response.message_uuid; } catch (error) { this.logger.error(`Failed to send SMS to ${to}: ${error.message}`, error.stack); // The error object from the SDK might contain more details, // especially in `error.response?.data` for specific Vonage API errors. // Consider logging error.response?.data if available for deeper debugging. // Re-throw a more specific error or handle as needed throw new Error(`Vonage API Error: ${error.message}`); } } }
Why this approach?
- Dependency Injection: We inject
ConfigService
to securely access environment variables. - Initialization: The Vonage client is initialized in the constructor, ready for use. We check for necessary config values early.
- Clear Method: The
sendSms
method encapsulates the logic for sending a single SMS, taking the recipient and message text as arguments. - Logging: Basic logging helps track requests and errors.
- Error Handling: A
try...catch
block handles potential errors during the API call. The comment now suggests checking nested error properties for more detail. - Messages API: We use
vonage.messages.send
, the recommended method for sending SMS and other message types.
- Dependency Injection: We inject
-
Update
SmsModule
to provideSmsService
: Make sureSmsService
is listed in theproviders
array and exported. Also, importConfigModule
.// src/sms/sms.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; // Import ConfigModule import { SmsService } from './sms.service'; // If you create a controller later, import it here // import { SmsController } from './sms.controller'; @Module({ imports: [ConfigModule], // Make ConfigService available providers: [SmsService], exports: [SmsService], // Export if needed by other modules // controllers: [SmsController], // Uncomment if you add a controller }) export class SmsModule {}
-
Import
SmsModule
andConfigModule
in the RootAppModule
: Modifysrc/app.module.ts
.// src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; // Import ConfigModule import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SmsModule } from './sms/sms.module'; // Import SmsModule @Module({ imports: [ ConfigModule.forRoot({ // Configure ConfigModule globally isGlobal: true, // Make config available everywhere envFilePath: '.env', // Specify the env file }), SmsModule, // Import our SmsModule ], controllers: [AppController], // Keep or remove default controller providers: [AppService], // Keep or remove default service }) export class AppModule {}
Why
.forRoot()
andisGlobal: true
? This loads the.env
file and makes theConfigService
available throughout the application without needing to importConfigModule
in every feature module.
3. Building a complete API layer
Now, let's create an API endpoint to trigger the SMS sending functionality.
-
Generate an
Sms
controller:nest generate controller sms --no-spec
This creates
src/sms/sms.controller.ts
. -
Define the API endpoint and Request Body Validation: We need a way to receive the
to
phone number andmessage
text in the request. NestJS uses DTOs (Data Transfer Objects) and built-in validation pipes for this.-
Install validation packages:
npm install class-validator class-transformer
-
Create a DTO file: Create
src/sms/dto/send-sms.dto.ts
.// src/sms/dto/send-sms.dto.ts import { IsNotEmpty, IsPhoneNumber, IsString, MaxLength } from 'class-validator'; export class SendSmsDto { @IsNotEmpty() @IsPhoneNumber(null) // Use null for basic E.164 format check, or specify region code string e.g., 'US' readonly to: string; @IsNotEmpty() @IsString() @MaxLength(1600) // Vonage allows longer messages, but be mindful of SMS segment costs readonly message: string; }
Why DTOs and Validation? DTOs define the expected shape of request data.
class-validator
decorators automatically validate incoming request bodies against these definitions, ensuring data integrity before it reaches your service logic. -
Implement the controller: Modify
src/sms/sms.controller.ts
.// src/sms/sms.controller.ts import { Controller, Post, Body, HttpCode, HttpStatus, Logger } from '@nestjs/common'; import { SmsService } from './sms.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 smsService: SmsService) {} @Post('send') // Route: POST /sms/send @HttpCode(HttpStatus.OK) // Send 200 OK on success instead of default 201 Created async sendSms(@Body() sendSmsDto: SendSmsDto): Promise<{ success: boolean; messageId?: string; error?: string }> { this.logger.log(`Received request to send SMS to ${sendSmsDto.to}`); try { const messageId = await this.smsService.sendSms(sendSmsDto.to, sendSmsDto.message); return { success: true, messageId: messageId }; } catch (error) { this.logger.error(`Error in sendSms controller: ${error.message}`); // Return a user-friendly error response // Avoid leaking internal error details in production return { success: false, error: 'Failed to send SMS. Please try again later.' }; // Or, consider throwing specific NestJS HttpExceptions for better control // throw new HttpException('Failed to send SMS via provider.', HttpStatus.SERVICE_UNAVAILABLE); } } }
Why this structure?
@Controller('sms')
: Defines the base route for all methods in this controller.@Post('send')
: Maps HTTP POST requests to/sms/send
to thesendSms
method.@Body()
: Tells NestJS to parse the request body and validate it against theSendSmsDto
.@HttpCode(HttpStatus.OK)
: Sets the successful response code to 200.- Async/Await: Handles the asynchronous nature of the
smsService.sendSms
call. - Error Handling: Catches errors from the service layer and returns a structured JSON error response.
-
-
Add the
SmsController
toSmsModule
: Updatesrc/sms/sms.module.ts
.// src/sms/sms.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { SmsService } from './sms.service'; import { SmsController } from './sms.controller'; // Import the controller @Module({ imports: [ConfigModule], providers: [SmsService], exports: [SmsService], controllers: [SmsController], // Add the controller here }) export class SmsModule {}
-
Enable Validation Pipe Globally: Modify
src/main.ts
to automatically use the validation pipe for all incoming requests.// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; // Import ValidationPipe import { ConfigService } from '@nestjs/config'; // Import ConfigService 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 forbidNonWhitelisted: true, // Throw error if extra properties are present transform: true, // Automatically transform payloads to DTO instances })); const configService = app.get(ConfigService); // Get ConfigService instance const port = configService.get<number>('PORT') || 3000; // Get port from .env or default await app.listen(port); console.log(`Application is running on: ${await app.getUrl()}`); } bootstrap();
Testing the API Endpoint
Start the development server:
npm run start:dev
You can now send a POST request to http://localhost:3000/sms/send
(or your configured port).
Using curl
:
Replace +14155552671
with a valid E.164 format test number (see Vonage setup) and YOUR_VONAGE_NUMBER_OR_SENDER_ID
in your .env
.
curl -X POST http://localhost:3000/sms/send \
-H ""Content-Type: application/json"" \
-d '{
""to"": ""+14155552671"",
""message"": ""Hello from NestJS and Vonage!""
}'
Expected Success Response (JSON):
{
""success"": true,
""messageId"": ""xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx""
}
Expected Validation Error Response (JSON):
(If to
is missing or invalid)
{
""statusCode"": 400,
""message"": [
""to must be a valid phone number""
// or ""to should not be empty""
],
""error"": ""Bad Request""
}
Expected Server Error Response (JSON): (If Vonage API call fails internally)
{
""success"": false,
""error"": ""Failed to send SMS. Please try again later.""
}
4. Integrating with Vonage
This involves setting up your Vonage account correctly and securely handling credentials.
-
Log in to your Vonage API Dashboard: https://dashboard.nexmo.com/
-
Set Messages API as Default:
- Navigate to Settings in the left-hand menu.
- Under API settings, find the SMS settings section.
- Ensure Messages API is selected as the default API for sending SMS messages.
- Click Save changes.
-
Create a Vonage Application:
- Navigate to Applications > Create a new application.
- Enter an Application name (e.g.,
""NestJS SMS Sender""
). - Click Generate public and private key. This will automatically download the
private.key
file. Save this file securely. - Copy the generated Application ID.
- Enable the Messages capability.
- For sending-only, you can leave the Inbound URL and Status URL blank initially. However, for production apps wanting delivery receipts or two-way messaging, you'd need to configure publicly accessible webhook URLs here (e.g., using ngrok for local development, or your deployed application's URL). Example:
https://your-app-domain.com/webhooks/status
andhttps://your-app-domain.com/webhooks/inbound
.
- For sending-only, you can leave the Inbound URL and Status URL blank initially. However, for production apps wanting delivery receipts or two-way messaging, you'd need to configure publicly accessible webhook URLs here (e.g., using ngrok for local development, or your deployed application's URL). Example:
- Scroll down to Link virtual numbers. Click Link next to the Vonage virtual number you want to send from. If you don't have one, you may need to buy one under Numbers > Buy numbers. Alternatively, investigate using Alphanumeric Sender IDs if applicable to your region and use case (requires registration).
- Click Create application.
-
Configure Environment Variables (
.env
):VONAGE_APPLICATION_ID
: Paste the Application ID you copied in the previous step.- Purpose: Identifies your application to Vonage when using the Messages API with private key authentication.
- Format: A UUID string (e.g.,
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
). - How to obtain: From the Vonage Application details page after creation.
VONAGE_PRIVATE_KEY_PATH
: Set the path relative to your project root where you saved the downloadedprivate.key
file. We used./private.key
.- Purpose: Used by the SDK along with the Application ID to authenticate your API requests securely.
- Format: A file path string (e.g.,
./private.key
orconfig/keys/private.key
). - How to obtain: Downloaded automatically when generating keys during Vonage Application creation. Keep this file secure and do not commit it to Git.
VONAGE_FROM_NUMBER
: Enter the Vonage virtual number you linked to the application, or a registered Alphanumeric Sender ID.- Purpose: The sender ID displayed on the recipient's phone. Must be a Vonage number owned by you and linked to the application, or an approved Alphanumeric Sender ID.
- Format: E.164 phone number (e.g.,
14155550100
) or Alphanumeric string (e.g.,MyAppName
, max 11 chars, availability/rules vary by country). - How to obtain: Purchase/view under Numbers > Your numbers in the Vonage dashboard and link it to your Vonage Application. Or register an Alphanumeric Sender ID via Vonage support/dashboard if available.
PORT
(Optional): The port your NestJS application will listen on. Defaults usually work locally.- Purpose: Network port for the application server.
- Format: A number (e.g.,
3000
). - How to obtain: Choose any available port.
-
Secure
private.key
:- Ensure the
private.key
file is included in your.gitignore
. - In production environments, manage this key file securely (e.g., using secrets management tools provided by your cloud provider or deployment platform). Do not embed it directly in container images.
- Ensure the
5. Implementing proper error handling, logging, and retry mechanisms
Our current setup includes basic logging and try/catch. Let's refine it.
-
Consistent Error Strategy:
- The
SmsService
catches errors from the Vonage SDK and logs detailed information internally. - The
SmsController
catches errors from the service and returns a generic, user-friendly JSON error response ({ success: false, error: '...' }
). This prevents leaking internal details. - Alternative: For more granular control, throw specific NestJS
HttpException
s from the service or controller (e.g.,BadRequestException
,ServiceUnavailableException
) which NestJS automatically translates into standard HTTP error responses.
- The
-
Logging Levels:
- NestJS's built-in
Logger
supports different levels (log
,error
,warn
,debug
,verbose
). - Use
log
for standard operations (e.g., ""Received request"", ""SMS sent""). - Use
error
for failures (e.g., ""Failed to send SMS"", ""Vonage configuration missing""). Include stack traces where helpful. - Use
warn
for potential issues (e.g., ""Retrying Vonage API call""). - Use
debug
orverbose
for detailed diagnostic information during development (can be configured to be disabled in production). - Consider using a more robust logging library like
Pino
(e.g., withnestjs-pino
) for production, allowing structured logging (JSON format), log rotation, and easier integration with log analysis tools.
- NestJS's built-in
-
Retry Mechanisms:
- Simple SMS Send: For a basic ""send SMS"" operation, implementing client-side retries can be complex and potentially lead to duplicate messages if the first request succeeded but the response was lost. Vonage itself has internal retry mechanisms for delivery.
- When to Retry: Client-side retries (ideally with exponential backoff and jitter) might be suitable for transient network errors before the request reaches Vonage, or for critical operations where you need higher assurance (though idempotency handling becomes crucial).
- Implementation: Libraries like
axios-retry
(if using Axios directly) or custom logic withsetTimeout
could be used. For this basic guide, we rely on Vonage's reliability and avoid client-side retries for the send operation itself. - Focus on Idempotency: Ensure your triggering mechanism (e.g., the event that causes the SMS to be sent) is idempotent if retries are involved at a higher level in your application.
-
Testing Error Scenarios:
- Invalid Credentials: Temporarily change
VONAGE_APPLICATION_ID
or the content ofprivate.key
in.env
and restart the app. API calls should fail. - Invalid 'To' Number: Send a request with a deliberately malformed phone number in the JSON body (e.g.,
""to"": ""123""
). TheValidationPipe
should return a 400 Bad Request. - Invalid 'From' Number: Set
VONAGE_FROM_NUMBER
to a number not linked to your Vonage app or an invalid format. The Vonage API should return an error. - Network Issues: Simulate network disruption between your app and Vonage (harder to test locally, might require specific tools or testing in a controlled environment).
- (Vonage Trial Account Limit): If using a trial account, attempt to send to a number not on your verified list (see Troubleshooting section). This should trigger a specific Vonage error.
- Invalid Credentials: Temporarily change
-
Log Analysis: During development, monitor the console output where
npm run start:dev
is running. In production, configure logging to output to files or stream to a log aggregation service (e.g., Datadog, ELK stack, CloudWatch Logs) for centralized monitoring and troubleshooting. Structured JSON logging makes filtering and searching much easier.
6. Creating a database schema and data layer
For this specific guide focused only on sending a single SMS via an API call, a database is not strictly required.
However, in a real-world application, you would likely need a database to:
- Store message history: Keep records of sent messages, their status (using Vonage status webhooks), recipient, content, timestamp, and associated user/event.
- Manage users: Store user profiles, including phone numbers.
- Track application state: Relate SMS messages to specific orders, events, or notifications within your application.
If a database were needed:
- Technology Choice: PostgreSQL, MySQL, MongoDB are common choices with NestJS.
- ORM/ODM: TypeORM or Prisma are popular choices for interacting with databases in a type-safe way within NestJS. Mongoose is common for MongoDB.
- Schema: You'd define entities (e.g.,
User
,SmsMessage
) with relevant fields (id
,to
,from
,body
,status
,vonageMessageId
,sentAt
,updatedAt
, potentiallyuserId
). - Data Layer: Implement repositories or use the ORM's built-in methods within services to create, read, update, and delete data (e.g., saving a record before or after attempting to send the SMS).
- Migrations: Use the ORM's migration tools (like TypeORM migrations or
prisma migrate
) to manage database schema changes over time.
For this guide's scope, we will omit database integration.
7. Adding security features
Security is paramount, especially when dealing with APIs and external services.
-
Input Validation and Sanitization:
- Done: We are already using
class-validator
viaValidationPipe
(whitelist: true
,forbidNonWhitelisted: true
) inmain.ts
. This ensures incoming request bodies match ourSendSmsDto
, stripping extra fields and rejecting invalid formats (like non-phone numbers forto
). - While
class-validator
handles format validation, explicit sanitization (e.g., stripping potential script tags if messages were user-generated and displayed elsewhere) might be needed depending on the broader application context, though less critical for the SMS content itself being sent out. Libraries likeclass-sanitizer
or custom logic could be used.
- Done: We are already using
-
Protection Against Common Vulnerabilities:
- Credentials: Securely managing
VONAGE_APPLICATION_ID
andprivate.key
via.env
andConfigModule
prevents exposure in code. Never commit secrets. Use environment variables or dedicated secrets management systems in production. - Rate Limiting: Protect the
/sms/send
endpoint from abuse (e.g., flooding recipients, exhausting your Vonage credit) by implementing rate limiting. NestJS has excellent modules for this:- Install:
npm install --save @nestjs/throttler
- Configure in
app.module.ts
:// src/app.module.ts import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { APP_GUARD } from '@nestjs/core'; // Import APP_GUARD // ... other imports @Module({ imports: [ // ... ConfigModule, SmsModule ThrottlerModule.forRoot([{ ttl: 60000, // Time window in milliseconds (e.g., 60 seconds) limit: 10, // Max requests per window per user/IP }]), ], controllers: [AppController, /* SmsController is part of SmsModule */], providers: [ AppService, // Apply ThrottlerGuard globally { provide: APP_GUARD, useClass: ThrottlerGuard, }, ], }) export class AppModule {}
- This applies a default limit (e.g., 10 requests per minute per IP) to all endpoints. You can customize limits per-controller or per-route using decorators (
@Throttle()
).
- Install:
- Authentication/Authorization: Our current endpoint is public. In a real application, you would protect it. Common methods include:
- API Keys: Require clients to send a secret API key in headers, validated by a custom Guard.
- JWT (JSON Web Tokens): For user-specific actions, require a valid JWT obtained after login, validated by
@nestjs/jwt
and Passport (@nestjs/passport
). - OAuth: For third-party integrations.
- This is beyond the scope of a basic SMS sender but crucial for production APIs.
- Credentials: Securely managing
-
Security Headers: Consider adding security headers like
helmet
(npm install helmet
) for protection against common web vulnerabilities (XSS, clickjacking, etc.).- Enable in
main.ts
:app.use(helmet());
- Enable in
-
SMS Pumping Fraud: Be aware of this risk where attackers abuse open SMS endpoints to send messages to premium-rate numbers they control. Rate limiting and authentication are primary defenses. Also consider monitoring usage patterns for anomalies.
-
Testing for Vulnerabilities:
- Use security scanners (e.g., OWASP ZAP, npm audit/snyk) to check for known vulnerabilities in dependencies.
- Perform penetration testing, especially if handling sensitive data or integrating into a larger system.
- Review code for security best practices (input validation, proper authentication/authorization, secure credential handling).
8. Handling special cases relevant to the domain
Sending SMS involves nuances beyond basic text transfer.
-
International Number Formatting:
- Best Practice: Always store and handle phone numbers in E.164 format (e.g.,
+14155552671
,+442071838750
). This is the internationally recognized standard and avoids ambiguity. - Our
IsPhoneNumber(null)
validator encourages this format. You might need input normalization logic before validation if users enter numbers in local formats. Libraries likegoogle-libphonenumber
can help parse, validate, and format numbers for different regions.
- Best Practice: Always store and handle phone numbers in E.164 format (e.g.,
-
Alphanumeric Sender IDs:
- Instead of a phone number, you can sometimes send SMS from a custom string (e.g.,
""MyAppName""
). - Availability: Support varies significantly by country and carrier network. Some countries require pre-registration.
- Format: Typically 3-11 characters, letters and numbers only (no spaces or special characters).
- Capability: Usually cannot receive replies.
- Configuration: If approved and supported for your destination, set
VONAGE_FROM_NUMBER
in your.env
to the Alphanumeric Sender ID.
- Instead of a phone number, you can sometimes send SMS from a custom string (e.g.,
-
Character Limits and Concatenation:
- A standard SMS segment is 160 GSM-7 characters (or 70 UCS-2 characters for non-Latin alphabets like Cyrillic or Chinese).
- Longer messages are split into multiple segments (concatenated SMS), which are reassembled by the receiving device.