AxeneAxene Docs
SDKs

Python SDK

Install and use the official axene-mailer Python client to send email, manage domains, contacts, suppressions, templates, and webhooks.

The official Python client for Axene Mailer wraps the Emails API and every other Core resource in a small, typed surface. It is published as axene-mailer on PyPI, and the source lives in the axene-sdks repository.

Installation

pip install axene-mailer

The package has zero runtime dependencies (it uses only the Python standard library) and supports Python 3.8 and above.

Client setup

Create a client with your API key. Keys start with axm_k_ and are passed as a bearer token on every request.

from axene_mailer import Axene
 
axene = Axene(api_key="axm_k_your_api_key")

The constructor accepts the following options:

OptionTypeDefaultDescription
api_keystrrequiredYour Axene Mailer API key. Must start with axm_k_.
base_urlstrhttps://mail.axene.ioOverride the API host (for staging or self-hosted setups).
max_retriesint3Maximum attempts per request. Retries apply to 429 and 5xx responses.
timeoutfloat30.0Per-request timeout in seconds.
axene = Axene(
    api_key="axm_k_your_api_key",
    base_url="https://mail.axene.io",
    max_retries=5,
    timeout=60.0,
)

Keep your API key on the server. Never embed it in client-side code or commit it to source control. Load it from an environment variable instead.

Send an email

Pass a message dictionary to axene.emails.send. The from, to, cc, and bcc fields accept a bare email string, a {"email", "name"} dict, or a list of either.

from axene_mailer import Axene
 
axene = Axene(api_key="axm_k_your_api_key")
 
result = axene.emails.send({
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Your receipt",
    "html": "<p>Thanks for your order.</p>",
})
 
print(result["id"], result["status"])

You expose a clean from key. The SDK maps it to the API's wire field from_ for you, so you never write from_ yourself.

A richer send can set names, multiple recipients, a reply-to, tags, and a schedule. The send_at field accepts a datetime or an ISO 8601 string.

from datetime import datetime, timedelta
 
result = axene.emails.send({
    "from": {"email": "[email protected]", "name": "Your Company"},
    "to": [
        {"email": "[email protected]", "name": "Jane Doe"},
        "[email protected]",
    ],
    "cc": "[email protected]",
    "subject": "Welcome aboard",
    "html": "<h1>Welcome!</h1>",
    "text": "Welcome!",
    "reply_to": "[email protected]",
    "headers": {"X-Campaign": "onboarding"},
    "tags": ["onboarding"],
    "send_at": datetime.utcnow() + timedelta(hours=1),
})

The send returns a result dict shaped like this:

{
    "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
    "status": "queued",
    "message_id": "<[email protected]>",
    "rejection_reason": None,
}

Emails

The axene.emails resource covers sending, lookups, search, scheduling, and inspection.

# Send a batch (one dict per email). Starter plan and above.
axene.emails.send_batch([
    {"from": "[email protected]", "to": "[email protected]", "subject": "Hi", "html": "<p>1</p>"},
    {"from": "[email protected]", "to": "[email protected]", "subject": "Hi", "html": "<p>2</p>"},
])
 
# Dry-run a send without delivering it.
check = axene.emails.validate({
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Test",
    "html": "<p>Test</p>",
})
print(check["valid"], check["can_send"])
 
# List recent emails (zero-based paging).
emails = axene.emails.list(status="delivered", page=0, limit=20)
 
# Fetch one email with its bodies and events.
email = axene.emails.get("a1b2c3d4-5678-90ab-cdef-1234567890ab")
 
# Event timeline for one email.
events = axene.emails.events("a1b2c3d4-5678-90ab-cdef-1234567890ab")
 
# Re-send a bounced, rejected, or failed email.
axene.emails.retry("a1b2c3d4-5678-90ab-cdef-1234567890ab")
 
# Search with inline tokens (to:, from:, status:, domain:, tag:).
hits = axene.emails.search(q="welcome status:delivered")
 
# Schedule management.
scheduled = axene.emails.list_scheduled()
axene.emails.cancel_scheduled("b2c3d4e5-6789-01bc-defg-2345678901bc")
axene.emails.send_scheduled_now("b2c3d4e5-6789-01bc-defg-2345678901bc")
 
# Poll for status changes since a timestamp (datetime or ISO string).
updated = axene.emails.updates("2026-04-06T10:00:00Z")
 
# Saved searches.
saved = axene.emails.get_saved_searches()
axene.emails.set_saved_searches(saved)

Domains

The axene.domains resource registers, verifies, inspects, and transfers sending domains.

# Register a domain. The response includes the DNS records to publish.
domain = axene.domains.create("yourdomain.com")
 
# List and fetch.
domains = axene.domains.list()
domain = axene.domains.get(domain["id"])
 
# Re-check DNS and verify.
axene.domains.verify(domain["id"])
 
# Live DNS checks and diagnostics.
health = axene.domains.health(domain["id"])
diagnosis = axene.domains.diagnose(domain["id"])
mx = axene.domains.mx_status(domain["id"])
records = axene.domains.published_records(domain["id"])
 
# Rotate the DKIM key.
axene.domains.rotate_dkim(domain["id"])
 
