Skip to main content

Sendara — multi-channel messaging API (email, SMS, push, voice, webhooks) for Python.

Project description

Sendara — Python SDK

The official Python client for the Sendara messaging API: email, SMS, broadcasts, contacts, templates, webhooks, and more — with sync and async clients, typed models, a typed error hierarchy, automatic retries, and an auto-paginating message iterator.

pip install sendara

Requires Python 3.9+. Runtime dependency: httpx.

Quickstart

import os
from sendara import Sendara, SendaraError

client = Sendara(os.environ["SENDARA_API_KEY"])  # sk_live_... or sk_test_...

# Send an email
result = client.emails.send(
    from_="hello@acme.com",
    to="user@example.com",
    subject="Welcome to Acme",
    html="<h1>Welcome 🎉</h1>",
)
print(result.id, result.status)

# Send an SMS / OTP
client.sms.send(to="+254712345678", body="Your Acme code is 481920")

# Paginate every message (cursor handled for you)
for message in client.messages.iter(channel="email", limit=100):
    print(message.id, message.status)

Async

import asyncio
from sendara import AsyncSendara

async def main():
    async with AsyncSendara("sk_live_...") as client:
        await client.emails.send(to="user@example.com", subject="Hi", html="<p>Hi</p>")
        async for message in client.messages.iter(limit=100):
            print(message.id)

asyncio.run(main())

Both clients are context managers (with / async with) and accept base_url, timeout, max_retries, and an optional http_client.

client = Sendara(
    "sk_live_...",
    base_url="https://api.sendara.dev",
    timeout=30.0,
    max_retries=3,
)

Idempotency

Every send accepts an idempotency_key. The SDK generates one (UUIDv4) automatically when you omit it, so retries are always safe. Pass your own to deduplicate across processes:

client.emails.send(to="u@e.com", subject="s", text="t", idempotency_key="order-42")

Retries

Idempotent requests (all GET/PUT/DELETE and sends) are retried automatically on 429, 409, 5xx, and network/timeout errors, using exponential backoff with jitter. A Retry-After header is honored when present. Attempts are bounded by max_retries.

Errors

Every failure raises a subclass of SendaraError carrying status, code, message, and request_id:

HTTP Exception
400 / 422 ValidationError
401 AuthenticationError
403 PermissionError_
404 NotFoundError
409 ConflictError
429 RateLimitError (.retry_after)
5xx ServerError
network APIConnectionError / APITimeoutError
from sendara import RateLimitError, SendaraError

try:
    client.emails.send(to="u@e.com", subject="s", html="<p>x</p>")
except RateLimitError as e:
    print("retry after", e.retry_after)
except SendaraError as e:
    print(e.status, e.code, e.message)

Verifying webhooks

webhooks.verify recomputes HMAC-SHA256(secret, "<timestamp>.<raw_body>") (hex) and checks it in constant time against the Sendara-Signature header. Pass the raw request body, never a re-serialized object.

from sendara import webhooks

# Flask example
@app.post("/webhooks/sendara")
def hook():
    event = webhooks.verify(
        os.environ["SENDARA_WEBHOOK_SECRET"],
        request.get_data(),     # raw bytes
        request.headers,        # case-insensitive mapping
    )
    print(event["event_type"], event["message_id"])
    return "", 200

verify raises WebhookVerificationError on any mismatch (bad signature, missing headers, or a timestamp outside the tolerance window, default 300s).

Method reference

client.emails.send(to, subject, html=, text=, from_=, message_type=,
                   template_id=, template_vars=, idempotency_key=, metadata=, test_send=)
client.emails.send_raw(request)            # POST /v1/send (escape hatch)
client.emails.send_batch([req, ...])       # POST /v1/send/batch
client.emails.send_bulk(request)           # POST /v1/send/bulk
client.sms.send(to, body, sender_id=, message_type=, idempotency_key=)

client.broadcasts.create(**params) / .list(limit=, offset=) / .get(id)
                 .send(id) / .cancel(id) / .delete(id)

client.messages.list(channel=, status=, from_=, to=, limit=, cursor=)
client.messages.iter(channel=, status=, from_=, to=, limit=)   # auto-paginating
client.messages.get(id)

client.suppressions.list(channel=) / .create(channel, recipient, reason=)
                    .delete(channel, recipient)

client.domains.list() / .create(domain) / .get(domain) / .verify(domain)

client.api_keys.list() / .create(scope=, test_mode=) / .rotate(id) / .revoke(id)

client.usage.get(period=)
client.usage.set_spend_cap(key_id=, soft_limit_micros=, hard_limit_micros=)

client.billing.get() / .checkout(plan=) / .portal()

client.templates.create(**params) / .list() / .get(id) / .update(id, **params)
                .delete(id) / .render(id, vars=)

client.contacts.create(**params) / .list(limit=, offset=) / .get(id)
               .update(id, **params) / .delete(id) / .import_(s3_key=, format=)
client.lists.create(name, list_type=, segment_rules=) / .list() / .get(id)
            .update(id, **params) / .delete(id)
            .add_member(id, contact_id) / .remove_member(id, contact_id) / .members(id)

client.webhooks.create(endpoint_url, event_types=) / .list() / .get(id)
               .update(id, **params) / .delete(id)
               .deliveries(id, limit=) / .rotate_secret(id)

client.uploads.create(file, filename=, content_type=)   # multipart image

client.test_recipients.list() / .create(email) / .resend(id) / .delete(id)

webhooks.verify(secret, payload, headers=, signature=, timestamp=, tolerance=300)

The async client (AsyncSendara) exposes every method above with await; messages.iter becomes an async for.

Development

cd sdk/python
pip install -e '.[dev]'
python -m pytest

MIT licensed.

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

sendara-0.2.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

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

sendara-0.2.0-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

Details for the file sendara-0.2.0.tar.gz.

File metadata

  • Download URL: sendara-0.2.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.13

File hashes

Hashes for sendara-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f3c6ea6f9c54e6057251336229b69d597ca54dcf02164bad006fe7386d421f93
MD5 0689790561ccb984feac89a44f583f4a
BLAKE2b-256 d6c0c2f4df879d7c6f66ac8c59bc1ba6c3c39b710908f3091ef3585daa8d087d

See more details on using hashes here.

File details

Details for the file sendara-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: sendara-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 22.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.13

File hashes

Hashes for sendara-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5f93f966ed0e5aec11b0fe2432633b7f4774a3fcdc322b5529687a93f19dcbad
MD5 7de2c3f4f0e8f3384b5005e81f6a5291
BLAKE2b-256 be1d5dbb7f7c1f9d4653a04e52c59cbf79c26efd12e3892f952f7faa9064f772

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