Search by

envoisms / envoisms-php

FraudShield1

Official PHP SDK for EnvoiSMS.ma - direct-operator SMS, WhatsApp Business API (WABA) and OTP/2FA verification for Morocco

v1.2.0 2026-09-16 13:45 UTC

This package is auto-updated.

Last update: 2026-09-16 13:52:21 UTC


README

Packagist version License: MIT

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_CONNECTED and 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_WINDOW otherwise, nothing charged);
  • outside that window, you send an approved template (template field), 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

Other official SDKs

Support

License

MIT