Skip to main content

Overview

Proper error handling is crucial for building robust SMS applications. This guide covers all error types, status codes, and best practices for handling failures gracefully.

HTTP Status Codes

The Lamah SMS API uses standard HTTP status codes to indicate the success or failure of requests:
Status CodeDescriptionAction Required
200SuccessContinue normal operation
400Bad RequestFix request parameters
401UnauthorizedCheck API token
402Payment RequiredAdd funds or upgrade plan
403ForbiddenCheck permissions
404Not FoundVerify resource exists
429Too Many RequestsImplement rate limiting
500Internal Server ErrorRetry request

Error Response Format

All error responses follow a consistent JSON format:
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_ERROR_CODE",
  "details": {
    "field": "specific_field_name",
    "value": "invalid_value",
    "additional_info": "context"
  }
}

Common Error Codes

Authentication Errors

{
  "error": "Invalid API token",
  "code": "UNAUTHORIZED"
}
Causes:
  • Missing Authorization header
  • Invalid token format
  • Expired or revoked token
Solutions:
  • Verify token is included in Authorization header
  • Check token format: Bearer YOUR_TOKEN
  • Generate a new token from dashboard
{
  "error": "Insufficient permissions to access this resource",
  "code": "ACCESS_DENIED"
}
Causes:
  • Token doesn’t have required permissions
  • Accessing resources from different project
Solutions:
  • Use correct project token
  • Contact support for permission issues

Validation Errors

{
  "error": "Invalid phone number format",
  "code": "INVALID_PHONE",
  "details": {
    "field": "receiver",
    "value": "123456789"
  }
}
Solutions:
  • Use international format with country code
  • Example: 00218912345678
  • Validate numbers before sending
{
  "error": "Message exceeds maximum length",
  "code": "MESSAGE_TOO_LONG",
  "details": {
    "max_length": 1530,
    "actual_length": 1600
  }
}
Solutions:
  • Trim message to fit limits
  • Split into multiple messages
  • Use templates for consistent formatting

Payment Errors

{
  "error": "Insufficient balance to send SMS",
  "code": "INSUFFICIENT_BALANCE",
  "details": {
    "required": 0.05,
    "available": 0.02
  }
}
Solutions:
  • Add funds to your account
  • Enable auto-recharge
  • Switch to subscription plan
{
  "error": "Payment method declined",
  "code": "PAYMENT_METHOD_FAILED"
}
Solutions:
  • Update payment method
  • Contact your bank
  • Try alternative payment method

Rate Limiting Errors

{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "details": {
    "limit": 100,
    "window": "1 minute",
    "retry_after": 60
  }
}
Solutions:
  • Implement exponential backoff
  • Reduce request frequency
  • Use bulk endpoints for multiple messages

Error Handling Strategies

Basic Error Handling

const sendSMS = async (messageData) => {
  try {
    const response = await fetch('https://sms.lamah.com/api/sms/messages', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_API_TOKEN',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(messageData)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new APIError(error, response.status);
    }

    return await response.json();
  } catch (error) {
    console.error('SMS sending failed:', error);
    throw error;
  }
};

class APIError extends Error {
  constructor(errorData, statusCode) {
    super(errorData.error);
    this.code = errorData.code;
    this.statusCode = statusCode;
    this.details = errorData.details;
  }
}

Advanced Error Handling with Retry Logic

class SMSService {
  constructor(apiToken) {
    this.apiToken = apiToken;
    this.baseUrl = 'https://sms.lamah.com';
  }

