Send emails with FastAPI
Transactional and marketing email from a FastAPI endpoint. One HTTP call returns an id you can follow to the inbox.
✓Free to start. Unlimited contacts on every tier.
One request, complete trace
Request
POST /api/v1/emails
Authorization: Bearer aem_…
Idempotency-Key: welcome-8f21
{ "to": "[email protected]", "template": "welcome" }
202 Accepted
{ "id": "msg_7ca31", "status": "queued" }
[ 01 / 05 ]Quickstart
How do I send email from FastAPI?
Create a key, keep it on the server, and call one endpoint from a FastAPI endpoint.
Get an API key
Open Settings → API & MCP and create a key with the Send + manage permission (the default). It starts with aem_ and is shown once.
Store it as a server-side environment variable
Set AEM_KEY in the environment of every process that sends, web and workers alike. The code reads it with os.environ.
EnvironmentAEM_KEY=aem_…Send the email
httpx.AsyncClient posts to the email API from your async code, and raise_for_status() raises on a 4xx or 5xx. A retry with the same Idempotency-Key won't send a second email.
send_welcome.pyimport os import httpx async def send_welcome(user) -> dict: async with httpx.AsyncClient(timeout=10) as client: response = await client.post( "https://sendandretain.com/api/v1/emails", headers={"Authorization": f"Bearer {os.environ['AEM_KEY']}", "Idempotency-Key": f"welcome-{user.id}"}, json={"to": user.email, "template": "welcome"}, ) response.raise_for_status() return response.json()Check the response
A 202 means queued, not delivered. Keep the id, then follow delivery in the message log or through a signed webhook.
202 Accepted{ "id": "msg_7ca31", "status": "queued" }
[ 02 / 05 ]Where it goes
Where should email code live in your FastAPI app?
Endpoints and background tasks
Call with httpx when the id belongs in the response. BackgroundTasks run in the web process, so use them only for short work.
Typed product events
Send typed events after the state change succeeds, so automations follow what really happened.
A raw body for webhooks
Type the endpoint to take Request and await request.body() before validation.
[ 03 / 05 ]Marketing email
Can I send marketing emails from FastAPI too?
Yes. Send an event from the same endpoint when a user signs up, starts a trial, or goes quiet, and an automation sends the right email. Automations start paused, and every email goes to one person: no newsletter blasts.
- Transactional and marketing email share one contact record and one unsubscribe list.
- Unsubscribes, bounces, and complaints are suppressed for you before every send.
- Templates are react.email components; published versions never change under you.
[ 04 / 05 ]Example prompts
What can you ask your coding agent to do in FastAPI?
Claude Code, Cursor, and Codex can do these over the Send & Retain MCP server, with your repo open. Automations they build stay paused until you turn them on.
Welcome email
“Add a welcome email to our FastAPI signup endpoint using Send & Retain and httpx, with an idempotency key.”
Magic link
“Add a sign-in link email to our FastAPI auth router, render it in our brand, and send me a test.”
Webhook
“Add a FastAPI endpoint that verifies Send & Retain delivery webhooks and marks bounced users.”
Trial automation
“Send a trial_started event from our FastAPI service and build a paused trial-conversion automation for it.”
[ 05 / 05 ]Related
Keep building with FastAPI
Python
Send emails from sync and async Python services with one HTTP call.
Email API
One REST API for every send, with scoped keys and signed webhooks.
Transactional email
Receipts, resets, and alerts on the same contact record as your automations.
Django
Send emails from Django views, services, and Celery tasks.
Questions people ask us
Send your first email from FastAPI today.
Create a key, paste the code into a FastAPI endpoint, and watch the email land in the message log.