Skip to main content

Official Python SDK for the MojaWave API — SMS and transactional messaging for Tanzania.

Project description

MojaWave Python SDK

A thin, typed Python client for the MojaWave REST API — send SMS (single, bulk, OTP), check credit balances, and verify webhooks across Tanzania's telco networks (Vodacom, Tigo, Airtel, Halotel).

pip install mojawave

Requires Python 3.8+.

Quickstart

from mojawave import MojaWave

client = MojaWave(api_key="sk_live_mw_...")  # or set MOJAWAVE_API_KEY

msg = client.sms.send(
    to="+255753276939",
    sender="MojaWave",
    message="Hello from Mojawave! Your verification code is 1234.",
)
print(msg.id, msg.status)

The client reads MOJAWAVE_API_KEY from the environment when api_key is omitted. Use an sk_test_mw_ key for the sandbox — no real messages are sent and no charges apply.

Never expose your live API key in client-side code. Use environment variables and server-side requests only.

Sending SMS

Single message

msg = client.sms.send(
    to="+255712345678",
    sender="MojaWave",                 # sender ID (≤11 alphanumeric chars); defaults to MojaWave
    message="Your code is 1234.",
    webhook_url="https://example.com/webhooks/sms",  # optional delivery receipts
    schedule_at="2026-07-01T09:00:00Z",              # optional ISO-8601 schedule
    metadata={"customer_id": "cust98765"},           # optional, echoed back
    tags=["onboarding", "verification"],             # optional
)

Look up a message

msg = client.sms.get("89b82624-f1a2-4f5e-85b5-102e79a06779")
if msg.delivered:
    print("Delivered at", msg.timeline.delivered_at)
elif msg.failed:
    print("Failed:", msg.failure_reason)

Bulk send (up to 10,000 recipients)

Bulk jobs run asynchronously — you get a job back immediately, then poll it.

job = client.sms.bulk(
    name="Marketing Campaign Q1",
    sender="MojaWave",
    message="Hello {name}, your code is {code}",
    recipients=[
        {"to": "+255712345678", "personalization": {"name": "John", "code": "ABC123"}},
        {"to": "+255712345679", "personalization": {"name": "Jane", "code": "XYZ789"}},
        "+255712345680",  # a bare string works too (no personalization)
    ],
    webhook_url="https://example.com/webhooks",
)
print(job.id, job.status, job.total_recipients)

# Poll for progress
job = client.sms.get_bulk(job.id)
print(f"{job.progress_percent}% — {job.sent_count} sent")

Unicode messages have a 70-character per-segment limit (vs. 160 for plain SMS). Plan message length accordingly.

Credits

balances = client.credits.balance()
print(balances.sms.balance, balances.sms.is_low_balance)
print(balances.email.balance)

Webhooks

MojaWave signs every webhook with an X-MojaWave-Signature header (HMAC-SHA256 of the raw body). Always verify against the raw request bytes — parsing to JSON first can change whitespace and break the check.

from mojawave import construct_event, WebhookVerificationError, SIGNATURE_HEADER

# Flask / Django view
signature = request.headers.get(SIGNATURE_HEADER)
try:
    event = construct_event(request.get_data(), signature, WEBHOOK_SECRET)
except WebhookVerificationError:
    return "Forbidden", 403

if event.type == "message.delivered":
    ...

Event types: message.sent, message.delivered, message.failed, credits.low. See examples/flask_webhook.py for a full handler.

If you only need a boolean, use verify_signature(payload, signature, secret).

Error handling

Every documented HTTP status maps to a typed exception. All inherit from MojaWaveError.

Exception HTTP Code
InvalidRequestError 400 invalid_request
AuthenticationError 401 unauthorized
InsufficientBalanceError 402 insufficient_balance
UnprocessableError 422 unprocessable
RateLimitError 429 rate_limit_exceeded
ServerError 5xx server_error
APIConnectionError / APITimeoutError transport failures
from mojawave import InsufficientBalanceError, RateLimitError

try:
    client.sms.send(to="+255712345678", message="hi")
except InsufficientBalanceError:
    ...  # top up
except RateLimitError as e:
    time.sleep(e.retry_after or 1)

The client automatically retries 429 and 5xx responses with exponential backoff (honouring Retry-After), controlled by max_retries (default 2).

Configuration

client = MojaWave(
    api_key="sk_live_mw_...",
    environment="live",        # or "sandbox"
    timeout=30.0,              # seconds
    max_retries=2,
    base_url="https://api.mojawave.com/v1",
)

Rate-limit headers from the most recent response are available on client.rate_limit (.limit, .remaining, .reset). The client is usable as a context manager to ensure the HTTP session is closed:

with MojaWave() as client:
    client.sms.send(to="+255712345678", message="hi")

Development

pip install -e ".[dev]"
pytest
mypy

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mojawave-1.0.0.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mojawave-1.0.0-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

Details for the file mojawave-1.0.0.tar.gz.

File metadata

  • Download URL: mojawave-1.0.0.tar.gz
  • Upload date:
  • Size: 14.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for mojawave-1.0.0.tar.gz
Algorithm Hash digest
SHA256 83149871ebeadd180072010f0de698f795511424a267983dadcc85b277896cfa
MD5 b93e6f63d83e6caf628cc1460a95117f
BLAKE2b-256 26776a28f0a843ec226a999caefac470fb6759743c23e2bc0f539ad83a5780e3

See more details on using hashes here.

File details

Details for the file mojawave-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: mojawave-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 16.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for mojawave-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b8402b4e807a9f8dfe6a5cd513ac052d11ee63a1525c5793a73ef728cead7c1e
MD5 b130b3eb88ca5b2fc0ade298cd551fcb
BLAKE2b-256 04c5bcc30f9b4af3c48586789174286638363063b3c4d59b6318c34d27ff9d9c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page