Skip to main content

postern-client (Python)

PyPI version

A dependency-light Python client + CLI for the Postern mailbox API (the token-gated /api/* surface served by the inbound/store worker). Built so crew agents and humans can hit the API without rebuilding tooling every session.

Stack map: docs/architecture.md.

flowchart LR
    script[Python script / CLI] -->|HTTPS Bearer| api[Postern Mailbox API]
  • Zero runtime dependencies. Pure stdlib (urllib). No build step.
  • Importable client (PosternClient) and a CLI (postern).
  • Per-user own key. The API origin and token come from the environment (POSTERN_API_URL / POSTERN_API_TOKEN); nothing is hardcoded, the token is never logged, and the CLI never accepts the token as an argument (so it cannot leak into shell history, ps, or argv).

Exposure posture: point POSTERN_API_URL at a loopback/mesh origin. The project does not expose the API publicly; this client does not change that.

Install

PyPI (postern-client; GitHub Release v* matching pyproject.toml triggers CI publish):

pip install postern-client
postern --help

From source (development):

cd clients/python
python -m venv .venv && . .venv/bin/activate
pip install -e .            # no runtime deps; pip install -e '.[dev]' adds mypy

This installs the postern command. Without installing you can run it as a module: python -m postern_client ....

Configure (bring your own key)

export POSTERN_API_URL=https://<the-postern-api-origin>
export POSTERN_API_TOKEN=<your-postern-api-token>
# verify the var is set WITHOUT echoing it:
echo "POSTERN_API_TOKEN is ${POSTERN_API_TOKEN:+SET}"

See .env.example. The token is a real credential: keep it out of tracked files and shell history.

CLI usage

postern ping                                   # validate your token

# send a message
postern send --to alice@example.com --subject "Hello" --text "hi there"
postern send --to a@x.com --to b@x.com --subject "Report" \
  --html "<p>see attached thinking</p>" --header X-Tag=ops
postern send --to a@x.com --subject "Long note" --text-file ./body.txt   # or - for stdin

# reply to a stored message (threads automatically)
postern reply <message-id> --text "thanks, got it"

# list with filters + pagination
postern list --direction inbound --limit 20
postern list --from alice@example.com --cursor "<cursor-from-previous-page>"

# read one message / a whole thread
postern get <message-id>
postern thread <thread-id>

# search (mode: fts | semantic | hybrid)
postern search "invoice overdue" --mode hybrid --limit 10

# download the i-th attachment of a message
postern attachment <message-id> 0 -o ./invoice.pdf

All commands print the API's JSON to stdout (so you can pipe into jq); the attachment command writes bytes to a file and prints a one-line summary to stderr. Exit codes: 0 ok, 1 error, 2 auth failure (bad/missing token).

Override the origin (not the token) per invocation with --api-url:

postern --api-url http://127.0.0.1:8787 ping

Library usage

from postern_client import from_env, PosternClient, PosternError

# build from POSTERN_API_URL / POSTERN_API_TOKEN
client = from_env()

# ...or construct explicitly (e.g. a token you loaded from your own secret store)
client = PosternClient("https://postern.example", token)

res = client.send("alice@example.com", "Hello", text="hi there")
print(res["messageId"], res["threadId"])

page = client.list_messages(direction="inbound", limit=20)
for summary in page["items"]:
    print(summary["messageId"], summary.get("subject"))
if page["cursor"]:
    nxt = client.list_messages(direction="inbound", limit=20, cursor=page["cursor"])

msg = client.get_message("<message-id>")           # dict, or None if absent
thread = client.get_thread("<thread-id>")          # list[dict]
hits = client.search("invoice", mode="hybrid")     # {"items": [...], "cursor": ...}

att = client.get_attachment("<message-id>", 0)     # Attachment(body, mime, filename)
with open(att.filename, "wb") as fh:
    fh.write(att.body)

Errors raise PosternError (with .status and the API .code, e.g. E_FIELD_MISSING); a bad token raises PosternAuthError. Methods return the API's parsed JSON, so the keys match the worker contract exactly.

API surface

method endpoint returns
send POST /api/send {messageId, threadId, ...}
reply POST /api/reply {messageId, threadId, ...}
list_messages GET /api/messages {items: [summary], cursor}
get_message GET /api/messages/{id} message dict or None
get_thread GET /api/threads/{id} [message]
search GET /api/search {items: [{message, ...}], cursor}
get_attachment GET /api/messages/{id}/attachments/{i} Attachment(body, mime, filename)
ping GET /api/messages?limit=1 bool

Tests

cd clients/python
python -m unittest discover -s postern_client/tests   # no network (injected transport)
python -m mypy                                         # the type gate (house style)

The transport is injectable, so the suite runs entirely offline; the API is faked, no token or origin is needed to test.

License

MIT (see LICENSE). The Postern server core is AGPL-3.0-only; this client is MIT to maximize reuse, matching the other Postern client integrations.

Download files

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

Source Distribution

postern_client-1.0.6.tar.gz (13.2 kB view details)

Uploaded Source

Built Distribution

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

postern_client-1.0.6-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file postern_client-1.0.6.tar.gz.

File metadata

  • Download URL: postern_client-1.0.6.tar.gz
  • Upload date:
  • Size: 13.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for postern_client-1.0.6.tar.gz
Algorithm Hash digest
SHA256 ad2791a0dcd944820a4ab8219593241240c6d285939bf92d8bbce51e403d61b5
MD5 187f444cb534b99f06419d8165a60310
BLAKE2b-256 a7f787b57d94ff5ce6daa46485739ec74b8a367100965365149fe9c5df831503

See more details on using hashes here.

File details

Details for the file postern_client-1.0.6-py3-none-any.whl.

File metadata

  • Download URL: postern_client-1.0.6-py3-none-any.whl
  • Upload date:
  • Size: 12.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for postern_client-1.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 6f5df91702d42e4f95c44474651f9f303bf38d8c0a5a496ad0f97daf93755823
MD5 571e5a0f2908ef28e51b54a75db0ab85
BLAKE2b-256 aea4b22feaf4dcc59660a55a33932e980c0c9a294e5e824e91915c6c13008978

See more details on using hashes here.

Release history Release notifications | RSS feed

1.4.5

2 files

1.4.4

2 files

1.4.3

2 files

1.4.2

2 files

1.4.1

2 files

1.4.0

2 files

1.3.9

2 files

1.3.8

2 files

1.3.7

2 files

1.3.6

2 files

1.3.5

2 files

1.3.4

2 files

1.3.3

2 files

1.3.2

2 files

1.3.1

2 files

1.3.0

2 files

1.2.1

2 files

1.2.0

2 files

1.1.0

2 files

This release

1.0.6 This release

2 files

1.0.4

2 files

1.0.3

2 files

0.1.0

2 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