@ciphercross/nestjs-twilio-otp
v1.0.1
Published
Universal NestJS module for Twilio OTP SMS sending with dynamic configuration
Readme
🚀 @ciphercross/nestjs-twilio-otp
Production-ready NestJS module for sending OTP SMS via Twilio
with dynamic configuration, async factory support,
built-in templates, mock mode, and phone utilities.
Ideal for authentication flows, onboarding, PIN resets,
and any SMS-based verification logic.
📦 Installation
npm install @ciphercross/nestjs-twilio-otp twilio
# or
yarn add @ciphercross/nestjs-twilio-otp twilioOptional: For enhanced international phone number validation, install libphonenumber-js:
npm install libphonenumber-js
# or
yarn add libphonenumber-jsIf libphonenumber-js is installed, the module will automatically use it for more accurate phone number validation. Otherwise, it falls back to basic regex validation.
⚙️ Features
- 🔐 OTP SMS sending with templates
- ⚡ Twilio-powered delivery
- 🔁 forRoot, forRootAsync, forRootWithConfig
- 🎛️ Configurable through ConfigModule
- 🧪 Mock mode (enabled: false) for dev/test environments
- 🌍 Phone formatting & validation helpers
- 💡 Custom OTP message templates
- 🧰 Fully typed TypeScript API
- 🧱 Works with NestJS 10.x / 11.x
🚀 Quick Start
1) Basic usage (forRoot)
import { Module } from '@nestjs/common';
import { TwilioModule } from '@ciphercross/nestjs-twilio-otp';
@Module({
imports: [
TwilioModule.forRoot({
accountSid: 'your-account-sid',
authToken: 'your-auth-token',
phoneNumber: '+1234567890', // or messagingServiceSid
messagingServiceSid: 'your-service-sid', // optional
enabled: true,
appName: 'MyApp',
defaultOtpExpiryMinutes: 10,
}),
],
})
export class AppModule {}2) Configuration using ConfigModule
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TwilioModule } from '@ciphercross/nestjs-twilio-otp';
@Module({
imports: [
ConfigModule.forRoot(),
TwilioModule.forRootWithConfig('twilio'), // configService.get('twilio.*')
],
})
export class AppModule {}Example .env:
TWILIO_ACCOUNT_SID=...
TWILIO_AUTH_TOKEN=...
TWILIO_PHONE_NUMBER=+1234567890
TWILIO_MESSAGING_SERVICE_SID=...
TWILIO_ENABLED=true3) Async factory (forRootAsync)
@Module({
imports: [
ConfigModule.forRoot(),
TwilioModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
accountSid: config.get('TWILIO_ACCOUNT_SID'),
authToken: config.get('TWILIO_AUTH_TOKEN'),
phoneNumber: config.get('TWILIO_PHONE_NUMBER'),
messagingServiceSid: config.get('TWILIO_MESSAGING_SERVICE_SID'),
enabled: config.get('TWILIO_ENABLED') === 'true',
appName: 'MyApp',
defaultOtpExpiryMinutes: 10,
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}📤 Sending SMS & OTP
TwilioService example
import { Injectable } from '@nestjs/common';
import { TwilioService } from '@ciphercross/nestjs-twilio-otp';
@Injectable()
export class AuthService {
constructor(private readonly twilio: TwilioService) {}
async sendOtp(phone: string, code: string) {
const result = await this.twilio.sendOtpSms(phone, code, 'sign_up');
if (result.success) {
console.log('OTP sent:', result.messageId);
} else {
console.error('Failed to send OTP:', result.error);
}
}
async sendCustomSms(phone: string, message: string) {
return this.twilio.sendSms({
to: phone,
message,
purpose: 'notification',
});
}
validatePhone(phone: string) {
return this.twilio.validatePhoneNumber(phone);
}
formatPhone(phone: string) {
return this.twilio.formatPhoneNumber(phone, '+1');
}
}🎨 Custom OTP Message
TwilioModule.forRoot({
accountSid: 'sid',
authToken: 'token',
phoneNumber: '+1234567890',
customMessageFormatter: (code: string, purpose: string) =>
`Your verification code is ${code} (${purpose})`,
});🧪 Mock Mode (No SMS Sent)
Perfect for development & automated tests.
TwilioModule.forRoot({
accountSid: 'test',
authToken: 'test',
enabled: false, // no requests sent to Twilio
});Mock response example:
{
"success": true,
"messageId": "mock-<uuid>"
}📘 API Reference
TwilioService
Methods
| Method | Description |
|--------|-------------|
| sendSms(options) | Send any SMS message |
| sendOtpSms(phone, code, purpose) | Send OTP code |
| validatePhoneNumber(phone) | Validate phone format |
| formatPhoneNumber(phone, countryCode?) | Format into E.164 |
| getMessageTemplate(code, purpose) | Build OTP message |
| getConfigStatus() | Check Twilio configuration state |
| testConnection() | Verify Twilio credentials |
🔑 Supported OTP Purposes
sign_upsign_inpin_resetpassword_resetbusiness_secret_resetphone_change- (any custom string uses fallback template)
🧰 Phone Utilities
Standalone import:
import {
formatPhoneNumber,
maskPhoneNumber,
normalizePhoneNumber,
isValidPhoneFormat,
extractCountryCode,
validatePhoneNumber,
} from '@ciphercross/nestjs-twilio-otp';⚠️ Rate Limiting & Security
Important: This module does not implement rate limiting. To protect your application from abuse and prevent excessive SMS costs, you should implement rate limiting in your application layer.
Recommended approaches:
- Use NestJS throttler guards (
@nestjs/throttler) - Implement rate limiting middleware (e.g.,
express-rate-limitwith Redis) - Track OTP requests per phone number/IP address
- Set maximum requests per time window (e.g., 3 requests per 15 minutes per phone number)
Example with NestJS Throttler:
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ThrottlerModule.forRoot([{
ttl: 60000, // 1 minute
limit: 3, // 3 requests per minute
}]),
TwilioModule.forRoot({...}),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}📄 License
MIT
