Install and send your first SMS
pip install envoisms, then client = EnvoiSMSClient(api_key=os.getenv("ENVOISMS_API_KEY")). client.send(to="+212600000000", message="…", from_sender="MyBusiness") returns the message id, which you poll with get_message() and match in your webhooks.
- from envoisms import EnvoiSMSClient
- from_sender: your validated Sender ID, or omit it for your default
- send_bulk(messages=[...], from_sender=...) for a bulk send; list_messages(limit=50, offset=0) for recent sends
Retry-safe sends and errors
Every send() and send_bulk() carries an Idempotency-Key — generated, or pass idempotency_key= — so a retry after a timeout can never bill the same message twice. The client retries 5xx responses and network errors up to max_retries times (2 by default) with exponential backoff, then raises EnvoiSMSError with status_code and code attributes.
- EnvoiSMSClient(api_key=..., max_retries=3, timeout=20)
- except EnvoiSMSError as e: e.status_code, e.code
SMS or WhatsApp, and the cascade
Default to SMS: it reaches every Moroccan mobile with no setup beyond the API key. channel="whatsapp" sends from your own connected WhatsApp Business number — 403 WHATSAPP_NOT_CONNECTED without one, 400 OUT_OF_24H_WINDOW for free text outside the 24-hour customer window, nothing charged in either case. Outside the window, pass an approved template. cascade=True drops back to SMS when the number is unreachable or has no WhatsApp.
- template={"name": "order_shipped", "language": "fr", "variables": ["89240"]}
- Leave channel on "sms" for any ordinary text to a customer
OTP, webhooks and the account
client.send_otp(to=..., brand="MyBusiness", code_length=6, expiry=600) returns a session_id; client.check_otp(session_id=..., code=...) returns verified; get_otp_session() inspects a session without consuming an attempt. EnvoiSMSClient.verify_webhook_signature(raw_body=..., signature_header=..., secret=...) checks the X-EnvoiSMS-Signature header against the raw request body.
- send_otp(..., channel="whatsapp"): a WhatsApp code through the shared sender, no connection needed
- get_balance(), list_packs(), create_topup(amount_mad=200, payment_method="stripe")
- analytics(days=30), create_api_key(), create_optout()