Send email from FastAPI with an email API
Send email from FastAPI with a typed client: build it once at startup, call it from a route, and get an id back for every message you send.
# main.py — pip install rasketimport os
from fastapi import FastAPIfrom rasket import Rasket
app = FastAPI()
# Built once, so its connection pool is reused.rasket = Rasket( api_key=os.environ["RASKET_API_KEY"], user_agent="acme-billing/1.0",)
@app.post("/orders/{order_id}/shipped")def send_shipping_email( order_id: str,) -> dict[str, str]: result = rasket.emails.send( { "from": "Acme <orders@send.acme.example>", "to": ["ronald.williams@example.com"], "subject": "Your order has shipped", "html": "<p>Order 1042 shipped today.</p>", }, idempotency_key=f"order-{order_id}", )
return {"id": result.body["id"]}What sending from FastAPI actually involves
To send email from FastAPI you make one HTTPS call from the route that knows the mail should go out. Build the client once at module scope rather than per request, so its connection pool is reused; the sample below does that. The rasket package is typed, so the message body is checked by the same type checker that checks your route.
Last updated 2026-09-16. Every field, header and limit below is the one the emails reference carries.
What you get with the key
Five things that arrive with the first request rather than with a later plan.
Idempotent sends
Put an Idempotency-Key on a send and repeat it as often as you like. For 24 hours the same key and payload return the first response instead of a second email, and a different payload under that key is refused rather than sent.
Signed webhooks you can replay
Delivery, bounce, complaint, open and click posted to your endpoint with a timestamp and a signature, retried on failure and replayable from the dashboard.
Inbound mail on your own domain
Receive at your domain on every plan. Each message arrives on a signed webhook with its headers, text and HTML, and its attachments behind signed links.
Templates with typed variables
Publish a template, then send it by ID or alias with the values it declares. Every email records the exact version it was rendered from.
Broadcasts and automations
Contacts with typed properties, topics people subscribe to, and per-contact workflows that wait, branch and send — through the same pipeline as the rest of your mail.
How to send email from FastAPI
Six steps, in this order. Everything before the fifth is done once, and only the fifth is about the language you write in.
- 1
Create an API key
Open API keys in the dashboard and create one. It is shown once and stored hashed, so copy it then. Put it in your environment as RASKET_API_KEY and read it from there; a key committed to a repository is a key you have to rotate.
- 2
Add a sending domain
POST /domains with a domain you control. A subdomain such as send.acme.example is the usual choice, because it keeps this mail's reputation separate from the address your people write from. The response carries a records array.
- 3
Publish the DNS records
Every row of records goes into your DNS: the DKIM TXT record at rasket._domainkey, and the two records that give the domain its own return path. Nothing sends from the domain until they resolve.
- 4
Verify the domain
POST /domains/{domain_id}/verify. The domain comes back with a status on each record, and once it reads verified you can send from any address on it. An unverified sender is refused rather than quietly dropped.
- 5
Send your first email from FastAPI
Post the message with the sample above. Carry an Idempotency-Key — yours or your queue's retry cannot then turn into a second email — and a User-Agent, which every request has to have. A 200 with an id means we have taken responsibility for the message.
- 6
Subscribe a webhook and check its signature
POST /webhooks with an https endpoint and the events you care about. Delivery, bounce, complaint, open and click arrive there with svix-id, svix-timestamp and svix-signature; verify against the raw body before anything parses it.
Verify webhooks in FastAPI
In an async route, the raw body is await request.body(). Read it before you touch the parsed model, and pass it with the headers to the svix package's Webhook(secret).verify(). Declaring a Pydantic model for the payload is convenient and destroys the signature: the bytes are gone by the time your function runs.
The five steps a verifier performs, in seven languages, are on the webhooks reference.
Questions about FastAPI
Do I need an SMTP server to send email from FastAPI?
No. The send is an ordinary HTTPS request, so it works anywhere the app runs — a container, a serverless function, a VM. There is no relay to keep up and no SMTP credentials to rotate, just one key in the environment.
Where do I keep the API key in a FastAPI app?
In the environment as RASKET_API_KEY, read once at startup. If you use pydantic-settings, put it on the settings model so a missing value is a clear error at boot rather than an exception on the first send.
Should the send block the response?
Not if you can help it. The synchronous client call will block the event loop, so put it behind a BackgroundTask, a worker, or a thread — and give it an idempotency key, because a retried background task is exactly the case the key exists for.
How do I stop a retry from sending the same email twice?
Pass idempotency_key built from the resource the mail concerns, such as order-1042, and reuse it on every attempt. For 24 hours a repeat returns the first response instead of sending again; a different payload under that key is refused rather than guessed at.
How do I verify a Rasket webhook in FastAPI?
Take the Request object rather than a parsed model, await request.body() first, and hand those bytes with dict(request.headers) to the svix package's verify(). Return 200 once stored and 400 when it raises; we retry a failure and you can replay any event from the dashboard.
Keep reading
Make your first send
Create a key, verify a domain, and post your first message from FastAPI. The free plan does not expire.