Skip to main content

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.
# .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:
// 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:
// 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

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

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

// 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

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