FastAPI
Send transactional email from FastAPI through eusend with the Python SDK — in a background task so the response is not held up, with idempotent retries and typed errors.
The eusend Python SDK is synchronous and built on the standard library, so it has no HTTP
client to conflict with yours. In FastAPI that means one rule: don't call it directly inside
an async def endpoint, where it would block the event loop. Use a background task, or a
plain def endpoint, and FastAPI runs the call in its thread pool.
Setup
pip install eusendEUSEND_API_KEY=eu_test_...The SDK reads EUSEND_API_KEY from the environment, so there is nothing to configure in
code. An eu_test_ key runs validation, quota and suppression checks and logs the email
without delivering it.
Send in a background task
For email the caller does not need confirmed — a welcome message, a notification — respond first and send afterwards:
import logging
import eusend
from fastapi import BackgroundTasks, FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
log = logging.getLogger(__name__)
class Signup(BaseModel):
name: str
email: EmailStr
def send_welcome(name: str, email: str) -> None:
try:
eusend.Emails.send({
'from': 'Acme <hello@yourdomain.com>',
'to': email,
'subject': 'Welcome to Acme',
'html': f'<p>Hi {name}, thanks for signing up.</p>',
})
except eusend.EusendError as e:
log.warning('welcome email failed: %s %s', e.code, e.message)
@app.post('/signup', status_code=201)
async def signup(body: Signup, background_tasks: BackgroundTasks):
background_tasks.add_task(send_welcome, body.name, body.email)
return {'ok': True}EmailStr needs pip install "pydantic[email]". Escape anything user-supplied before it goes
into html — the example above keeps it short, and html.escape(name) is the fix.
Send and report the result
When the caller does need to know — a receipt, a password reset — use a def endpoint and
turn the SDK's exception into a response:
from fastapi import HTTPException
class Receipt(BaseModel):
order_id: str
email: EmailStr
@app.post('/receipts')
def send_receipt(body: Receipt):
try:
email = eusend.Emails.send(
{
'from': 'Acme <billing@yourdomain.com>',
'to': body.email,
'subject': f'Receipt for order {body.order_id}',
'html': '<p>Thanks — your order is confirmed.</p>',
},
options={'idempotency_key': f'receipt-{body.order_id}'},
)
except eusend.EusendError as e:
raise HTTPException(status_code=502, detail=e.code)
return {'id': email['id']}The idempotency_key makes a retried request return the original email instead of sending a
second one. Branch on e.code — the error code — not on the
message. eusend.RateLimitError is the subclass to catch if you want to back off on a 429.
Surviving a restart
A background task lives in the process: if the server restarts before it runs, the email is
never sent. For mail that must go out, put the send on a real queue (Celery, RQ, Arq) and
keep the idempotency_key so a retried job cannot send twice.
Next steps
- Python SDK reference for attachments, scheduling and batch sends.
- Verify a domain to send to real recipients from your own address.
- Webhooks to learn when a message is delivered, bounced or complained about.
SvelteKit
Send transactional email from SvelteKit through eusend — from a form action or a server endpoint, with the API key kept in private environment variables.
Better Auth
Send Better Auth's verification, password-reset, OTP, magic-link and invitation email through eusend with the official plugin — branded default templates, non-blocking sends, and contact sync for verified users.