Send email from FastAPI with emailr_
Send transactional email from FastAPI with the async emailr_ Python SDK, run sends as background tasks and verify signed delivery webhooks.
The emailr Python package ships both a sync Emailr client and an AsyncEmailr client. In FastAPI, use the async client so sending never blocks the event loop.
This guide creates one client for the app's lifetime, sends from an endpoint and a background task, and verifies webhooks.
Set up emailr_ with FastAPI
- 01
Create an API key
Create a key under API keys in the dashboard. Keys start with
et_live_and are scoped to one domain workspace. Store it asEMAILR_API_KEYin your secret manager or environment, never in client-side code. - 02
Add and verify your sending domain
In the dashboard, open Domains and add the domain you send from. A subdomain such as
mail.yourcompany.comkeeps transactional reputation separate from your corporate mailbox.Emailr generates the DNS records: a DKIM TXT record on the
emailr._domainkeyselector, an SPF TXT record, and an MX + TXT pair on thesendsubdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.Emailr's DKIM selector and
sendsubdomain don't collide with other providers' records, so you can add them before cutting over and send from both providers during the migration. A domain can only have one SPF record: if one already exists, merge Emailr'sinclude:into it instead of adding a second record. - 03
Install the SDK
Install the package and set
EMAILR_API_KEYin your environment.Terminalbashpip install emailr - 04
Create one client per app
Open the client in FastAPI's lifespan handler and close it on shutdown so connections are reused across requests.
main.pypythonimport os from contextlib import asynccontextmanager from emailr import AsyncEmailr from fastapi import FastAPI @asynccontextmanager async def lifespan(app: FastAPI): app.state.emailr = AsyncEmailr(api_key=os.environ["EMAILR_API_KEY"]) yield await app.state.emailr.close() app = FastAPI(lifespan=lifespan) - 05
Send from an endpoint or a background task
fromis a Python keyword, so the SDK names the argumentfrom_. UseBackgroundTaskswhen the response shouldn't wait for the email.main.pypythonfrom fastapi import BackgroundTasks, Request from pydantic import BaseModel, EmailStr class Signup(BaseModel): email: EmailStr async def send_welcome(client: AsyncEmailr, email: str) -> None: await client.emails.send( to=email, from_="Acme <hello@mail.acme.com>", subject="Welcome to Acme", html="<p>Thanks for signing up.</p>", tags={"category": "welcome"}, ) @app.post("/signup") async def signup(body: Signup, request: Request, tasks: BackgroundTasks): tasks.add_task(send_welcome, request.app.state.emailr, body.email) return {"ok": True} - 06
Verify delivery webhooks
Read the raw bytes with
await request.body()before parsing, compute the HMAC with your webhook secret and compare in constant time.main.pypythonimport hashlib import hmac import json from fastapi import HTTPException @app.post("/webhooks/emailr") async def emailr_webhook(request: Request): raw = await request.body() expected = hmac.new( os.environ["EMAILR_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256 ).hexdigest() signature = request.headers.get("x-emailr-signature", "") if not hmac.compare_digest(expected, signature): raise HTTPException(status_code=401, detail="Invalid signature") payload = json.loads(raw) if payload["event"] == "email.bounced": ... # mark the address as undeliverable return {"ok": True}
Tips
send()returns a response object withmessage_id,recipientsandstatus. Storemessage_idto match webhook events later.- Pass
template_idandtemplate_datato send stored templates instead of inline HTML. - For heavy volumes, move sends to a worker such as Celery or RQ and use the sync
Emailrclient there.
Frequently asked questions
Is there an async Python SDK for emailr_?
Yes. The emailr package includes AsyncEmailr for asyncio frameworks like FastAPI, alongside the synchronous Emailr client.
Why is the sender argument called from_?
from is a reserved word in Python, so the SDK uses from_. It's sent to the API as the from field.
Should I send email in a FastAPI BackgroundTask?
For user-facing endpoints, yes: the response returns immediately and the email is sent right after. For guaranteed delivery with retries, use a task queue.
How do I verify emailr_ webhooks in Python?
Compute hmac.new(secret, raw_body, hashlib.sha256).hexdigest() and compare it to the X-Emailr-Signature header with hmac.compare_digest.
Keep going
Related guides
- Send email from LaravelSend transactional email from Laravel with the emailr_ HTTP API: render Mailables, queue sends with jobs and verify signed delivery webhooks.
- Send email from RailsSend Action Mailer email from Rails through the emailr_ API with a custom delivery method, deliver_later support and signed webhook verification.
- Send email from AWS LambdaSend transactional email from AWS Lambda with the emailr_ SDK, process SQS batches with partial failure reporting and handle signed webhooks.
Start sending in minutes
Create a free account, verify your domain and send your first email. No credit card required.