Les six statuts d'un message
Chaque message porte un statut, lisible par GET /v1/messages/{id} et poussé à votre webhook.
- queued — accepté par EnvoiSMS.ma, en attente de départ.
- sent — remis au réseau ; l'opérateur ne s'est pas encore prononcé.
- delivered — l'opérateur confirme la remise sur le téléphone.
- failed — l'opérateur ou la route a refusé le message ; error_code dit pourquoi.
- undeliverable — le numéro ne peut pas être joint (numéro inexistant, résilié).
- unconfirmed — le réseau a accepté le message mais n'a jamais rendu de verdict dans la fenêtre de rapport ; un DLR tardif remplace ce statut.
D'où vient l'accusé de réception
Le DLR (Delivery Report) est émis par l'opérateur du destinataire, pas par nous : nous le relayons tel quel. Un téléphone éteint ou hors réseau retarde la remise, et donc le DLR, parfois de plusieurs heures. « sent » n'est donc pas un échec : c'est une attente.
Lire un échec : error_code
Un message failed ou undeliverable porte un error_code court et stable, sur lequel votre code peut brancher.
- DLR_<statut réseau> — l'échec vient du rapport de l'opérateur (par exemple DLR_REJECTD, DLR_EXPIRED, DLR_UNDELIV).
- GATEWAY_* — échec côté routage EnvoiSMS.ma, avant d'atteindre le réseau.
- META_* — refus WhatsApp, pour les messages envoyés sur ce canal.
- Un code inconnu se traite comme un échec définitif : de nouvelles valeurs peuvent apparaître.
Recevoir les statuts par webhook
Déclarez une URL et abonnez-la aux événements message.sent, message.delivered et message.failed. Chaque appel est signé : vérifiez l'en-tête X-EnvoiSMS-Signature-256 (HMAC-SHA256 du timestamp et du corps) et rejetez un timestamp de plus de 5 minutes. Les métadonnées passées à l'envoi reviennent dans le webhook, pour relier le statut à votre commande ou à votre utilisateur.
Ce qui est facturé
Un message en échec est remboursé dès que nous avons la preuve que le réseau ne l'a pas facturé ; quand cette preuve manque, il est examiné par notre équipe plutôt que remboursé à l'aveugle. Pour un code OTP que vous générez vous-même, ajoutez metadata.purpose = "otp" : sur un échec définitif vers un numéro marocain, le code encore valide (moins de 10 minutes) est renvoyé une fois par une autre route SMS, sans frais supplémentaires.