New from emailrHealthCheck Email iconHealthCheck Email
emailr_

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.

FastAPI iconFastAPIFramework

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

  1. 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 as EMAILR_API_KEY in your secret manager or environment, never in client-side code.

  2. 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.com keeps transactional reputation separate from your corporate mailbox.

    Emailr generates the DNS records: a DKIM TXT record on the emailr._domainkey selector, an SPF TXT record, and an MX + TXT pair on the send subdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.

    Emailr's DKIM selector and send subdomain 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's include: into it instead of adding a second record.

  3. 03

    Install the SDK

    Install the package and set EMAILR_API_KEY in your environment.

    Terminalbash
    pip install emailr
  4. 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.pypython
    import 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)
  5. 05

    Send from an endpoint or a background task

    from is a Python keyword, so the SDK names the argument from_. Use BackgroundTasks when the response shouldn't wait for the email.

    main.pypython
    from 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}
  6. 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.pypython
    import 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 with message_id, recipients and status. Store message_id to match webhook events later.
  • Pass template_id and template_data to send stored templates instead of inline HTML.
  • For heavy volumes, move sends to a worker such as Celery or RQ and use the sync Emailr client 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.

Start sending in minutes

Create a free account, verify your domain and send your first email. No credit card required.