Development Environment Setup
Setting up a proper development environment is crucial for building robust SMS applications with the Lamah API.API Token Management
Never expose your API tokens in client-side code or public repositories. Always use environment variables or secure configuration management.
Copy
# .env file
LAMAH_API_TOKEN=your_api_token_here
LAMAH_BASE_URL=https://sms.lamah.com
Testing Strategies
Unit Testing
Test your SMS integration logic without actually sending messages:Copy
// sms.test.js
const SMSService = require('./sms-service');
// Mock the fetch function
global.fetch = jest.fn();
describe('SMS Service', () => {
beforeEach(() => {
fetch.mockClear();
});
test('should send SMS successfully', async () => {
const mockResponse = {
message_id: 'msg_123',
status: 'sent',
cost: 0.05
};
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const smsService = new SMSService('test-token');
const result = await smsService.sendMessage({
message: 'رسالة تجريبية من خدمة لمحة',
receiver: '00218912345678',
sender: 'Lamah'
});
expect(result.success).toBe(true);
expect(result.data.message_id).toBe('msg_123');
expect(fetch).toHaveBeenCalledWith(
'https://sms.lamah.com/api/sms/messages',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Authorization': 'Bearer test-token'
})
})
);
});
});
Integration Testing
Test with the actual API using test phone numbers:Copy
// integration.test.js
const SMSService = require('./sms-service');
describe('SMS Integration Tests', () => {
const smsService = new SMSService(process.env.LAMAH_TEST_TOKEN);
const testPhoneNumber = process.env.TEST_PHONE_NUMBER;
test('should send real SMS to test number', async () => {
if (!testPhoneNumber) {
console.log('Skipping integration test - no test phone number');
return;
}
const result = await smsService.sendMessage({
message: 'رسالة اختبار تكامل من خدمة لمحة',
receiver: testPhoneNumber,
sender: 'Lamah',
payment_type: 'wallet'
});
expect(result.success).toBe(true);
expect(result.data.message_id).toBeDefined();
// Wait and check message status
await new Promise(resolve => setTimeout(resolve, 5000));
const status = await smsService.getMessageStatus(result.data.message_id);
expect(['sent', 'delivered']).toContain(status.status);
}, 30000); // 30 second timeout
});
Error Handling Best Practices
Comprehensive Error Handling
Copy
class SMSService {
async sendMessage(messageData) {
try {
const response = await fetch(`${this.baseUrl}/api/sms/messages`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(messageData)
});
if (!response.ok) {
const error = await response.json();
return this.handleAPIError(error, response.status);
}
const result = await response.json();
return { success: true, data: result };
} catch (error) {
console.error('Network error:', error);
return {
success: false,
error: 'NETWORK_ERROR',
message: 'Failed to connect to SMS service',
retryable: true
};
}
}
handleAPIError(error, statusCode) {
const errorMap = {
400: { retryable: false, category: 'CLIENT_ERROR' },
401: { retryable: false, category: 'AUTH_ERROR' },
402: { retryable: false, category: 'PAYMENT_ERROR' },
429: { retryable: true, category: 'RATE_LIMIT' },
500: { retryable: true, category: 'SERVER_ERROR' }
};
const errorInfo = errorMap[statusCode] || { retryable: false, category: 'UNKNOWN' };
return {
success: false,
error: error.code || 'API_ERROR',
message: error.error || 'Unknown API error',
...errorInfo,
details: error.details
};
}
}
Rate Limiting and Retry Logic
Implementing Backoff Strategies
Copy
class RateLimitedSMSService {
constructor(apiToken, maxRequestsPerMinute = 100) {
this.apiToken = apiToken;
this.maxRequestsPerMinute = maxRequestsPerMinute;
this.requestQueue = [];
this.processing = false;
}
async sendMessage(messageData) {
return new Promise((resolve, reject) => {
this.requestQueue.push({ messageData, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.processing || this.requestQueue.length === 0) {
return;
}
this.processing = true;
const intervalMs = (60 * 1000) / this.maxRequestsPerMinute;
while (this.requestQueue.length > 0) {
const { messageData, resolve, reject } = this.requestQueue.shift();
try {
const result = await this.makeRequest(messageData);
resolve(result);
} catch (error) {
reject(error);
}
// Wait before next request
if (this.requestQueue.length > 0) {
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
}
this.processing = false;
}
async makeRequest(messageData) {
// Implement actual API request with retry logic
return this.sendWithRetry(messageData);
}
}
Performance Optimization
Connection Pooling and Caching
Copy
// Use connection pooling for high-volume applications
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000
});
// Cache frequently accessed data
class SMSServiceWithCache {
constructor(apiToken) {
this.apiToken = apiToken;
this.cache = new Map();
this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
}
async getProjectDetails() {
const cacheKey = 'project_details';
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.data;
}
const details = await this.fetchProjectDetails();
this.cache.set(cacheKey, {
data: details,
timestamp: Date.now()
});
return details;
}
}
Monitoring and Logging
Structured Logging
Copy
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'sms-error.log', level: 'error' }),
new winston.transports.File({ filename: 'sms-combined.log' })
]
});
class SMSService {
async sendMessage(messageData) {
const startTime = Date.now();
logger.info('SMS send initiated', {
receiver: messageData.receiver,
sender: messageData.sender,
messageLength: messageData.message.length
});
try {
const result = await this.makeAPICall(messageData);
logger.info('SMS sent successfully', {
messageId: result.message_id,
cost: result.cost,
duration: Date.now() - startTime
});
return result;
} catch (error) {
logger.error('SMS send failed', {
error: error.message,
receiver: messageData.receiver,
duration: Date.now() - startTime
});
throw error;
}
}
}
Security Best Practices
Token Security
Store API tokens securely and rotate them regularly
Input Validation
Validate all phone numbers and message content
Rate Limiting
Implement client-side rate limiting to prevent abuse