Django Email Production Checklist

A production checklist for email from Django: a transactional provider or Anymail backend, sending from Celery after commit, SPF, DKIM, and DMARC, bounce and complaint webhooks, separate streams for marketing, templates with text parts, staging safety, and monitoring.

Problem statement

Password resets, receipts, invitations, and alerts all depend on email, and email failures are silent: a message lands in spam, a provider suspends your account after complaints, staging sends real customers test emails, or a slow SMTP server makes signup take ten seconds. This checklist covers the Django side of sending email reliably, from the backend and queueing to authentication records, feedback handling, and environment safety.

Email from DjangoSendCelery, after commitProviderAnymail or SMTPAuthenticateSPF, DKIM, DMARCFeedbackbounces, complaintsReliable email needs async sending, authenticated domains, and feedback loops.
  1. Use a transactional email provider (SES, Postmark, SendGrid, Mailgun, Brevo) via django-anymail or SMTP with TLS.
  2. Send from Celery tasks triggered with transaction.on_commit, with retries and timeouts.
  3. Authenticate your sending domain with SPF, DKIM, and a DMARC policy; use a dedicated subdomain for app mail.
  4. Handle bounces and complaints via provider webhooks: suppress bad addresses and alert on spikes.
  5. Make non-production environments safe: console or sandbox backends, or an allowlist of recipients.

Checklist

Backend and sending

  • EMAIL_BACKEND set to an Anymail backend or SMTP with EMAIL_USE_TLS/EMAIL_USE_SSL matching the port.
  • EMAIL_TIMEOUT set (for example 10 seconds) so a stuck SMTP connection cannot hang workers.
  • DEFAULT_FROM_EMAIL and SERVER_EMAIL set to addresses on your authenticated domain.
  • Emails sent from Celery tasks after commit, with retries on transient errors.
  • ADMINS and error emails either configured deliberately or replaced by an error tracker.

Domain authentication

  • SPF record includes the provider; no more than 10 DNS lookups.
  • DKIM signing enabled with the provider's CNAME or TXT records.
  • DMARC record published, starting at p=none with reports, moving to quarantine or reject.
  • Transactional mail sent from a subdomain (for example mail.example.com) separate from marketing and employee mail.
record and purposeProvesSet up bySPF (TXT)allowed sending serversyou, provider includeDKIM (CNAME or TXT)message not alteredprovider keysDMARC (TXT)policy for failuresyou, start p=noneReturn-Path / MAIL FROMaligned bounce domainprovider guideMX for bouncesbounce processingprovider, if customSPF and DKIM authenticate; DMARC tells receivers what to do when they faildjango-deployment.com
Major mailbox providers require SPF, DKIM, and DMARC for bulk senders.

Feedback and reputation

  • Bounce and complaint webhooks received (Anymail provides tracking signals), signature verified.
  • Hard-bounced and complaining addresses suppressed; users notified to fix their address.
  • Alerts when bounce or complaint rates exceed provider thresholds.
  • Marketing and newsletters on a separate stream or provider account, with unsubscribe headers (List-Unsubscribe, one-click).

Content and templates

  • HTML emails include a plain-text alternative (EmailMultiAlternatives).
  • Absolute URLs built from a configured site URL, not request.build_absolute_uri in tasks.
  • Links point to your domain (consider click tracking domains carefully).
  • Translations and time zones applied per recipient.

Environments and monitoring

  • Staging and development use the console backend, a sandbox, or a recipient allowlist; never real customer addresses.
  • Copies of production data in staging have emails anonymized.
  • Dashboards for sent, delivered, bounced, and complained counts; alerts on sending failures and queue backlog.
from event to inbox1Business event commitson_commit hook2Celery task rendersHTML + text3Provider API sendsretries on errors4DKIM-signed deliverySPF, DMARC pass5Webhooks report statussuppress bouncesEach step either succeeds, retries, or reports backdjango-deployment.com
Users never wait on email delivery in the request.

Explanation

Sending email is easy; getting it delivered and handling failures is not. Synchronous SMTP in a view ties user-facing latency to an external server. Mailbox providers decide inbox placement based on domain authentication and reputation, which depends on bounces and complaints you must process. Separating environments prevents embarrassing and harmful test emails. A Django setup with Anymail, Celery, authenticated DNS, and feedback webhooks covers these concerns with standard tools.

Edge cases / notes

  • Port blocking. Many cloud providers block outbound port 25 and sometimes 587; HTTP API backends avoid this.
  • Rate limits. Providers and new domains have sending limits; warm up volume gradually.
  • Error emails. Django's mail_admins on errors can flood inboxes during incidents; prefer an error tracker.

Next steps

FAQ

Should Django send email directly via SMTP?

It can, but a transactional provider via an HTTP API is more reliable and avoids port blocking.

Why send email from Celery?

So users do not wait on email servers, and failures can be retried.

Do I need DMARC?

Yes. Major mailbox providers expect SPF, DKIM, and DMARC, especially for bulk senders.

How do I keep staging from emailing customers?

Use the console or a sandbox backend, or an allowlist of recipients, and anonymize emails in copied data.

Keep going