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.
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:
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)
Related Posts
The Hidden Risks of SMS: SS7 Vulnerabilities and Roaming Attacks
Explore the security vulnerabilities in SMS infrastructure and why businesses are moving to more secure messaging platforms.
Building Rich Notifications: Beyond Plain Text Messages
Explore how to create engaging, interactive messages with buttons, images, and structured content using the Inbox platform.