@mirad-work/sms-nestjs
v0.3.0
Published
NestJS adapter for @mirad-work/sms-core
Readme
@mirad-work/sms-nestjs
A comprehensive NestJS adapter for @mirad-work/sms-core - providing seamless SMS functionality for NestJS applications with support for multiple Iranian SMS providers.
🚀 Features
- 🏗️ NestJS Integration: First-class NestJS module with dependency injection support
- 🔧 Multiple Configuration Options: Sync, async, factory-based, and environment-based configuration
- 📱 Multi-Provider Support: Kavenegar, SMS.ir, Melipayamak, IPPanel, and Mock (for testing)
- 🛡️ Robust Error Handling: Comprehensive error handling with proper NestJS exceptions
- 🔄 Retry & Fallback: Built-in retry mechanisms and fallback driver support
- 📊 Bulk Operations: Efficient bulk SMS sending with concurrency control
- 🩺 Health Checks: Built-in health monitoring and service status endpoints
- 📝 Comprehensive Logging: Detailed logging for debugging and monitoring
- 🧪 Testing Ready: Mock driver and testing utilities included
- 📖 TypeScript First: Full TypeScript support with comprehensive type definitions
📦 Installation
npm install @mirad-work/sms-nestjs @mirad-work/sms-core
# or
yarn add @mirad-work/sms-nestjs @mirad-work/sms-corePeer Dependencies
Make sure you have these NestJS packages installed:
npm install @nestjs/common @nestjs/config🏁 Quick Start
1. Basic Setup with Environment Variables
First, create your .env file:
# SMS Configuration
SMS_DEFAULT_DRIVER=kavenegar
SMS_TIMEOUT=10000
# Kavenegar Configuration
SMS_KAVENEGAR_API_KEY=your-kavenegar-api-key
SMS_KAVENEGAR_LINE_NUMBER=your-line-number
# Or for SMS.ir
# SMS_DEFAULT_DRIVER=smsir
# SMS_SMSIR_API_KEY=your-smsir-api-key
# SMS_SMSIR_LINE_NUMBER=your-line-number2. Configure Your Module
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { SmsModule } from '@mirad-work/sms-nestjs';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
SmsModule.forEnvironment({ isGlobal: true }),
],
})
export class AppModule {}3. Use in Your Service
import { Injectable } from '@nestjs/common';
import { SmsService } from '@mirad-work/sms-nestjs';
@Injectable()
export class AuthService {
constructor(private readonly smsService: SmsService) {}
async sendOtp(phoneNumber: string, code: string) {
const message = this.smsService.createVerificationMessage(
phoneNumber,
'otp-verification', // Your template name
{ code } // Template variables
);
return await this.smsService.verify(message);
}
}📋 Configuration Options
Environment-based Configuration (Recommended)
import { SmsModule } from '@mirad-work/sms-nestjs';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
SmsModule.forEnvironment({ isGlobal: true }),
],
})
export class AppModule {}Synchronous Configuration
import { SmsModule, DriverType } from '@mirad-work/sms-nestjs';
@Module({
imports: [
SmsModule.forRoot({
config: {
defaultDriver: DriverType.KAVENEGAR,
timeout: 10000,
drivers: {
kavenegar: {
url: 'https://api.kavenegar.com/v1/',
apiKey: 'your-api-key',
lineNumber: 'your-line-number',
},
},
},
isGlobal: true,
}),
],
})
export class AppModule {}Asynchronous Configuration with Factory
import { SmsModule, NestSmsConfigHelper } from '@mirad-work/sms-nestjs';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot(),
SmsModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) =>
NestSmsConfigHelper.createFromConfigService(configService),
inject: [ConfigService],
isGlobal: true,
}),
],
})
export class AppModule {}Custom Configuration Factory
import { Injectable } from '@nestjs/common';
import {
SmsOptionsFactory,
SmsModule,
NestSmsConfigHelper
} from '@mirad-work/sms-nestjs';
import { ConfigService } from '@nestjs/config';
import { ISmsConfig } from '@mirad-work/sms-core';
@Injectable()
export class SmsConfigService implements SmsOptionsFactory {
constructor(private configService: ConfigService) {}
createSmsOptions(): ISmsConfig {
const environment = this.configService.get('NODE_ENV');
if (environment === 'test') {
return NestSmsConfigHelper.createForTesting();
}
return NestSmsConfigHelper.createFromConfigService(this.configService);
}
}
@Module({
imports: [
SmsModule.forRootAsync({
useClass: SmsConfigService,
isGlobal: true,
}),
],
providers: [SmsConfigService],
})
export class AppModule {}🔧 Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| SMS_DEFAULT_DRIVER | Default SMS driver | kavenegar |
| SMS_TIMEOUT | Request timeout (ms) | 10000 |
| Kavenegar |
| SMS_KAVENEGAR_URL | Kavenegar API URL | https://api.kavenegar.com/v1/ |
| SMS_KAVENEGAR_API_KEY | Kavenegar API key | - |
| SMS_KAVENEGAR_LINE_NUMBER | Kavenegar line number | - |
| SMS.ir |
| SMS_SMSIR_URL | SMS.ir API URL | https://api.sms.ir/v1/ |
| SMS_SMSIR_API_KEY | SMS.ir API key | - |
| SMS_SMSIR_LINE_NUMBER | SMS.ir line number | - |
| Melipayamak |
| SMS_MELIPAYAMAK_URL | Melipayamak API URL | https://console.melipayamak.com/api/ |
| SMS_MELIPAYAMAK_API_KEY | Melipayamak API key | - |
| SMS_MELIPAYAMAK_LINE_NUMBER | Melipayamak line number | - |
| IPPanel |
| SMS_IPPANEL_URL | IPPanel API URL | https://api2.ippanel.com/ |
| SMS_IPPANEL_API_KEY | IPPanel API key | - |
| SMS_IPPANEL_LINE_NUMBER | IPPanel line number | - |
| Mock (Testing) |
| SMS_USE_MOCK | Use mock driver | false |
| SMS_MOCK_SHOULD_FAIL | Mock driver should fail | false |
| SMS_MOCK_DELAY | Mock driver delay (ms) | 0 |
🚀 Usage Examples
Basic SMS Sending
@Injectable()
export class NotificationService {
constructor(private smsService: SmsService) {}
async sendWelcome(phoneNumber: string, name: string) {
const response = await this.smsService.verify({
to: phoneNumber,
template: 'welcome',
tokens: { name },
});
if (response.success) {
console.log(`Welcome SMS sent: ${response.messageId}`);
} else {
console.error(`Failed to send SMS: ${response.error}`);
}
return response;
}
}Bulk SMS Operations
async sendBulkNotifications(recipients: Array<{phone: string, code: string}>) {
const messages = recipients.map(recipient => ({
to: recipient.phone,
template: 'otp-verification',
tokens: { code: recipient.code },
}));
const responses = await this.smsService.verifyBulk(messages, {
concurrency: 5, // Send 5 at a time
failFast: false, // Continue even if some fail
});
const successful = responses.filter(r => r.success).length;
console.log(`Sent ${successful}/${responses.length} SMS messages`);
return responses;
}Health Monitoring
@Controller('health')
export class HealthController {
constructor(private smsService: SmsService) {}
@Get('sms')
async checkSmsHealth() {
const health = await this.smsService.healthCheck();
if (health.status === 'unhealthy') {
throw new ServiceUnavailableException('SMS service is unhealthy');
}
return health;
}
}Advanced Error Handling
@Injectable()
export class SmsNotificationService {
constructor(private smsService: SmsService) {}
async sendWithRetry(phoneNumber: string, template: string, tokens: any) {
const maxRetries = 3;
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await this.smsService.verify({
to: phoneNumber,
template,
tokens,
});
if (response.success) {
return response;
} else {
// Provider returned error - don't retry
throw new Error(response.error || 'SMS sending failed');
}
} catch (error) {
lastError = error;
if (attempt < maxRetries) {
// Exponential backoff
await this.delay(Math.pow(2, attempt) * 1000);
}
}
}
throw lastError;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}🧪 Testing
Setup for Testing
import { Test } from '@nestjs/testing';
import { SmsModule, SmsService } from '@mirad-work/sms-nestjs';
describe('MyService', () => {
let smsService: SmsService;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [
SmsModule.forTesting({
shouldFail: false, // Control if SMS should succeed/fail
delay: 100, // Simulate network delay
}),
],
providers: [MyService],
}).compile();
smsService = module.get<SmsService>(SmsService);
});
it('should send SMS successfully', async () => {
const response = await smsService.verify({
to: '+989123456789',
template: 'test-template',
tokens: { code: '123456' },
});
expect(response.success).toBe(true);
expect(response.messageId).toBeDefined();
});
});Mock Specific Scenarios
// Test failure scenarios
const failingModule = await Test.createTestingModule({
imports: [
SmsModule.forTesting({
shouldFail: true, // All SMS will fail
delay: 0,
}),
],
}).compile();
// Test with realistic delays
const realisticModule = await Test.createTestingModule({
imports: [
SmsModule.forTesting({
shouldFail: false,
delay: 500, // 500ms delay to simulate real network
}),
],
}).compile();📊 API Reference
SmsService Methods
verify(message: ISmsMessage): Promise<ISmsResponse>
Send a verification SMS (OTP, etc.)
verifyBulk(messages: ISmsMessage[], options?): Promise<ISmsResponse[]>
Send multiple SMS messages with concurrency control
createVerificationMessage(to, template, tokens, options?): ISmsMessage
Create a properly formatted SMS message object
getAvailableDrivers(): DriverType[]
Get list of available/configured drivers
isDriverAvailable(driver: DriverType): boolean
Check if a specific driver is available
getDefaultDriver(): DriverType
Get the default driver configured
getServiceInfo(): ServiceInfo
Get comprehensive service information
healthCheck(): Promise<HealthStatus>
Perform service health check
Configuration Helpers
NestSmsConfigHelper.createFromConfigService(configService): ISmsConfig
Create configuration from NestJS ConfigService
NestSmsConfigHelper.createForTesting(options?): ISmsConfig
Create configuration for testing with mock driver
NestSmsConfigHelper.validateDriverEnvironment(driver, configService): boolean
Validate required environment variables for a driver
🔍 Troubleshooting
Common Issues
Module not found errors
npm install @mirad-work/sms-coreConfiguration validation errors
// Check required environment variables const missing = NestSmsConfigHelper.getMissingEnvironmentVars( DriverType.KAVENEGAR, configService ); console.log('Missing vars:', missing);SMS not sending
// Check service health const health = await smsService.healthCheck(); console.log('Service status:', health); // Check available drivers const drivers = smsService.getAvailableDrivers(); console.log('Available drivers:', drivers);
Debug Mode
Enable debug logging in your NestJS application:
// main.ts
import { Logger } from '@nestjs/common';
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log', 'debug'], // Enable debug logs
});🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
Development Setup
# Clone the repository
git clone https://github.com/mirad-work/sms-nestjs
cd sms-nestjs
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:cov📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🏢 About Mirad Work
This package is maintained by Mirad Work Organization - building quality open-source tools for the Persian/Iranian developer community.
📞 Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
🙏 Acknowledgments
- @mirad-work/sms-core - The core SMS library
- NestJS - The progressive Node.js framework
- All SMS provider APIs for their services
