Skip to main content

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, with its From and Reply-To, 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.20.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 mailtea 0.20.0
File Size Uploaded Provenance
mailtea-0.20.0.tar.gz 50.5 kB With provenance Details

Built distribution (wheel)

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

Total release size: 89.8 kB

Release files / mailtea-0.20.0.tar.gz

Download URL mailtea-0.20.0.tar.gz
Size 50.5 kB
Tags Source
SHA-256 checksum
How to use checksums
84802ee37f4f1a741e995ba9b1c118fae4faae3c053446871dfff4277df11739
BLAKE2b-256 checksum
How to use checksums
ea253457c27ae989086437a01ce614d03f12ebf115e66ab4968ac3cc3fc6df70
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 Oct 1, 2026.

Transparency log

Release files / mailtea-0.20.0-py3-none-any.whl

Download URL mailtea-0.20.0-py3-none-any.whl
Size 39.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
337313b02c8d635302708ab69b1fe03db46d943736a9bf69256c4484f83b3918
BLAKE2b-256 checksum
How to use checksums
b0fd84bef229992fee7fb99353151d547bc73192d330ac92bd72dc729e874b77
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 Oct 1, 2026.

Transparency log

Security for mailtea 0.20.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.

Select from 2 files

Attestation for mailtea-0.20.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 Oct 1, 2026.

Transparency log

A 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.

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