Send emails with Django
Transactional and marketing email from a Django view, service, or Celery task. One HTTP call returns an id you can trace.
✓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 Django?
Create a key, keep it on the server, and call one endpoint from a view, service, or Celery task.
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
requests posts to the email API. A stable Idempotency-Key makes a task retry safe: the same key never sends a second email.
send_welcome.pyimport os import requests response = requests.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"}, timeout=10, ) response.raise_for_status()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 Django app?
Views, services, and tasks
Call straight from a view when a 202 is enough. Use Celery or django-q when your own work must outlive the request.
Events from a service
Send events from a service after the database change commits, not from a signal that hides the network call.
A raw body for webhooks
A csrf_exempt view reads request.body before anything parses it.
[ 03 / 05 ]Marketing email
Can I send marketing emails from Django too?
Yes. Send an event from the same service 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 Django?
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 Django signup view using Send & Retain, with an idempotency key.”
Celery task
“Move our password reset email into a Celery task that calls Send & Retain after the transaction commits.”
Webhook
“Add a Django view that verifies Send & Retain delivery webhooks and marks bounced users.”
Trial automation
“Send a trial_started event from our Django app and build a paused trial-conversion automation for it.”
[ 05 / 05 ]Related
Keep building with Django
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.
FastAPI
Send emails from FastAPI endpoints and background tasks.
Questions people ask us
Send your first email from Django today.
Create a key, paste the code into a view, service, or Celery task, and watch the email land in the message log.