To send SMS in Python, run pip install esms-sms, create an API key in the eSMS dashboard and call esms.messages.send(to=..., text=...). Start with a test key so you can send SMS from Python and see real responses without anything reaching a phone network or touching your balance.

Last reviewed: 29 October 2026

How to send SMS in Python step by step

  1. Create an API key

    Sign in at sms.esmsafrica.io and open API Keys under DEVELOPERS in the left menu. Type a Key name, pick the Environment and tick the Permissions the key needs: Send SMS and Read messages and reports cover sending and status checks. Choose Test (sandbox) while you build. Click Generate key and copy it straight away: the full key is shown once, right after you create it. Test keys start with esms_test_, live keys with esms_live_.

    eSMS API keys page with the key name, environment, permissions, Generate key button and keys table numbered 1 to 5
    1 Key name, 2 Environment (Live or Test), 3 Permissions checkboxes, 4 Generate key button, 5 API keys table.
  2. Install the package

    pip install esms-sms

    The current release on PyPI is 1.2.0. Note the names: you install esms-sms but import esms.

  3. Keep the key out of your code

    Store the key in an environment variable or your secrets manager, and never commit it:

    export ESMS_API_KEY=esms_test_your_key_here
  4. Send a message

    Arguments to send() are keyword-only. The number must include the country code.

    # send.py
    import os
    from esms import Esms
    
    esms = Esms(api_key=os.environ["ESMS_API_KEY"])
    
    res = esms.messages.send(
        to="+254712340001",             # international (E.164) format
        text="Hello from eSMS Africa",
        # sender_id="YOURBRAND",        # optional: an approved sender ID, else the country default
    )
    
    print(res.id, res.status, res.segments)

    Run python send.py. With a test key you get an id starting with test_ and a status of queued; the simulated message then moves to delivered unless you use one of the sandbox magic numbers. With a live key the status is normally submitted. If you omit sender_id, the country's default sender is used.

  5. Read the delivery status

    msg = esms.messages.get(res.id)
    print(msg.status)                   # queued | submitted | sent | delivered | failed ...
    for event in msg.timeline:
        print(event.at, event.event, event.detail)

    For production, let delivery reports come to you by webhook rather than polling: see Webhooks and delivery reports.

  6. Catch errors by code

    Every failure raises an EsmsError (or a subclass) carrying status, code, message and request_id.

    from esms import Esms, EsmsError, InsufficientBalanceError, RateLimitError
    
    try:
        esms.messages.send(to="+254712340001", text="Hi")
    except InsufficientBalanceError as e:
        print(f"Top up: have {e.balance}, need {e.cost} {e.currency}")
    except RateLimitError:
        print("Slow down and retry later")
    except EsmsError as e:
        # e.code is the stable API code, e.g. country_not_enabled
        print(e.status, e.code, e.message, e.request_id)
  7. Go live

    Create a key with Environment Live, enable each destination country, top up, and replace the test key. The code does not change.

Common errors and how to fix them

What you seeWhat it meansHow to fix it
ModuleNotFoundError: No module named 'esms'The package is not installed in the interpreter you are running.Run pip install esms-sms inside the same virtual environment, then import esms.
ValueError: An API key is requiredapi_key was empty.Check the environment variable is set for the process (web server, worker or cron job).
AuthenticationError (401)The key is wrong or revoked.Copy a fresh key from the API Keys page.
403 country_not_enabledLive sending to that country is off for your account. Nothing was charged.Enable it on the Countries page. Test keys work without this.
403 sender_not_approvedThat sender ID is not approved for the destination country.Omit sender_id or request the sender ID on the Sender IDs page.
400 missing_country_codeThe number is in local format.Use the international format, such as +256772123456.
InsufficientBalanceError (402)Not enough balance for the message.Top up, then retry.

Frequently asked questions

Is the Python SDK async?

No, it is synchronous. In asyncio code, run calls in a worker thread, for example with asyncio.to_thread(), or call it from a background task queue.

Does it work with Django and Flask?

Yes. Create one Esms client at start-up, reuse it, and send from a view or a background job.

How do I send the same message to many people?

Use esms.messages.send_bulk(), or esms.messages.send_batch() for personalised text per recipient.

Can I verify webhook signatures with the SDK?

Yes. Esms.verify_webhook(raw_body, signature, secret) checks the X-Webhook-Signature header in constant time.

Ready? Create a free eSMS account, then create a test API key and send your first simulated message in minutes. Nothing is charged until you switch to a live key.