Back
2 min read1 November 2026
Tutorials

Send an SMS with Python in 5 minutes (complete example)

Send your first SMS to a Tunisian number from Python with the Sendimo API and the requests library: test key, sending, error handling and delivery tracking.

The Sendimo teamSMS platform in Tunisia

This tutorial sends an SMS to a Tunisian number from a Python script, using the requests library and the Sendimo REST API. It works the same way in a Django, Flask or FastAPI app: put the call in your view or background task.

What you need

  • Python 3.9 or later;
  • the requests library: pip install requests;
  • a Sendimo API key. Start with an sk_test_ test key: responses are realistic, but nothing is sent or charged.

Keep the key out of your code, in an environment variable:

export SENDIMO_API_KEY="sk_test_..."

1. Send the SMS

A send is one POST /v1/messages request with the number in international format, the sender name and the text.

import os
import requests

response = requests.post(
    "https://api.sendimo.tn/v1/messages",
    headers={
        "Authorization": f"Bearer {os.environ['SENDIMO_API_KEY']}",
        "Idempotency-Key": "order-4821-otp",
    },
    json={
        "to": "+21620123456",
        "from": "SENDIMO",
        "text": "Your code is 482913. It expires in 5 minutes.",
        "reference": "order-4821",
    },
    timeout=10,
)
response.raise_for_status()  # 4xx/5xx -> exception

message = response.json()
print(message["id"], message["status"])  # msg_01HF8Z3K9QX2 queued

The API answers 202 Accepted: the message is accepted and queued for sending.

{
  "id": "msg_01HF8Z3K9QX2",
  "status": "queued",
  "to": "+21620123456",
  "from": "SENDIMO",
  "encoding": "GSM-7",
  "segments": 1,
  "network": "ooredoo",
  "cost": { "amount": "0.140", "currency": "TND" },
  "reference": "order-4821"
}

Three fields deserve your attention:

  • to: always in E.164 format, with +216;
  • from: a sender name approved on your account;
  • Idempotency-Key: if your script retries after a network failure, the same key guarantees the SMS is sent only once.

2. Test without sending anything

With an sk_test_ key, two numbers simulate the two possible outcomes:

  • +21600000001 always ends delivered;
  • +21600000002 always ends failed.

You can test your whole flow, errors included, before switching to an sk_live_ key.

3. Handle errors

Every error has the same shape, with a stable code:

try:
    response.raise_for_status()
except requests.HTTPError:
    error = response.json()["error"]
    if error["code"] == "insufficient_balance":
        print("Not enough balance: top up your account.")
    elif error["code"] == "invalid_phone_number":
        print("Invalid number:", error["message"])
    else:
        print(error["code"], error["request_id"])

The most common codes:

HTTP Code Cause
400 invalid_phone_number to is not a valid number
401 unauthorized Bad, missing or revoked API key
402 insufficient_balance Not enough balance for the send
403 sender_not_approved from is not an approved sender name
429 rate_limited Too many requests; wait for Retry-After

On a 500 or 503 error, retry with the same Idempotency-Key.

4. Track delivery

The status moves from queued to sent, then to delivered or failed. Two ways to follow it:

  • poll GET /v1/messages/{id};
  • better: receive the message.delivered or message.failed event on your webhook, without polling.

How much does it cost?

This SMS is 45 characters in GSM-7: one segment, billed at your plan's rate, from 0.100 DT to 0.150 DT. The details are in our article on what an SMS costs in Tunisia.

Read next

Ready to send your SMS?

Launching soon in Tunisia. Join early access.