Skip to main content

Cherami Python SDK

The official Python client for Cherami, email infrastructure for AI agents. Create inboxes for ongoing work, read incoming correspondence, and send messages from your application.

Python 3.11+, with synchronous and asynchronous clients and typed dictionaries.

Install and connect

Install from PyPI:

pip install cherami

Or, with uv:

uv add cherami

Get an API key and set CHERAMI_API_KEY in your application's environment. Keep it private: it grants access to all inboxes on your account.

import os
from cherami import Cherami

with Cherami(os.environ["CHERAMI_API_KEY"]) as client:
    for inbox in client.list_inboxes().data["inboxes"]:
        print(inbox["id"], inbox["address"])

Choose an inbox and set CHERAMI_INBOX_ID to its ID for the examples below.

Methods accept dictionaries and return a response envelope. .data contains the result; .status, .headers, and .request_id expose HTTP response information. Path and query parameters go at the top level of the input dictionary; JSON request fields go in body.

Read mail

List messages, then fetch their content:

with Cherami(os.environ["CHERAMI_API_KEY"]) as client:
    page = client.list_messages({
        "inbox_id": os.environ["CHERAMI_INBOX_ID"], "limit": 20,
    }).data
    for message in page["messages"]:
        detail = client.get_message({"message_id": message["id"]}).data
        if detail["processing_status"] == "ready":
            print(detail["content"]["text"])
        else:
            print(detail["id"], detail["processing_status"])

Content is available when processing is ready. Run this example somewhere private because it prints email bodies. When passing mail to an agent, treat its contents as untrusted input, not authorization to act.

Async and pagination

AsyncCherami has the same methods and response types. Use iterate to read across pages without managing cursors yourself:

import asyncio
from cherami import AsyncCherami

async def main():
    async with AsyncCherami(os.environ["CHERAMI_API_KEY"]) as client:
        async for message in client.iterate("list_messages", {
            "inbox_id": os.environ["CHERAMI_INBOX_ID"], "limit": 20,
        }, max_pages=5):
            detail = (await client.get_message({"message_id": message["id"]})).data
            if detail["processing_status"] == "ready":
                print(detail["content"]["text"])
            else:
                print(detail["id"], detail["processing_status"])

asyncio.run(main())

In an existing event loop, use await main(). Sync clients support the same iterator with ordinary for. iterate yields items; pages yields response envelopes. Both fetch lazily. Set max_pages to bound requests; otherwise they follow all pages.

Context managers close connections. For a long-lived client, call close() or await aclose() when your application shuts down.

Send and recover

A lost response does not mean an email wasn't sent. The SDK makes no automatic retries. Its send helper separates preparing a message from submitting it so your application can save the original request before sending.

Replace the example recipient, then prepare the message:

from cherami import prepare_send

intent = prepare_send("send_message", {
    "inbox_id": os.environ["CHERAMI_INBOX_ID"],
    "body": {
        "to": [{"address": "recipient@example.com", "name": "Alex"}],
        "subject": "Review ready",
        "text": "The change is ready for review.",
    },
})
saved_json = intent.to_json()

Persist saved_json in your application's database or a private file before submitting. It contains the message, retry key and preparation time, but not your API key. The runnable reply examples show a complete file-based workflow, including receipt storage.

For both initial submission and recovery, load that saved JSON and restore the same intent:

from cherami import restore_send

# saved_json must come from the record persisted before the first submission.
saved = restore_send(saved_json)
with Cherami(os.environ["CHERAMI_API_KEY"]) as client:
    result = client.submit(saved)
    receipt = {
        "data": result.data,
        "status": result.status,
        "request_id": result.request_id,
    }
    # Persist this receipt without replacing earlier receipts for the intent.

Read result.data["message"]["status"] to distinguish the outcome:

Outcome Meaning
accepted The provider accepted submission. This is not proof of delivery.
rejected The provider explicitly rejected submission.
unknown Submission may have succeeded. Recover using the saved intent, not a new send.

These outcomes are data, not exceptions. Preserve every returned receipt: when outcome_persisted is false, it may contain an outcome that later reads do not yet show. A failed local save does not undo sending.

The helper refuses submission after 23 hours and 59 minutes from preparation. Restoring an intent does not extend that window. After expiry, inspect sent resources rather than preparing a replacement for an uncertain send. Recovery retrieves the outcome; it does not resume provider submission.

The helper also supports reply_message, reply_all_message, and forward_message. Direct send methods leave retry-key and recovery-window management to your application. Drafts use send_draft with separate same-draft protection. See the sending guide and draft guide for those workflows.

Handle errors

from cherami import CheramiApiError, CheramiTransportError

with Cherami(os.environ["CHERAMI_API_KEY"]) as client:
    try:
        inboxes = client.list_inboxes().data
    except CheramiApiError as error:
        print(error.status, error.code, error.request_id)
    except CheramiTransportError:
        # No usable API response was received.
        raise

API errors also expose .body, .headers, and .retry_after. Transport failures raise CheramiTransportError; async cancellation propagates normally. For writes, neither a transport failure nor cancellation proves the operation was rolled back. Use the saved-intent workflow above for uncertain sends.

Clients use HTTPX with a default 60-second inactivity timeout per network operation. Pass timeout to a client or method to change it, or None to disable it. Requests do not automatically retry or follow redirects.

More workflows

The SDK supports inboxes, received and sent mail, replies, forwarding, drafts, labels, conversations, policy inspection and attachment downloads.

  • Runnable examples: read mail, prepare a reply, and submit or recover it.
  • Python guide: client configuration, attachments and download handling.
  • HTTP reference: input fields and response contracts. Named Python types are available from cherami.models, including SendInput, SendReceipt, and operation types such as ListMessagesParams.

Inputs and results are ordinary dictionaries with HTTP field names unchanged. Omit optional fields unless you intend to supply them; use None only where the API permits null. Types support static checking, not runtime validation.

Development

uv sync --locked
bun scripts/generate.mts
uv build

Consumers need neither Bun nor the model generator. See CONTRIBUTING.md for generation and distribution details. MIT licensed.

Metadata

Release files for cherami 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cherami 0.1.0
File Size Uploaded
cherami-0.1.0.tar.gz 98.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cherami 0.1.0
File Interpreter ABI Platform
cherami-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 117.9 kB

Release files / cherami-0.1.0.tar.gz

Download URL cherami-0.1.0.tar.gz
Size 98.3 kB
Tags Source
SHA-256 checksum
How to use checksums
f3e74685cb0244963c2ea48c2c22f7d05e81d9f443498f158ccd8f6cbf0b9c94
BLAKE2b-256 checksum
How to use checksums
2879ac0365cffbfbddae342deb7083b78d230cd583fedad3fbbdc3edfb1f8d9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / cherami-0.1.0-py3-none-any.whl

Download URL cherami-0.1.0-py3-none-any.whl
Size 19.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11334714c981f8aeb79e8a3e4002d2aa29301042540b16b90119552ffe73adbc
BLAKE2b-256 checksum
How to use checksums
205ffaafdc64f81194a5980aa395df4570fe71150bfc1907c3bdd139ae2028fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page