envoisms / envoisms-php
Official PHP SDK for EnvoiSMS.ma - direct-operator SMS, WhatsApp Business API (WABA) and OTP/2FA verification for Morocco
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Official PHP SDK for EnvoiSMS.ma — the direct-operator SMS, WhatsApp Business (WABA) and OTP verification API platform for Morocco (Maroc).
composer require envoisms/envoisms-php
What is EnvoiSMS.ma?
EnvoiSMS.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.
Quick Start — Send an SMS
<?php require_once 'vendor/autoload.php'; use EnvoiSMS\Client; $client = new Client(getenv('ENVOISMS_API_KEY')); $response = $client->send([ 'to' => '+212600000000', 'message' => 'Votre code de vérification est 492018', 'from' => 'MonBusiness', // validated Sender ID, or omit to use your default ]); echo 'Message ID: ' . $response['id']; // poll it with getMessage(), match it in webhooks
Every send() / sendBulk() carries an Idempotency-Key (generated, or pass one as the second argument), so a retry after a timeout can never bill the same message twice.
Choosing a channel: SMS or WhatsApp?
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.
Send a WhatsApp Business Message
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 $response = $client->send([ 'to' => '+212600000000', 'message' => 'Bonjour ! Votre commande #89240 a été expédiée.', 'channel' => 'whatsapp', ]); // Start the conversation yourself: approved template, any time $response = $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']], ]);
Automatic channel fallback (cascade)
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.
$response = $client->send([ 'to' => '+212600000000', 'message' => 'Votre chauffeur arrive dans 2 minutes.', 'channel' => 'whatsapp', 'cascade' => true, ]);
OTP / 2FA Verification
// 1. Send OTP (channel defaults to sms; pass channel => 'whatsapp' to send over WhatsApp instead) $otpResponse = $client->sendOtp([ 'to' => '+212600000000', 'brand' => 'MonBusiness', 'code_length' => 6, 'expiry' => 600, // seconds ]); $sessionId = $otpResponse['session_id']; // 2. Check the code the user typed in $verifyResult = $client->checkOtp($sessionId, '492018'); if (!empty($verifyResult['verified'])) { echo "OTP verified successfully!"; } // Optional: inspect a session's status without consuming an attempt $session = $client->getOtpSession($sessionId);
Bulk Sending
$client->sendBulk([ 'messages' => [ ['to' => '+212600000001', 'message' => 'Promo -20% ce week-end'], ['to' => '+212600000002', 'message' => 'Promo -20% ce week-end'], ], 'from' => 'MonBusiness', ]);
Message Status & Delivery
$status = $client->getMessage('msg_123'); $recent = $client->listMessages(50, 0);
Verifying Delivery Webhooks (DLR)
If you configure a delivery-status webhook, verify its X-EnvoiSMS-Signature header before trusting the payload:
use EnvoiSMS\Client; $rawBody = file_get_contents('php://input'); // raw request body, not the parsed array $isValid = Client::verifyWebhookSignature( $rawBody, $_SERVER['HTTP_X_ENVOISMS_SIGNATURE'], getenv('ENVOISMS_WEBHOOK_SECRET') );
Account & Billing
$balance = $client->getBalance(); $packs = $client->listPacks(); $paymentMethods = $client->listPaymentMethods(); $client->createTopup(['amount_mad' => 200, 'payment_method' => 'stripe']);
Analytics & API Keys
$stats = $client->analytics(30); $newKey = $client->createApiKey(['name' => 'Server key']);
Compliance: Opt-outs (STOP)
$client->createOptout('+212600000000');
Error Handling & Retries
The client retries 5xx responses and cURL errors up to maxRetries times (default 2) with exponential backoff, and throws EnvoiSMSError — with statusCode and errorCode properties — on any failure:
use EnvoiSMS\Client; use EnvoiSMS\EnvoiSMSError; $client = new Client(getenv('ENVOISMS_API_KEY'), maxRetries: 3, timeoutSeconds: 20); try { $client->send(['to' => '+212600000000', 'message' => 'Test']); } catch (EnvoiSMSError $e) { echo "Send failed ({$e->statusCode} {$e->errorCode}): {$e->getMessage()}"; }
Why teams pick EnvoiSMS.ma over an aggregator
- 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.
Documentation & Pricing
- 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
Other official SDKs
- Python:
pip install envoisms - Node.js / TypeScript:
npm install envoisms - WooCommerce, Shopify, Zapier and Google Sheets integrations: envoisms.ma/fr/integrations
Support
- Email: support@envoisms.ma
- Sales: sales@envoisms.ma
License
MIT