Email System
Complete guide to the email system using MJML templates, django-templated-email, and Resend for delivery
Content Rendering Error
This page contains invalid MDX syntax and cannot be fully rendered.
Language `mjml` is not included in this bundle. You may want to load it from external source.
Email System¶
Overview¶
The email system uses MJML templates for responsive, cross-client compatible HTML emails.
Stack¶
- MJML: Email markup language for responsive emails
- django-mjml: Django template tag for MJML compilation
- django-templated-email: Template-based email sending
- Resend: Production email delivery (via Anymail)
- Mailpit: Local email testing
Templates¶
Location¶
- MJML templates:
backend/templates/mjml/ - Templated email:
backend/templates/templated_email/
Base Templates¶
All email templates inherit from base templates:
- MJML:
backend/templates/mjml/base.mjml - Templated email:
backend/templates/templated_email/base_email.html
Context Variables¶
All templates have access to:
site_name: Site name from Constance configSUPPORT_EMAIL: Support email addressdomain: Site domain
Local Development¶
Mailpit Setup¶
Mailpit captures all outgoing emails locally for testing.
Option 1: Homebrew (macOS)¶
Code
# Install
brew install mailpit
# Start
brew services start mailpit
# Stop
brew services stop mailpit
Option 2: Docker¶
Code
docker run -d \
--name mailpit \
-p 1025:1025 \
-p 8025:8025 \
axllent/mailpit
Access¶
- SMTP:
localhost:1025 - Web UI:
http://localhost:8025
Environment Configuration¶
Local development automatically uses Mailpit (SMTP on port 1025).
Code
# config/settings.py (local dev)
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "localhost"
EMAIL_PORT = 1025
Testing Emails¶
Run Tests¶
Code
# Test all email templates render correctly
pytest tests/test_email_rendering.py -v
Send Test Emails¶
Code
# Send to Mailpit (local)
python manage.py send_test_email your@email.com --all --local
# Send via Resend (live)
python manage.py send_test_email your@email.com --all --live
# Categories
python manage.py send_test_email your@email.com --auth --local
python manage.py send_test_email your@email.com --security --local
python manage.py send_test_email your@email.com --promotional --local
python manage.py send_test_email your@email.com --mjml --local
Categories¶
| Flag | Description |
|---|---|
--auth |
Email confirmation, magic links, invitations |
--security |
Password reset, unknown account, account deletion |
--promotional |
Waitlist, subscriptions, trials, newsletters |
--mjml |
Pure MJML templates |
--all |
All templates |
Production¶
Production uses Resend via Anymail.
Code
# config/settings.py (production)
EMAIL_BACKEND = "anymail.backends.resend.EmailBackend"
ANYMAIL = {"RESEND_API_KEY": env("RESEND_API_KEY")}
Required Environment Variables¶
Code
RESEND_API_KEY=re_xxxxx
DEFAULT_FROM_EMAIL=noreply@yourdomain.com
SUPPORT_EMAIL=support@yourdomain.com
Creating New Templates¶
MJML Template¶
Code
{% extends "mjml/base.mjml" %}
{% block title %}Email Title{% endblock %}
{% block preview %}Preview text for email clients{% endblock %}
{% block content %}
<mj-text font-size="22px" font-weight="bold" padding-bottom="20px">
Heading
</mj-text>
<mj-text>
Hi {{ user.first_name|default:"there" }},
</mj-text>
<mj-text>
Your email content here.
</mj-text>
<mj-button href="{{ action_url }}" padding="30px 0">
Call to Action
</mj-button>
{% endblock %}
Templated Email¶
Code
{% extends "templated_email/base_email.html" %}
{% block subject %}Email Subject{% endblock %}
{% block heading %}Email Heading{% endblock %}
{% block content %}
<mj-text>
Hi {{ user.first_name|default:"there" }},
</mj-text>
<mj-text>
Your email content here.
</mj-text>
{% endblock %}
{% block cta %}
<mj-button href="{{ action_url }}" padding="30px 0">
Call to Action
</mj-button>
{% endblock %}
Sending Emails Programmatically¶
Code
from templated_email import send_templated_mail
send_templated_mail(
template_name="users/user_magic_link",
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
context={
"user": user,
"magic_link": magic_link_url,
"site_name": config.SITE_NAME,
"SUPPORT_EMAIL": settings.SUPPORT_EMAIL,
},
)
Last updated on