  async sendSMSWithRetry(messageData, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const result = await this.sendSMS(messageData);
        return { success: true, data: result };
      } catch (error) {
        const shouldRetry = this.shouldRetry(error, attempt, maxRetries);
        
        if (!shouldRetry) {
          return { success: false, error: this.formatError(error) };
        }

        const delay = this.calculateDelay(attempt);
        await this.sleep(delay);
      }
    }

    return { 
      success: false, 
      error: { code: 'MAX_RETRIES_EXCEEDED', message: 'Maximum retry attempts exceeded' }
    };
  }

  shouldRetry(error, attempt, maxRetries) {
    if (attempt >= maxRetries) return false;

    // Retry on network errors
    if (error.name === 'TypeError' || error.name === 'NetworkError') {
      return true;
    }

    // Retry on specific status codes
    const retryableStatusCodes = [429, 500, 502, 503, 504];
    return retryableStatusCodes.includes(error.statusCode);
  }

  calculateDelay(attempt) {
    // Exponential backoff with jitter
    const baseDelay = 1000; // 1 second
    const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
    const jitter = Math.random() * 1000; // Add up to 1 second of jitter
    return exponentialDelay + jitter;
  }

  formatError(error) {
    return {
      code: error.code || 'UNKNOWN_ERROR',
      message: error.message || 'An unknown error occurred',
      statusCode: error.statusCode,
      details: error.details,
      retryable: this.shouldRetry(error, 1, 2)
    };
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  async sendSMS(messageData) {
    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();
      throw new APIError(error, response.status);
    }

    return response.json();
  }
}

// Usage
const smsService = new SMSService('YOUR_API_TOKEN');

const result = await smsService.sendSMSWithRetry({
  message: 'رسالة تجريبية من خدمة لمحة',
  receiver: '00218912345678',
  sender: 'Lamah',
  payment_type: 'wallet'
});

if (result.success) {
  console.log('SMS sent:', result.data.message_id);
} else {
  console.error('SMS failed:', result.error);
}

Error Monitoring and Alerting

Logging Errors

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: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

const sendSMSWithLogging = async (messageData) => {
  try {
    const result = await sendSMS(messageData);
    
    logger.info('SMS sent successfully', {
      messageId: result.message_id,
      receiver: messageData.receiver,
      cost: result.cost
    });
    
    return result;
  } catch (error) {
    logger.error('SMS sending failed', {
      error: error.message,
      code: error.code,
      receiver: messageData.receiver,
      statusCode: error.statusCode
    });
    
    // Send alert for critical errors
    if (error.statusCode >= 500) {
      await sendAlert('Critical SMS API error', error);
    }
    
    throw error;
  }
};

Best Practices

Graceful Degradation

Handle errors gracefully without breaking user experience

Retry Logic

Implement exponential backoff for transient errors

Error Monitoring

Monitor error rates and set up alerts

User Feedback

Provide meaningful error messages to users

Error Recovery Strategies

  1. Immediate Retry: For network timeouts
  2. Exponential Backoff: For rate limiting
  3. Circuit Breaker: For persistent failures
  4. Fallback Options: Alternative communication methods
  5. User Notification: Inform users of delivery issues

Testing Error Scenarios

describe('SMS Error Handling', () => {
  test('should handle insufficient balance', async () => {
    // Mock API response
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 402,
      json: async () => ({
        error: 'Insufficient balance',
        code: 'INSUFFICIENT_BALANCE',
        details: { required: 0.05, available: 0.02 }
      })
    });

    const result = await smsService.sendSMSWithRetry(messageData);
    
    expect(result.success).toBe(false);
    expect(result.error.code).toBe('INSUFFICIENT_BALANCE');
  });

  test('should retry on rate limit', async () => {
    // Mock rate limit then success
    fetch
      .mockResolvedValueOnce({
        ok: false,
        status: 429,
        json: async () => ({ error: 'Rate limit exceeded', code: 'RATE_LIMIT_EXCEEDED' })
      })
      .mockResolvedValueOnce({
        ok: true,
        json: async () => ({ message_id: 'msg_123', status: 'sent' })
      });

    const result = await smsService.sendSMSWithRetry(messageData);
    
    expect(result.success).toBe(true);
    expect(fetch).toHaveBeenCalledTimes(2);
  });
});