SendAfrica logoSendAfricaDocs

Python SDK

pip install sendafrica — typed dataclass responses, async support via httpx, retry with backoff, and webhook verification for FastAPI/Django apps.


Packagesendafrica on PyPI · v1.0.1 · stable
Installpip install sendafrica
RequiresPython 3.9+
Repogithub.com/SendAfrica/SendAfrica-python-sdk

The Python SDK wraps the REST API in idiomatic Python — typed dataclass responses, automatic retry with exponential backoff on 429/5xx errors, local phone normalization to E.164 before any network call, and helpers for webhook signature verification.

#Installation

install.sh
bash
# Standard install
pip install sendafrica

# With async support (adds httpx)
pip install "sendafrica[async]"

# Development (testing, coverage, async)
pip install -e ".[dev,async]"

#Client setup

auth.py
python
from sendafrica import SendAfrica

# Option 1: explicit key
client = SendAfrica(api_key="SA-xxxxx")

# Option 2: set env var, then no arguments needed
# export SENDAFRICA_API_KEY="SA-xxxxx"
client = SendAfrica()

# Full configuration
client = SendAfrica(
    api_key="SA-xxxxx",
    base_url="https://api.sendafrica.online/v1",  # default
    timeout=10,                # seconds per request
    max_retries=3,             # retries on 429/5xx/connection errors
    environment="production",  # label for logging
    debug=False,               # True prints [sendafrica] logs to stdout
    webhook_secret=None,       # HMAC secret for webhook verification
)

#Sending SMS

send.py
python
result = client.sms.send(
    to="0712345678",
    message="Your OTP is 123456",
    sender="MyBrand",  # optional, max 11 chars
)

print(result.message_id)    # "SA-abc123..."
print(result.status)        # "sent"
print(result.credits_used)  # 1

# Preview cost without sending (zero network calls)
analysis = client.sms.analyze("Habari, how are you?")
print(analysis.encoding)   # "GSM-7"
print(analysis.parts)      # 1
print(analysis.credits)    # 1

#Bulk sending

bulk.py
python
results = client.sms.send_many([
    {"to": "0711111111", "message": "Hello John"},
    {"to": "0722222222", "message": "Hello Mary"},
    {"to": "+255733333333", "message": "Hello Alex"},
], sender="MyBrand")

print(results.sent_count)    # 2
print(results.failed_count)  # 1

for failure in results.failed:
    print(failure["index"], failure["to"], failure["error"])

rate_limit_per_sec paces requests client-side (default 10/sec) so large batches never trip the API rate limit.

#Credits & payments

credits.py
python
balance = client.credits.balance()
print(balance.account_id)  # "acc_abc123"
print(balance.balance)     # 4820

transactions = client.credits.history(page=1, per_page=50)
for tx in transactions:
    print(tx.id, tx.type, tx.amount, tx.balance_after, tx.created_at)

# Mobile money top-up
payment = client.payments.create(
    amount=50000,
    provider="snippe",
    phone="0712345678",
)
print(payment.id, payment.status, payment.credit_amount)

#Webhooks

webhooks.py
python
from fastapi import Request

@app.post("/webhooks/sendafrica")
async def webhook(request: Request):
    event = client.webhooks.parse(
        await request.body(),
        signature=request.headers.get("X-SendAfrica-Signature"),
    )

    if event.type == "sms.delivered":
        print(f"Message {event.message_id} delivered")

    # event.type: "sms.delivered", "sms.failed", ...
    # event.data: full raw event payload dict

#Error handling

All errors inherit from SendAfricaError. Every exception carries .message, .status_code, .request_id, and .response_body; RateLimitError adds .retry_after:

errors.py
python
from sendafrica.exceptions import (
    SendAfricaError,
    InsufficientCreditsError,
    RateLimitError,
    InvalidPhoneError,
)

try:
    client.sms.send(to="0712345678", message="Hello")
except InsufficientCreditsError:
    print("Not enough credits -- top up first")
except RateLimitError as e:
    print(f"Rate limited -- retry after {e.retry_after}s")
except InvalidPhoneError as e:
    print(f"Bad phone number: {e.message}")
except SendAfricaError as e:
    print(f"API error: {e} (status={e.status_code}, request_id={e.request_id})")
ExceptionHTTP
AuthenticationError401
ValidationError / InvalidPhoneError400 / 422
InsufficientCreditsError402
RateLimitError429
NotFoundError404
ServerError5xx
APIConnectionErrornetwork
WebhookSignatureErrorHMAC mismatch