June 7, 2026
Email OTP Is Broken in Morocco — Here's the WhatsApp API That Actually Works
Why email OTPs fail in Morocco and MENA, and how to replace them with Wasel's WhatsApp API in 10 minutes. Drop-in REST API for developers.
You’ve just launched your app. Sign-up flow looks clean. You fire the OTP to the user’s email.
They never see it.
Not in spam. Not in promotions. Just… gone. Or received 4 minutes later, after the code expired.
In Europe or the US, this is a minor UX annoyance. In Morocco and most of MENA, it’s a conversion killer.
Here’s why — and what you can do about it today.
Why email fails in Morocco
Morocco has ~95% WhatsApp penetration on smartphones. Every shop owner, every patient, every customer manages their life through WhatsApp. It’s open all day. It has notifications. It gets read.
Email? Most users don’t have a mail app configured on their phone. And those who do have trained themselves to ignore it — promotional tabs, spam filters, delivery delays. The few that get through land 4 hours later when the OTP has long expired.
This isn’t a Moroccan quirk. It’s true across MENA, Sub-Saharan Africa, and large parts of Southeast Asia. WhatsApp is the inbox. Email is where you store receipts you’ll never open.
WhatsApp vs SMS vs Email: honest comparison for Morocco
| SMS | |||
|---|---|---|---|
| Open rate (Morocco) | 8–14% | ~75% | 90–98% |
| Delivery speed | 0–10 min | Instant | Instant |
| Read time | Hours / days | Minutes | Seconds |
| Cost per message | ~$0.001 | ~$0.04–0.07 | ~$0.005–0.015 |
| User friction | High (app switch) | Low | Very low |
| Copy code UX | No | No | ✅ Native button |
| Rich content | HTML (often clipped) | Plain text | Templates + media |
| Works offline | No | ✅ | No |
| Moroccan user expectation | Low | Medium | High |
Honest take: SMS is still better than email in Morocco and works offline. But WhatsApp costs less than SMS, has better UX, and users are already there. The only real case for SMS is when you need guaranteed offline delivery.
What breaks in production
User signs up → OTP sent to email → User doesn't have push email configured (very common) → OTP expires after 5 minutes → User retries 2–3x → User abandonsYour funnel leaks at peak intent. Your email provider reports it as “delivered.” You have zero visibility.
The fix: Wasel External API
Wasel exposes a clean REST API that lets any CRM, ERP, or backend send WhatsApp template messages and OTPs — without touching the WhatsApp Business API directly.
Base URL: https://wasel-api.wasel.ma/external/v1
Auth: X-API-Key: ext_YOUR_API_KEY_HERE
1. WhatsApp OTP — drop-in email replacement
The cleanest part: you never handle the code yourself. The API generates it, sends it via WhatsApp, and validates it. Your backend just calls two endpoints.
Step 1 — Send the OTP
curl -s -X POST "https://wasel-api.wasel.ma/external/v1/otp/send" \ -H "X-API-Key: ext_YOUR_API_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "phone": "+212600000000", "lang": "fr", "ttl_minutes": 10, "reference": "session_abc123" }' | jqParameters:
phone— E.164 format (required)lang—fr/en/ar(default:fr)ttl_minutes— 1–60, default 10max_attempts— 1–10, default 3reference— your session/user ID (recommended, binds send → verify)
The user receives a native WhatsApp message with a one-tap “Copy code” button — no typing, no app-switching.
Step 2 — Verify the code
curl -s -X POST "https://wasel-api.wasel.ma/external/v1/otp/verify" \ -H "X-API-Key: ext_YOUR_API_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "phone": "+212600000000", "code": "847291", "reference": "session_abc123" }' | jqResponse reason values:
| reason | meaning |
|---|---|
verified | ✅ Correct code |
invalid_code | Wrong — attempts counter incremented |
expired | TTL passed — re-send required |
max_attempts_exceeded | Too many wrong guesses |
not_found | No pending OTP for this phone |
Check status anytime
curl -s "https://wasel-api.wasel.ma/external/v1/otp/status?phone=%2B212600000000&reference=session_abc123" \ -H "X-API-Key: ext_YOUR_API_KEY_HERE" | jqRequires a key with scope
otp.
2. Send a single template message
Order confirmations, appointment reminders, payment alerts — triggered directly from your CRM or ERP.
curl -X POST "https://wasel-api.wasel.ma/external/v1/send-template" \ -H "Content-Type: application/json" \ -H "X-API-Key: ext_YOUR_API_KEY_HERE" \ -d '{ "phone": "+212600000000", "template_name": "order_confirmation", "lang": "fr", "variables": ["ORD-1001"], "response_action_key": "order-confirm-v1", "custom_data": { "orderNumber": "ORD-1001", "source": "erp" } }'Two optional power fields:
response_action_key— binds the user’s WhatsApp reply to a workflow action (e.g. user replies “Confirmer” → triggers an automation in your system)custom_data— arbitrary JSON stored with the message for downstream handling in your ERP/CRM
3. Bulk template (up to 500 recipients)
One call, personalized per recipient. No loop on your side.
curl -X POST "https://wasel-api.wasel.ma/external/v1/send-template-bulk" \ -H "Content-Type: application/json" \ -H "X-API-Key: ext_YOUR_API_KEY_HERE" \ -d '{ "template_name": "appointment_reminder", "lang": "fr", "recipients": [ { "phone": "+212600000001", "variables": ["demain à 10h"] }, { "phone": "+212600000002", "variables": ["demain à 14h"] } ] }'Error handling reference
400 invalid payload / missing fields / template not found401 invalid or missing X-API-Key header403 rate limit or quota exceeded404 no OTP found for this phone (status endpoint)429 rate limited — back off and retry500 unexpected server error502 WhatsApp delivery failed — retry after a few secondsDefault rate limit: 60 req/min per key. Simple exponential backoff for 429 / 502:
async function sendWithRetry(payload, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const res = await fetch('https://wasel-api.wasel.ma/external/v1/send-template', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.WASEL_API_KEY }, body: JSON.stringify(payload) });
if (res.status === 429 || res.status === 502) { await new Promise(r => setTimeout(r, 1000 * 2 ** attempt)); continue; }
return res.json(); } throw new Error('Max retries exceeded');}Real-world impact
Switching from email → WhatsApp OTP on Moroccan deployments:
- Sign-up completion rate: +35–50%
- OTP support tickets: -70–80%
- Time to verified: ~4 min average → ~25 seconds
Appointment reminders:
- No-show rate: -40–50%
- Confirmation response rate: ~5% (email) → ~68% (WhatsApp)
Compliance note
WhatsApp requires opt-in. For OTP/transactional: user providing their phone at sign-up is sufficient. For marketing: explicit checkbox required. Morocco follows CNDP regulations, broadly aligned with GDPR.
TL;DR
- Email OTP in Morocco/MENA has 8–14% open rate. WhatsApp: 90%+.
/otp/send+/otp/verifyis a drop-in replacement — you never touch the code itself./send-templateand/send-template-bulkcover every transactional notification your stack needs.response_action_key+custom_datawire WhatsApp replies back into your existing workflows.- Standard REST,
X-API-Keyauth, 60 req/min, clean error codes.
🚀 Start free 7-day trial — no credit card
Get Started Today →Built something with the Wasel API? Drop a comment — happy to help debug or extend the examples.