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.
- Use a transactional email provider (SES, Postmark, SendGrid, Mailgun, Brevo) via django-anymail or SMTP with TLS.
- Send from Celery tasks triggered with
transaction.on_commit, with retries and timeouts. - Authenticate your sending domain with SPF, DKIM, and a DMARC policy; use a dedicated subdomain for app mail.
- Handle bounces and complaints via provider webhooks: suppress bad addresses and alert on spikes.
- Make non-production environments safe: console or sandbox backends, or an allowlist of recipients.
Checklist
Backend and sending
EMAIL_BACKENDset to an Anymail backend or SMTP withEMAIL_USE_TLS/EMAIL_USE_SSLmatching the port.EMAIL_TIMEOUTset (for example 10 seconds) so a stuck SMTP connection cannot hang workers.DEFAULT_FROM_EMAILandSERVER_EMAILset to addresses on your authenticated domain.- Emails sent from Celery tasks after commit, with retries on transient errors.
ADMINSand 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=nonewith reports, moving toquarantineorreject. - Transactional mail sent from a subdomain (for example
mail.example.com) separate from marketing and employee mail.
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_uriin 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.
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_adminson errors can flood inboxes during incidents; prefer an error tracker.
Next steps
- Improve inbox placement with the Django Email Deliverability Checklist.
- Set up the backend with Configure Django Anymail.
- Send in the background with Send Django Email Asynchronously With Celery.
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.