Send emails with Python

pip install mailstein

Every call returns (data, error) rather than raising, so the failure case is impossible to forget.

import os
from mailstein import Mailstein

mailstein = Mailstein(os.environ["MAILSTEIN_API_KEY"])

data, error = mailstein.emails.send({
    "from": "hello@yourdomain.com",
    "to": "someone@example.com",
    "subject": "Welcome",
    "html": "<p>Thanks for signing up.</p>",
})

if error:
    raise RuntimeError(error["message"])
print(data["emailId"])

FastAPI

from fastapi import BackgroundTasks, FastAPI, HTTPException
from pydantic import BaseModel, EmailStr

app = FastAPI()

class Signup(BaseModel):
    email: EmailStr

def send_welcome(email: str) -> None:
    _, error = mailstein.emails.send({
        "from": "hello@yourdomain.com",
        "to": email,
        "subject": "Welcome",
        "html": "<p>Thanks for signing up.</p>",
    })
    if error:
        # Log it; do not raise. Nobody is waiting on this, and an exception in
        # a background task is a signup that already succeeded looking broken.
        print("send failed:", error)

@app.post("/signup")
def signup(body: Signup, background: BackgroundTasks):
    background.add_task(send_welcome, body.email)
    return {"ok": True}

Sending in the background is the point. The SDK is synchronous, so calling it inline blocks the request for as long as the send takes.

Django

# views.py
from django.http import JsonResponse

def signup(request):
    data, error = mailstein.emails.send({
        "from": "hello@yourdomain.com",
        "to": request.POST["email"],
        "subject": "Welcome",
        "html": "<p>Thanks for signing up.</p>",
    })
    if error:
        return JsonResponse({"error": "Could not send"}, status=502)
    return JsonResponse({"id": data["emailId"]})

For anything beyond a handful of messages, put the send in Celery rather than the request. Or use SMTP, which lets Django's own send_mail work unchanged.

Something here wrong or missing? It is generated from the running API — tell us and we will fix the source.