mailtea (Python)
The official Python SDK for Mailtea — a thin, zero-dependency wrapper over the REST API. Python 3.9+.
Install
pip install mailtea
Usage
import os
from mailtea import Mailtea
mailtea = Mailtea(os.environ["MAILTEA_API_KEY"])
sent = mailtea.emails.send(
from_="you@yourdomain.com",
to="recipient@example.com",
subject="Hello from Mailtea",
html="<p>Your first email, sent with <strong>Mailtea</strong>.</p>",
)
print(sent.id)
email = mailtea.emails.get(sent.id)
print(email.status)
The API key can be passed to Mailtea(api_key) or read from MAILTEA_API_KEY.
Self-hosting or local dev? pass base_url= or set MAILTEA_API_BASE_URL.
Every method equally accepts a single wire-format dict — handy when you already hold the JSON payload:
sent = mailtea.emails.send({
"from": "you@yourdomain.com",
"to": "recipient@example.com",
"subject": "Hello from Mailtea",
"html": "<p>Your first email, sent with <strong>Mailtea</strong>.</p>",
})
print(sent["id"]) # responses support ["..."] and attribute access alike
Keyword arguments use snake_case wire names; a trailing underscore escapes
Python reserved words (from_= → "from").
API
| Method | Description |
|---|---|
emails.send(params) |
Send a transactional email → {"id": ...} |
emails.batch(emails) |
Send up to 100 emails → {"data": [{"id": ...}]} |
emails.get(id) |
Retrieve an email and its delivery status |
emails.list(params=None) |
List emails → {"data", "total", "limit", "offset", "has_more"}. Pass mode="test" for test-mode mail |
emails.update(id, params) |
Reschedule a scheduled email |
emails.reschedule(id, scheduled_at) |
Convenience wrapper over update |
emails.cancel(id) |
Cancel a scheduled email |
emails.analytics(params=None) |
Aggregate transactional metrics over an optional date window |
emails.inbound.list(params=None) |
List received emails in a publication (cursor-paginated) |
emails.inbound.get(id) |
Retrieve a received email with body, headers, and attachments |
emails.inbound.reply(id, params=None) |
Reply to a received email (threads by construction) |
emails.inbound.attachments.list(id) |
List a received email's attachments (signed download URLs) |
emails.inbound.attachments.get(id, attachment_id) |
Retrieve one inbound attachment |
contacts.create / upsert / list / get / update / delete |
Manage audience contacts (upsert = create; the endpoint upserts) |
posts.create(...) |
Create a newsletter post (draft, or send=True) → {"id": ...} |
posts.send(id, scheduled_at=None) |
Send a draft post to the audience, now or scheduled |
posts.send_test(id, params) |
Send a [TEST] copy of a post → {"sent_to", "failed_to"} |
posts.list(params) |
List posts (offset-paginated) → {"data", "total"} |
posts.get(id, params=None) |
Retrieve a post by id |
posts.update(id, params) |
Update a draft post (subject, html, text, from, reply_to, name) |
posts.delete(id, params=None) |
Delete a draft post |
segments.create / list / get / update / delete |
Manage audience segments |
topics.create / list / get / update / delete |
Manage topic definitions (visibility="public" → shown on the reader preference page) |
senders.create / list / get / update / delete |
Manage named From identities (email immutable) |
templates.create / list / get / update / publish / duplicate / delete |
Manage reusable email templates |
templates.render(params) |
Render a spec to HTML without saving → {"html", "text"} |
templates.versions(id, params=None) |
List a template's design history, newest first (metadata only) |
templates.restore_version(id, version, params=None) |
Put an older design back as unpublished changes; a published template keeps sending its published version until you publish again |
suppressions.list / add / remove |
Manage the team-wide do-not-send list |
suppressions.export() |
Export the whole suppression list as CSV (raw text) |
domains.create / list / get / verify / update / delete |
Manage sending domains (add, read DNS records, verify) |
domains.tracking.create / list / verify / delete |
Manage CNAME tracking sub-domains under a domain |
webhooks.create / list / get / update / delete |
Manage outbound event subscriptions |
contact_properties.create / list / update / delete |
Manage custom contact fields (team-scoped) |
api_keys.create / list / revoke |
Manage API keys (settings:write). create(mode="test") mints a test key |
automations.create / list / get / update / delete |
Manage automation graphs (steps + optional connections) |
automations.validate(params) |
Dry-run a graph → {"valid", "issues"} (no automation needed) |
automations.activate / pause / archive |
Lifecycle (cancel_runs defaults false on pause, true on archive) |
automations.versions(id, ...) / automations.version(id, version, ...) |
List stored versions; retrieve one with its graph |
automations.metrics(id, params=None) |
Per-step funnel counts and branch splits (test runs excluded) |
automations.test(id, params) |
One test run against a real contact — sends real, billed email |
automation_runs.list / get / cancel |
Inspect and cancel runs (a run pins the version it started on) |
events.send(params) |
Record a custom event → {"enrolled_automations", "resumed_runs"} |
events.list(params) |
List recorded events (cursor-paginated) |
event_definitions.create / list / get / update / delete |
Manage the event catalog (name immutable) |
Payloads follow the REST wire format (reply_to, scheduled_at, …), passed as
keyword arguments or a plain dict. Errors raise MailteaError with status,
details, and request_id.
emails.send also accepts tags, custom headers, attachments, and
scheduled_at. Attachments carry base64 content; set a content_id (plus
content_type) to embed an inline image referenced by cid: in the HTML:
mailtea.emails.send(
from_="you@yourdomain.com",
to="recipient@example.com",
subject="Your receipt",
html='<p>Thanks!</p><img src="cid:logo" />',
tags=[{"name": "category", "value": "receipt"}],
attachments=[
{"filename": "receipt.pdf", "content": pdf_base64},
{"filename": "logo.png", "content": logo_base64,
"content_type": "image/png", "content_id": "logo"}, # inline
],
)
Test mode
A test key (mt_test_…) sends nothing. Every message it creates is validated,
recorded and emits webhooks, but is never handed to a provider — so CI can point
at production Mailtea with your real code and your real webhook handler.
key = client.api_keys.create(name="CI", mode="test")
# key["token"] starts with mt_test_ and key["mode"] == "test"
test_client = Mailtea(key["token"])
test_client.emails.send(
from_="you@yourdomain.com",
to="bounced@test.mailtea.email",
subject="Bounce handling",
html="<p>Never delivered.</p>",
)
page = test_client.emails.list(mode="test")
Reserved recipients on test.mailtea.email force the outcome — delivered@,
bounced@, complained@, delayed@, failed@ — and the first to recipient
decides. Every email carries mode, and a test key reads only test mail while a
live key reads only live mail; there is no mixed view.
A test key is not a data sandbox. It reads and writes your real contacts, templates, senders and webhooks. Only delivery is simulated.
Verifying webhooks
Mailtea signs every outbound webhook with Standard Webhooks.
verify_webhook_signature checks the signature and rejects replays. Pass the
raw request body (not re-serialized JSON) and the endpoint's whsec_…
signing secret:
from mailtea import verify_webhook_signature
ok = verify_webhook_signature(
secret=signing_secret, # whsec_… from webhooks.create
msg_id=request.headers["webhook-id"],
timestamp=request.headers["webhook-timestamp"],
payload=raw_body, # exact bytes/string received
signature_header=request.headers["webhook-signature"],
)
if not ok:
return 401
sign_webhook(secret, msg_id, timestamp, payload) produces the same header, handy
for faking deliveries in tests. Both are stdlib-only.
Develop
cd sdks/python
python3 -m unittest discover -s tests -t .
Metadata
Release files for mailtea 0.18.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mailtea-0.18.0.tar.gz | 48.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mailtea-0.18.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.5 kB
Release files / mailtea-0.18.0.tar.gz
| Download URL | mailtea-0.18.0.tar.gz |
|---|---|
| Size | 48.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2b89653459ac220e462aaf6424da00d4882a1a66fe1243108171ba41b21df8c
|
|
BLAKE2b-256 checksum How to use checksums |
be60c0f1e5dccb25a530e7ec4755a4371bebc0d384ab167d217aa59cb2025bc2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / mailtea-0.18.0-py3-none-any.whl
| Download URL | mailtea-0.18.0-py3-none-any.whl |
|---|---|
| Size | 38.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
971ff45a2ad5852680b0dbcb0c9e6239b3bf3d2c4f1227b0950a716a7f007956
|
|
BLAKE2b-256 checksum How to use checksums |
b448a18b85311bbab59a3a39164fe1efdd4dd5bd6050b19fa58d8b30819eae4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logSecurity for mailtea 0.18.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for mailtea-0.18.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.