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=, 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.3.0.tar.gz (20.5 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.3.0-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for sendara-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2958d67abbc02376fd0b7213468939e4d21a2913eba648591a54fc5e237da3c7
MD5 c3e514bb98befef9bf2c6773941b490e
BLAKE2b-256 d532fb8e6d1eeec0cb4c72df2403cc86e8a6d3478ad5f4a4eae6caf0743a1b3d

See more details on using hashes here.

File details

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

File metadata

  • Download URL: sendara-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 22.3 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fbae34fcd7f3dea3aa71720f0e46771055f1d340d1c6f305b48e0b07394cc289
MD5 bb5632c178562f6744106fb98e24c2cf
BLAKE2b-256 fd11e82dc54cb8f452136dbf246b76129fe65e6803f9c3a3f94ef2e1345d03da

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