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 config
  • SUPPORT_EMAIL: Support email address
  • domain: 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