Official Node.js / TypeScript SDK for EnvoiSMS.ma — the direct-operator SMS, WhatsApp Business (WABA) and OTP verification API platform for Morocco (Maroc).
npm install envoismsEnvoiSMS.ma routes transactional and marketing messages through direct connections to Morocco's three mobile operators, plus the official WhatsApp Cloud API — no aggregator, no gray SIM routes.
| Channel | What it's for | Covered by this SDK |
|---|---|---|
| SMS Direct Opérateurs — IAM, Inwi, Orange | OTP codes, delivery alerts, marketing SMS, from 0.48 MAD/SMS | ✅ send(), sendBulk() |
| WhatsApp Business API (Meta WABA) | Approved templates, interactive buttons, catalog, multi-agent inbox, from 0.65 MAD/message | ✅ send({ channel: 'whatsapp' }) |
| OTP / 2FA Verification | Send + check one-time codes over SMS or WhatsApp | ✅ sendOtp(), checkOtp() |
| Numéro Virtuel (+212) | Cloud Moroccan business line, no physical SIM, shared team inbox | Manage from the dashboard |
| Assistant IA Conversationnel | Darija/French AI agent for COD order confirmation & support handoff | Manage from the dashboard |
Numéros Virtuels and the AI assistant are configured from your EnvoiSMS.ma dashboard today; dedicated SDK endpoints for them are on the roadmap. Everything below (send, OTP, billing, webhooks) works with the SDK right now.
import { EnvoiSMSClient } from 'envoisms';
const client = new EnvoiSMSClient(process.env.ENVOISMS_API_KEY!);
const result = await client.send({
to: '+212600000000',
message: 'Votre code de vérification est 492018',
from: 'MonBusiness', // validated Sender ID, or omit to use your default
});
console.log('Message ID:', result.id); // poll it with getMessage(), match it in webhooksEvery send() / sendBulk() carries an Idempotency-Key (generated, or pass idempotencyKey), so a retry after a timeout can never bill the same message twice.
Default to SMS. It reaches every Moroccan mobile (IAM, Inwi, Orange) with no setup beyond your API key, and it is what an "ordinary text to a customer" needs — even when that customer uses WhatsApp.
channel: "whatsapp" is different in kind, not just in name. It sends from your own WhatsApp Business number, which means:
- the number must be connected in your dashboard (WhatsApp tab) — otherwise the API answers
403 WHATSAPP_NOT_CONNECTEDand nothing is charged; - a free-form text is only accepted while the recipient has written to that number in the last 24 hours (
400 OUT_OF_24H_WINDOWotherwise, nothing charged); - outside that window, you send an approved template (
templatefield), not free text.
| You want to… | Use |
|---|---|
| Send a text to a customer (order status, reminder, alert) | channel: "sms" (the default — just omit channel) |
| Send a one-time code | sendOtp() — pass channel: "whatsapp" for a WhatsApp code through our shared sender, no connection needed |
| Reply on WhatsApp to a customer who wrote to your number in the last 24 h | channel: "whatsapp" with message |
| Start a WhatsApp conversation (marketing, utility) | channel: "whatsapp" with an approved template |
Common mistake: sending an SMS-style text with channel: "whatsapp" "because the customer is on WhatsApp". Both refusals above name the fix — send it as SMS.
Only from a WhatsApp Business number you connected in your dashboard — see the table above. Free text works inside the 24-hour customer window; otherwise send an approved template.
// Reply to a customer who wrote to your number in the last 24 h
await client.send({
to: '+212600000000',
message: 'Bonjour ! Votre commande #89240 a été expédiée.',
channel: 'whatsapp',
});
// Start the conversation yourself: approved template, any time
await client.send({
to: '+212600000000',
message: 'Votre commande #89240 a été expédiée.', // shown in your history; the template body is what goes out
channel: 'whatsapp',
template: { name: 'order_shipped', language: 'fr', variables: ['89240'] },
});Send over WhatsApp and drop back to SMS automatically when a number is unreachable or has no WhatsApp — the same fallback used for VTC riders on flaky mobile data.
await client.send({
to: '+212600000000',
message: 'Votre chauffeur arrive dans 2 minutes.',
channel: 'whatsapp',
cascade: true,
});// 1. Send OTP (channel defaults to sms; pass channel: 'whatsapp' to send over WhatsApp instead)
const otpResponse = await client.sendOtp({
to: '+212600000000',
brand: 'MonBusiness',
code_length: 6,
expiry: 600, // seconds
});
// 2. Check the code the user typed in
const verifyResult = await client.checkOtp({
session_id: otpResponse.session_id,
code: '492018',
});
if (verifyResult.verified) {
console.log('OTP verified successfully');
}
// Optional: inspect a session's status without consuming an attempt
const session = await client.getOtpSession(otpResponse.session_id);await client.sendBulk({
messages: [
{ to: '+212600000001', message: 'Promo -20% ce week-end' },
{ to: '+212600000002', message: 'Promo -20% ce week-end' },
],
from: 'MonBusiness',
});const status = await client.getMessage('msg_123');
const recent = await client.listMessages(50, 0);If you configure a delivery-status webhook, verify its X-EnvoiSMS-Signature header before trusting the payload:
import { EnvoiSMSClient } from 'envoisms';
const isValid = EnvoiSMSClient.verifyWebhookSignature(
rawBody, // raw request body string, not parsed JSON
request.headers['x-envoisms-signature'] as string,
process.env.ENVOISMS_WEBHOOK_SECRET!
);const balance = await client.getBalance();
const packs = await client.listPacks();
const paymentMethods = await client.listPaymentMethods();
await client.createTopup({ amount_mad: 200, payment_method: 'stripe' });const stats = await client.analytics(30);
const newKey = await client.createApiKey({ name: 'Server key' });await client.createOptout('+212600000000');The client retries 5xx responses and network/timeout errors up to maxRetries times (default 2) with exponential backoff, and throws EnvoiSMSError — with statusCode and code properties — on any failure:
import { EnvoiSMSClient, EnvoiSMSError } from 'envoisms';
const client = new EnvoiSMSClient({
apiKey: process.env.ENVOISMS_API_KEY!,
maxRetries: 3,
timeoutMs: 20000,
});
try {
await client.send({ to: '+212600000000', message: 'Test' });
} catch (err) {
if (err instanceof EnvoiSMSError) {
console.error(`Send failed (${err.statusCode} ${err.code}): ${err.message}`);
}
}- Direct routes to IAM, Inwi and Orange — no international transit hop, no gray-route ban risk.
- 2.4–2.8 second OTP latency, measured across IAM, Inwi and Orange — aggregators routing through Europe typically land in the 10s+ range.
- Billing in MAD, no EUR/USD conversion surprises.
- Local support based in Casablanca, not an offshore ticket queue.
See the full breakdown on envoisms.ma.
- Full API reference: envoisms.ma/fr/docs
- Pricing & credit packs: envoisms.ma/fr/tarifs
- Real customer use cases: envoisms.ma/fr/cas-usage
- Create a free account (5 MAD credit included): envoisms.ma/fr/register
- Python:
pip install envoisms - PHP:
composer require envoisms/envoisms-php - WooCommerce, Shopify, Zapier and Google Sheets integrations: envoisms.ma/fr/integrations
- Email: support@envoisms.ma
- Sales: sales@envoisms.ma
MIT