Skip to main content

Sendara — email API (transactional + marketing email, broadcasts, contacts, templates, inbound, webhooks) for Python.

Project description

Sendara — Python SDK

The official Python client for the Sendara email API: transactional and marketing email, broadcasts, contacts and lists, templates, domains, inbound email, and webhooks — 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)

# Paginate every message (cursor handled for you)
for message in client.messages.iter(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.broadcasts.create(**params) / .list(limit=, offset=) / .get(id)
                 .send(id) / .cancel(id) / .delete(id)

client.messages.list(channel=, status=, search=, from_=, to=, limit=, cursor=)
client.messages.iter(channel=, status=, search=, from_=, to=, limit=)   # auto-paginating
client.messages.get(id) / .get(idempotency_key=)   # full event timeline

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.4.0.tar.gz (21.0 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.4.0-py3-none-any.whl (22.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for sendara-0.4.0.tar.gz
Algorithm Hash digest
SHA256 a7e0dac7400c812aa0dd90758f6778fdcc4cdf7da9a7e10fbe5c551c32479613
MD5 0549ad0c7fc419e777bb3e8c5728e5d8
BLAKE2b-256 ba2b2d2457b719b70c3eeb1d018d012561001f85956129b5c12a12fe8e2b136b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: sendara-0.4.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.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 805c030664e63a15ef9e0ab8be1cfe57d20eb503ba4883afb8baadb2906fc725
MD5 d64febc4040ec71b82184f6c56ebe76b
BLAKE2b-256 bf1d8ca742d7b2856b002d46deb90942232f61cc06096cd2b25fa57c89fb9c9e

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