The "over_sms_send_rate_limit: SMS send rate limit exceeded" error occurs when Supabase Auth blocks SMS sending due to rate limiting. This protection prevents SMS bombing attacks and excessive usage, typically triggered by too many authentication attempts to a single phone number within a short time period or exceeding overall project SMS quotas.
The "over_sms_send_rate_limit" error is a Supabase Auth security feature that protects against SMS abuse and excessive usage. When this error occurs, Supabase has detected that SMS sending attempts have exceeded predefined rate limits for either a specific phone number or your entire project. This rate limiting serves several important purposes: 1. **Security**: Prevents SMS bombing attacks where malicious actors attempt to flood a phone number with authentication messages 2. **Cost control**: Limits unexpected SMS charges by preventing runaway loops or misconfigured applications 3. **Compliance**: Helps maintain compliance with carrier regulations and anti-spam policies 4. **Resource protection**: Ensures fair usage of Supabase's SMS infrastructure The error typically appears when: - A user requests too many SMS OTP codes in rapid succession - Your application has a bug causing repeated SMS sending attempts - Multiple users are simultaneously triggering SMS authentication - You've exceeded your project's daily or monthly SMS quota
Add client-side protections to prevent users from triggering rate limits:
// Client-side rate limiting for SMS requests
let lastSmsRequestTime = 0;
const SMS_COOLDOWN_MS = 60000; // 60 seconds between requests
async function requestSmsOtp(phoneNumber) {
const now = Date.now();
const timeSinceLastRequest = now - lastSmsRequestTime;
if (timeSinceLastRequest < SMS_COOLDOWN_MS) {
const remainingSeconds = Math.ceil((SMS_COOLDOWN_MS - timeSinceLastRequest) / 1000);
throw new Error(`Please wait ${remainingSeconds} seconds before requesting another code`);
}
lastSmsRequestTime = now;
const { error } = await supabase.auth.signInWithOtp({
phone: phoneNumber,
options: { channel: 'sms' }
});
if (error) {
if (error.code === 'over_sms_send_rate_limit') {
// Show user-friendly message with longer cooldown
return {
success: false,
message: 'Too many attempts. Please wait 5 minutes before trying again.',
cooldown: 300000 // 5 minutes
};
}
throw error;
}
return { success: true, message: 'SMS sent successfully' };
}Best practices:
- Minimum 60-second cooldown between SMS requests per phone number
- Show clear countdown timers to users
- Disable SMS request buttons during cooldown periods
- Implement exponential backoff for retries
Protect SMS endpoints from automated attacks:
// Using reCAPTCHA v3 with Supabase
async function sendSmsWithCaptcha(phoneNumber) {
// Get reCAPTCHA token
const token = await grecaptcha.execute('YOUR_SITE_KEY', {action: 'sms_request'});
// Verify token with your backend
const captchaValid = await verifyCaptchaToken(token);
if (!captchaValid) {
throw new Error('CAPTCHA verification failed');
}
// Proceed with SMS request
const { error } = await supabase.auth.signInWithOtp({
phone: phoneNumber,
options: { channel: 'sms' }
});
return { error };
}
// Alternative: hCaptcha or Turnstile
async function sendSmsWithTurnstile(phoneNumber) {
const token = await turnstile.execute('YOUR_WIDGET_ID');
const response = await fetch('/api/verify-turnstile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
});
if (!response.ok) {
throw new Error('Bot protection verification failed');
}
const { error } = await supabase.auth.signInWithOtp({
phone: phoneNumber,
options: { channel: 'sms' }
});
return { error };
}Implementation options:
- Google reCAPTCHA v3 (invisible)
- hCaptcha
- Cloudflare Turnstile
- Custom challenge-response mechanisms
Set up monitoring to identify patterns and abuse:
// Log rate limit events for analysis
async function handleSmsError(error, phoneNumber, context) {
const logEntry = {
timestamp: new Date().toISOString(),
errorCode: error.code,
errorMessage: error.message,
phoneNumber: maskPhoneNumber(phoneNumber), // Mask for privacy
context: context,
userAgent: navigator.userAgent,
ipAddress: await getClientIp() // From your backend
};
// Send to analytics service
await fetch('/api/log/sms-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
});
// Check Supabase logs for more details
console.warn('SMS rate limit triggered:', {
phoneNumber: maskPhoneNumber(phoneNumber),
time: new Date().toISOString(),
errorDetails: error
});
}
// Check Supabase Dashboard for logs:
// 1. Go to Project → Authentication → Logs
// 2. Filter for "over_sms_send_rate_limit" events
// 3. Look for patterns:
// - Same IP address making many requests
// - Same phone number receiving excessive SMS
// - Time-based patterns indicating automated attacksMonitoring tools:
- Supabase Authentication Logs dashboard
- Custom logging to analytics platform (Segment, Mixpanel, etc.)
- Error tracking (Sentry, Bugsnag)
- Security monitoring (fail2ban patterns, IP blocking)
Provide alternatives when SMS rate limits are hit:
// Multi-method authentication with fallbacks
async function authenticateUser(identifier, method = 'auto') {
try {
if (method === 'sms' || method === 'auto') {
const { error } = await supabase.auth.signInWithOtp({
phone: identifier,
options: { channel: 'sms' }
});
if (!error) return { success: true, method: 'sms' };
if (error.code === 'over_sms_send_rate_limit') {
console.warn('SMS rate limited, trying email fallback');
// Check if identifier could be an email
if (identifier.includes('@')) {
return await authenticateUser(identifier, 'email');
}
// Offer alternative methods
return {
success: false,
method: 'sms',
error: 'SMS rate limited. Please try again in 5 minutes or use email authentication.',
fallbacks: ['email', 'magic_link']
};
}
}
if (method === 'email' || method === 'auto') {
const { error } = await supabase.auth.signInWithOtp({
email: identifier
});
if (!error) return { success: true, method: 'email' };
}
// Magic link fallback
if (method === 'magic_link') {
const { error } = await supabase.auth.signInWithOtp({
email: identifier,
options: {
emailRedirectTo: 'https://yourapp.com/auth/callback'
}
});
if (!error) return { success: true, method: 'magic_link' };
}
throw new Error('All authentication methods failed');
} catch (err) {
console.error('Authentication failed:', err);
return { success: false, error: err.message };
}
}Fallback options:
- Email OTP authentication
- Magic links
- Social login (Google, GitHub, etc.)
- One-time backup codes
- Time-based waiting periods with clear user communication
Reduce unnecessary SMS sends:
// Cache successful authentications to reduce SMS volume
const smsCache = new Map();
async function getCachedSmsAuth(phoneNumber) {
const cacheKey = `sms-auth:${phoneNumber}`;
const cached = smsCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < 300000) { // 5 minute cache
return { cached: true, data: cached.data };
}
// Remove expired entries
for (const [key, value] of smsCache.entries()) {
if (Date.now() - value.timestamp > 300000) {
smsCache.delete(key);
}
}
return { cached: false };
}
// Implement resend logic with increasing delays
async function resendSmsOtp(phoneNumber, attempt = 1) {
const baseDelay = 60000; // 60 seconds
const maxDelay = 300000; // 5 minutes
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
if (attempt > 3) {
throw new Error('Maximum resend attempts reached. Please contact support.');
}
await new Promise(resolve => setTimeout(resolve, delay));
const { error } = await supabase.auth.signInWithOtp({
phone: phoneNumber,
options: { channel: 'sms' }
});
return { error, nextAttemptDelay: Math.min(delay * 2, maxDelay) };
}
// Usage
const result = await resendSmsOtp('+1234567890', 1);
if (result.error && result.error.code === 'over_sms_send_rate_limit') {
console.log(`Next attempt available in ${result.nextAttemptDelay / 1000} seconds`);
}Optimization strategies:
- Cache authentication states to prevent duplicate SMS sends
- Implement exponential backoff for resend requests
- Use session-based authentication where appropriate
- Consider push notifications or in-app messages as alternatives
If legitimate traffic is hitting rate limits:
## When to contact support:
1. High-volume legitimate traffic: Your application has many genuine users needing SMS auth
2. Business-critical applications: SMS authentication is essential for your service
3. Scheduled maintenance/testing: Need temporary limit increases for testing
## Information to provide:
- Your Supabase project ID
- Current error rates and patterns
- Estimated SMS volume requirements
- Use case details (B2C, B2B, internal tool, etc.)
- Security measures you have in place (CAPTCHA, rate limiting, etc.)
## Alternative solutions to discuss:
1. Custom rate limit tiers: Higher limits for verified business use cases
2. Burst allowances: Temporary increases for specific events
3. Enterprise plans: Higher limits available with paid plans
4. Custom SMS providers: Using your own Twilio/MessageBird account with higher limits
## Important considerations:
- Rate limits exist for security and cost reasons
- Supabase may require additional security measures before increasing limits
- Consider implementing email or social login as primary methods
- Evaluate if SMS is truly necessary for your use case
## Contact methods:
1. Supabase Dashboard Support: Project → Support
2. Discord Community: #help channel for advice
3. Email Support: [email protected] for business inquiries
## Understanding Supabase SMS Rate Limits
### Default Rate Limits (Subject to Change)
Supabase imposes several layers of rate limiting for SMS authentication:
1. Per-phone number limits:
- Typically 5-10 SMS attempts per hour per phone number
- Prevents SMS bombing attacks on individual users
2. Per-project limits:
- Overall SMS sending capacity per project
- Varies based on plan (Free, Pro, Enterprise)
- Includes both authentication and custom SMS sends
3. Geographic limits:
- Some countries/carriers have stricter limits
- International SMS may have additional restrictions
### Rate Limit Headers and Responses
When rate limited, Supabase returns:
- HTTP Status: 429 Too Many Requests
- Error code: over_sms_send_rate_limit
- Retry-After header (if available): Suggested wait time in seconds
### Security Implications
Rate limiting is a critical security feature:
- Prevents denial-of-wallet attacks: SMS costs money; unlimited sending could incur huge bills
- Protects user privacy: Prevents harassment via SMS bombing
- Compliance: Meets carrier requirements for A2P (application-to-person) messaging
- Resource fairness: Ensures all Supabase users have access to SMS services
### Monitoring and Alerting Setup
Implement comprehensive monitoring:
# Example alert configuration (Prometheus + Alertmanager)
groups:
- name: supabase-sms-alerts
rules:
- alert: HighSMSRateLimitRate
expr: rate(supabase_sms_rate_limit_errors_total[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High rate of SMS rate limiting"
description: "{{ $value }} SMS rate limit errors per second"
- alert: SMSFailureRateHigh
expr: sum(rate(supabase_sms_failures_total[5m])) / sum(rate(supabase_sms_attempts_total[5m])) > 0.1
for: 10m
labels:
severity: critical
annotations:
summary: "High SMS failure rate"
description: "{{ $value | humanizePercentage }} of SMS attempts are failing"### Alternative Authentication Strategies
Consider these alternatives to reduce SMS dependency:
1. Email-based authentication: Lower cost, fewer rate limits
2. Social providers: Google, GitHub, etc. handle their own rate limiting
3. WebAuthn/Passkeys: Passwordless, secure, no SMS costs
4. Magic links: Email-based, good user experience
5. TOTP apps: Google Authenticator, Authy, etc.
6. Backup codes: For emergency access when SMS fails
### Carrier-Specific Considerations
Different carriers have different SMS handling:
- Twilio: Detailed error codes for carrier rejections
- MessageBird: Regional delivery optimizations
- Vonage: Strong European coverage
- Local providers: Often cheaper but may have delivery issues
### Legal and Compliance Notes
- GDPR: Phone numbers are personal data; ensure proper consent
- TCPA (US): Requires prior express consent for marketing SMS
- CASL (Canada): Similar consent requirements
- 10DLC (US): Registration required for business SMS
- Carrier filtering: Some carriers block short codes or certain content
email_conflict_identity_not_deletable: Cannot delete identity because of email conflict
How to fix "Cannot delete identity because of email conflict" in Supabase
mfa_challenge_expired: MFA challenge has expired
How to fix "mfa_challenge_expired: MFA challenge has expired" in Supabase
conflict: Database conflict, usually related to concurrent requests
How to fix "database conflict usually related to concurrent requests" in Supabase
phone_exists: Phone number already exists
How to fix "phone_exists" in Supabase
StorageApiError: resource_already_exists
StorageApiError: Resource already exists