Send email from Python

Five steps: install the SDK, create a test key, send to the sandbox, read the log, then verify a domain so you can send to anyone.

1. Install

The Python SDK is on PyPI as avelto. Python 3.9 or newer.

shell
pip install avelto

2. Create an API key

Sign in, open API keys in the dashboard and create a test key. It starts with av_test_. Export it so the samples can read it:

shell
export AVELTO_API_KEY=av_test_...

3. Send your first email

from_ is the sender, because from is a Python keyword. A rejected send raises AveltoError with the HTTP status, the API code and the message.

Python
# send.pyimport osfrom avelto import Avelto, AveltoErroravelto = Avelto(os.environ["AVELTO_API_KEY"])try:    email = avelto.emails.send(        from_="you@sandbox.avelto.dev",        to="delivered@sandbox.avelto.dev",        subject="Hello from Avelto",        text="It works.",    )except AveltoError as err:    raise SystemExit(f"{err.status} {err.code}: {err.message}")print(email["id"])  # "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10"
shell
python send.py

The API answers 201 Created with the email id, which the SDK returns as a dict:

HTTP
HTTP/1.1 201 CreatedContent-Type: application/json{ "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" }

4. Check the log

Fetch the email by id. status moves from queued to sent to delivered, and events records each step: email.queued, email.sent, email.delivered.

Python
email = avelto.emails.get(email_id)print(email["status"])  # "queued", then "sent", then "delivered"for event in email["events"]:    print(event["type"], event["occurred_at"])
JSON
{  "id": "9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10",  "mode": "test",  "from": "you@sandbox.avelto.dev",  "to": ["delivered@sandbox.avelto.dev"],  "subject": "Hello from Avelto",  "status": "delivered",  "events": [    {      "id": "e1f0c3a4-8b2d-4c6e-9a1f-5d7b3e2c8a90",      "type": "email.queued",      "payload": {},      "occurred_at": "2026-09-17T10:12:04.000Z"    },    {      "id": "a7c2e9d1-3f4b-4a8e-b6c0-2d9e1f7b5c34",      "type": "email.sent",      "payload": { "test": true, "ses_message_id": "test-9c1f4a52-6f6e-4b8f-9b8e-2e1a5c7d3f10" },      "occurred_at": "2026-09-17T10:12:06.000Z"    },    {      "id": "c4b8d2f6-7e1a-4d3c-8f9b-6a2e0c5d1b78",      "type": "email.delivered",      "payload": { "test": true, "recipients": ["delivered@sandbox.avelto.dev"] },      "occurred_at": "2026-09-17T10:12:06.000Z"    }  ]}

5. Verify a domain

Add a domain, publish the DNS records it returns (an ownership TXT, an SPF TXT and a DMARC TXT, then three DKIM CNAMEs once the ownership record is found), then poll domains.get until status is verified. It re-checks DNS on every call. Use a subdomain such as mail.acme.com.

Python
# verify_domain.pyimport osimport timefrom avelto import Aveltoavelto = Avelto(os.environ["AVELTO_API_KEY"])def show(d):    for rec in d["dns_records"]:        print(rec["type"], rec["name"], rec["value"], f"({rec['purpose']})", sep="\t")domain = avelto.domains.create("mail.acme.com")show(domain)  # the ownership record first, with SPF and DMARC# Publish the records, then poll. GET re-checks DNS on every call.# Once the ownership record is found, the DKIM records appear: publish those too.while domain["status"] == "pending":    time.sleep(30)    latest = avelto.domains.get(domain["id"])    if latest["ownership_verified"] and not domain["ownership_verified"]:        show(latest)    domain = latestprint(domain["status"])  # "verified" or "failed"
shell
python verify_domain.py

Once the domain is verified, switch AVELTO_API_KEY to a live key (av_live_) and change from_ to an address on it, such as hello@mail.acme.com. Nothing else changes.

Next

  • Python SDK: the whole client, sync and asyncio, with retries, batch send, templates and webhooks.
  • Send email: every field, attachments, tags, scheduling and idempotency.
  • Webhooks: get events pushed to your app, with verify_webhook_signature.
  • Test mode: test keys, the sandbox sender and the simulator addresses.