Quickstart: Python
One requests call to send, one to verify — safely, against the sandbox, before any real email is involved.
Facts verified 2026-07-17 — corrections: hello@emailfast.dev
1. Send
The API is plain JSON over HTTPS with bearer auth, so requests is all you need. (A generated Python SDK covers the same surface — see the docs — but the raw calls are two lines longer and hide nothing.)
import os
import requests
API = "https://api.emailfast.dev"
headers = {"Authorization": f"Bearer {os.environ['EMAILFAST_API_KEY']}"} # ef_sandbox_… works everywhere
r = requests.post(f"{API}/v1/emails", headers=headers, json={
"to": "ada@example.com",
"subject": "Welcome to the ledger",
"html": "<h1>Hi {{name}}</h1><p>Your account is ready.</p>",
"data": {"name": "Ada"},
"idempotency_key": "welcome-ada-1", # retry-safe: same key can never double-send
})
r.raise_for_status()
msg = r.json()
print(msg["id"], msg["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. That is why the idempotency_key matters: wrap this call in any retry loop you like — urllib3.Retry, tenacity, a bare for — and a timeout after the server committed the send still can't produce a duplicate.
2. Watch it happen
timeline = requests.get(f"{API}/v1/messages/{msg['id']}", headers=headers).json()
print([e["type"] for e in timeline["events"]]) # admitted → rendered → (sandbox-)delivered …With a sandbox key the message lands in your hosted capture inbox — same pipeline, nothing sent: sandbox keys (ef_sandbox_…) that run the real pipeline dry: real validation, real rendering, real events, a hosted capture inbox — and no email leaves.
3. Batch, if you need it
r = requests.post(f"{API}/v1/emails/batch", headers=headers, json={
"messages": [
{"to": "a@example.com", "subject": "Hi", "html": "<p>A</p>"},
{"to": "b@example.com", "subject": "Hi", "html": "<p>B</p>"},
],
"idempotency_key": "digest-2026-07-17", # derived per recipient — partial retries stay safe
})Up to 500 per call, with per-item results.