Back to Blog
Tutorial

Send OTP without SMS: A Developer's Guide

Learn how to implement secure OTP delivery using Inbox API instead of traditional SMS. Better security, lower costs, and improved user experience.

Sarah Chen
January 15, 2025
8 min read

Send OTP without SMS: A Developer's Guide


One-time passwords (OTPs) are a critical security component for modern applications, but traditional SMS delivery has significant drawbacks: high costs, security vulnerabilities, and poor user experience. In this guide, we'll show you how to implement secure OTP delivery using the Inbox API.


Why Move Beyond SMS?


Traditional SMS has several critical issues:


  • Security vulnerabilities: SS7 attacks, SIM swapping, and interception
  • High costs: $0.04+ per message globally
  • Poor UX: Messages can be delayed or lost
  • No rich formatting: Plain text only

  • Inbox solves these problems with end-to-end encrypted, structured messages delivered through a dedicated app.


    Getting Started


    First, sign up for an Inbox account and get your API key:


    curl -X POST https://api.getinbox.app/v1/auth/signup \

    -H "Content-Type: application/json" \

    -d '{"email": "you@example.com", "password": "secure_password"}'


    Sending Your First OTP


    Here's how to send an OTP using the Inbox API:


    const response = await fetch('https://api.getinbox.app/v1/send', {

    method: 'POST',

    headers: {

    'Authorization': 'Bearer YOUR_API_KEY',

    'Content-Type': 'application/json'

    },

    body: JSON.stringify({

    to: '+15551234567',

    type: 'otp',

    body: {

    code: '123456',

    purpose: 'signin',

    expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString()

    }

    })

    });


    Advanced OTP Features


    Auto-Expiring Codes


    Inbox automatically handles code expiration:


    {

    "code": "123456",

    "purpose": "signin",

    "expires_at": "2025-01-15T10:30:00Z",

    "auto_clear": true

    }


    Custom Branding


    Add your app's branding to OTP messages:


    {

    "code": "123456",

    "purpose": "signin",

    "app_name": "MyApp",

    "app_icon": "https://myapp.com/icon.png",

    "expires_at": "2025-01-15T10:30:00Z"

    }


    Implementation Best Practices


    1. Rate Limiting


    Implement proper rate limiting to prevent abuse:


    const rateLimiter = new Map();


    function canSendOTP(phoneNumber) {

    const key = phoneNumber;

    const now = Date.now();

    const attempts = rateLimiter.get(key) || [];


    // Remove attempts older than 1 hour

    const recentAttempts = attempts.filter(time => now - time < 3600000);


    if (recentAttempts.length >= 5) {

    return false; // Max 5 attempts per hour

    }


    recentAttempts.push(now);

    rateLimiter.set(key, recentAttempts);

    return true;

    }


    2. Secure Code Generation


    Use cryptographically secure random number generation:


    import crypto from 'crypto';


    function generateOTP(length = 6) {

    const digits = '0123456789';

    let otp = '';


    for (let i = 0; i < length; i++) {

    const randomIndex = crypto.randomInt(0, digits.length);

    otp += digits[randomIndex];

    }


    return otp;

    }


    3. Verification Flow


    Implement a complete verification flow:


    class OTPService {

    constructor(inboxApiKey) {

    this.apiKey = inboxApiKey;

    this.pendingOTPs = new Map();

    }


    async sendOTP(phoneNumber, purpose) {

    const code = generateOTP();

    const expiresAt = new Date(Date.now() + 10 * 60 * 1000);


    // Store for verification

    this.pendingOTPs.set(phoneNumber, {

    code,

    purpose,

    expiresAt,

    attempts: 0

    });


    // Send via Inbox

    await this.sendToInbox(phoneNumber, code, purpose, expiresAt);


    return { success: true, expiresAt };

    }


    verifyOTP(phoneNumber, submittedCode) {

    const stored = this.pendingOTPs.get(phoneNumber);


    if (!stored) {

    return { success: false, error: 'No OTP found' };

    }


    if (new Date() > stored.expiresAt) {

    this.pendingOTPs.delete(phoneNumber);

    return { success: false, error: 'OTP expired' };

    }


    if (stored.attempts >= 3) {

    this.pendingOTPs.delete(phoneNumber);

    return { success: false, error: 'Too many attempts' };

    }


    if (stored.code !== submittedCode) {

    stored.attempts++;

    return { success: false, error: 'Invalid code' };

    }


    this.pendingOTPs.delete(phoneNumber);

    return { success: true };

    }

    }


    Cost Comparison


    Here's how Inbox compares to traditional SMS:


    | Provider | Cost per OTP | Monthly (1000 OTPs) |

    |----------|--------------|---------------------|

    | Traditional SMS | $0.04 | $40.00 |

    | Inbox | $0.004 | $4.00 |

    | **Savings** | **90%** | **$36.00** |


    Migration from SMS


    Migrating from SMS to Inbox is straightforward:


    Before (SMS)

    await twilioClient.messages.create({

    body: 'Your verification code is: 123456',

    from: '+15551234567',

    to: userPhoneNumber

    });


    After (Inbox)

    await fetch('https://api.getinbox.app/v1/send', {

    method: 'POST',

    headers: {

    'Authorization': 'Bearer YOUR_API_KEY',

    'Content-Type': 'application/json'

    },

    body: JSON.stringify({

    to: userPhoneNumber,

    type: 'otp',

    body: {

    code: '123456',

    purpose: 'signin',

    expires_at: new Date(Date.now() + 10 * 60 * 1000).toISOString()

    }

    })

    });


    Conclusion


    Moving from SMS to Inbox for OTP delivery provides:


  • 90% cost savings compared to traditional SMS
  • Better security with end-to-end encryption
  • Improved UX with structured, actionable messages
  • Auto-expiring codes and smart organization
  • Rich formatting and branding options

  • Ready to get started? [Sign up for Inbox](https://getinbox.app/signup) and send your first OTP in minutes.


    Next Steps


  • [Read the full API documentation](/docs)
  • [Explore message schemas](/docs/schemas)
  • [Set up webhooks for delivery confirmations](/docs/webhooks)
  • [Join our developer community](https://discord.gg/inbox)