# Transfer to another Axene account.
axene.domains.transfer(domain["id"], target_email="[email protected]", note="handover")
 
# Availability checks.
axene.domains.check_availability("newdomain.com")
axene.domains.check("yourdomain.com")
 
# Delete.
axene.domains.delete(domain["id"])

Contacts

The axene.contacts resource manages subscriber lists, their contacts, CSV imports, and templated bulk sends.

# Lists.
lists = axene.contacts.list_lists()
contact_list = axene.contacts.create_list(name="Newsletter", description="Monthly updates")
detail = axene.contacts.get_list(contact_list["id"], page=0, limit=50)
axene.contacts.update_list(contact_list["id"], name="Monthly Newsletter")
axene.contacts.delete_list(contact_list["id"])
 
# Contacts.
contact = axene.contacts.add_contact(
    contact_list["id"],
    email="[email protected]",
    name="Sam",
    metadata={"plan": "pro"},
)
axene.contacts.remove_contact(contact_list["id"], contact["id"])
 
# Import from a CSV file (multipart upload; email column auto-detected).
with open("contacts.csv", "rb") as f:
    axene.contacts.upload_csv(contact_list["id"], f.read(), filename="contacts.csv")
 
# Send a templated email to every contact in the list.
# Subject/html/text may use {{email}}, {{name}}, and {{metadata_key}} placeholders.
axene.contacts.bulk_send(
    contact_list["id"],
    sender_address_id="sender-uuid",
    subject="Hello {{name}}",
    html="<p>Hi {{name}}</p>",
)

Suppressions

The axene.suppressions resource manages the do-not-send list. The list returns a paginated envelope of {items, total, page, limit}.

# Paginated list with optional search.
page = axene.suppressions.list(page=0, limit=50, search="example.com")
for item in page["items"]:
    print(item)
 
# Suppress a single address.
axene.suppressions.add(email="[email protected]", reason="manual")
 
# Bulk import from a file (one email per line).
with open("suppressions.txt", "rb") as f:
    axene.suppressions.bulk_upload(f.read(), filename="suppressions.txt")
 
# Remove a suppression.
axene.suppressions.remove("suppression-uuid")

Templates

The axene.templates resource manages reusable email templates. Available on the Starter plan and above.

templates = axene.templates.list()
 
template = axene.templates.create(
    name="Receipt",
    subject="Your receipt",
    html="<p>Thanks, {{name}}.</p>",
)
 
axene.templates.get(template["id"])
axene.templates.update(template["id"], subject="Your updated receipt")
axene.templates.duplicate(template["id"])
axene.templates.delete(template["id"])

The html and text arguments map to the wire fields html_body and text_body. Template variables are derived server-side from {{name}} placeholders, so you do not pass them.

Webhooks

The axene.webhooks resource manages event subscriptions and inspects deliveries.

# Create a webhook. The signing secret is generated and returned.
hook = axene.webhooks.create(
    url="https://your-app.example.com/hooks/axene",
    events=["email.delivered", "email.bounced"],
)
 
# List and update.
hooks = axene.webhooks.list()
axene.webhooks.update(hook["id"], is_active=False)
 
# Send a sample email.delivered delivery to test the endpoint.
axene.webhooks.test(hook["id"])
 
# Inspect delivery attempts (paginated envelope).
deliveries = axene.webhooks.list_deliveries(hook["id"], page=0, limit=20)
delivery = axene.webhooks.get_delivery(hook["id"], "delivery-uuid")
 
# Delete.
axene.webhooks.delete(hook["id"])

Error handling

Any non-2xx response, or a transport failure that survives every retry, raises AxeneError. Inspect its attributes to branch on specific failures.

AttributeTypeDescription
statusintHTTP status code. 0 indicates a transport or network failure.
codestr or NoneMachine-readable error code from the API body, when present.
messagestrHuman-readable message (the exception's string value).
detailanyThe raw parsed response body, for debugging.
from axene_mailer import Axene, AxeneError
 
axene = Axene(api_key="axm_k_your_api_key")
 
try:
    axene.emails.send({
        "from": "[email protected]",
        "to": "[email protected]",
        "subject": "Your receipt",
        "html": "<p>Thanks for your order.</p>",
    })
except AxeneError as e:
    print(f"status={e.status} code={e.code} message={e}")
    if e.status == 422:
        # Sender not registered, or domain not verified.
        ...

Configuration

The client tunes its network behaviour through three constructor options.

  • Retries. Requests that return 429 (rate limited) or any 5xx are retried automatically, up to max_retries attempts (default 3). Backoff is exponential, and a Retry-After header is honoured when present. Uploads are not retried, because they are not idempotent.
  • Timeout. Each request is bounded by timeout seconds (default 30.0). A timeout that exhausts all retries raises AxeneError with status set to 0.
  • Base URL. Point base_url at a staging or self-hosted host. It defaults to https://mail.axene.io and trailing slashes are trimmed.
axene = Axene(
    api_key="axm_k_your_api_key",
    base_url="https://staging-mail.axene.io",
    max_retries=5,
    timeout=45.0,
)

Next steps

  • SDK overview - compare the Python client with the SDKs for other languages.
  • Emails API reference - the underlying REST endpoints, request bodies, and response shapes.

On this page