Email Fast Get a sandbox key
Guides

Send email with Python

The stdlib is enough: urllib, one JSON POST, honest error handling.

Facts verified 2026-07-17 — corrections: hello@emailfast.dev

Python's standard library is enough — the Email Fast API is one JSON POST, so urllib.request does the job without adding requests to your dependencies. Wrap it in a small function and call it anywhere. If you'd rather have a typed client, the Python SDK quickstart covers that instead.

# send.py — Python 3.10+, stdlib only
import json
import os
import urllib.error
import urllib.request

API_KEY = os.environ["EMAILFAST_API_KEY"]  # ef_sandbox_... while testing

def send_email(to: str, subject: str, html: str, data: dict, idempotency_key: str) -> dict:
    req = urllib.request.Request(
        "https://api.emailfast.dev/v1/emails",
        data=json.dumps({
            "to": to,
            "subject": subject,
            "html": html,
            "data": data,
            "idempotency_key": idempotency_key,
        }).encode(),
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as res:
            return json.load(res)
    except urllib.error.HTTPError as e:
        raise RuntimeError(f"send rejected ({e.code}): {e.read().decode()}") from e

result = send_email(
    to="ada@example.com",
    subject="Welcome",
    html="<h1>Hi {{name}}</h1>",
    data={"name": "Ada"},
    idempotency_key="welcome-ada-1",  # a retry replays, never re-sends
)
print(result["id"], result["status"])  # msg_... queued

a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.

Gotchas

Next

See it for yourself

Sandbox keys run the real pipeline dry — real validation, real events, a hosted inbox, no email sent. Early access is onboarding now.