How to Develop Responsive HTML Email in Django

Table of Contents

Introduction

In this tutorial, I will talk about how to develop Responsive HTML Email in Django.

After reading this article, you will learn:

  1. What is responsive HTML Email and the benefits
  2. What is MJML and How to develop responsive HTML Email with MJML
  3. How to make MJML work with Django template.
  4. How to check HTML Email in local development environment.

What Is Responsive HTML Email

With responsive design, you can send email templates which can change depending on what screen size they are viewed. These emails will always render correctly regardless of the device it’s viewed on.

Responsive email designs use CSS media queries to produce two different copies that depend on the size of your user’s screen. Media queries will automatically adjust the email copy’s layout, content, and text size to the user’s device screen.

If you have experience with some responsive frameworks such as Bootstrap, Tailwind CSS, I guess you already know what Responsive HTML Email is.

HTML Email Can Be a Pain

If you have developed HTML email, I am sure the experience was not good:

  1. HTML emails lack standards: The code used to design these emails is just plain HTML and CSS which is no way like on the web. No real standards exist between email clients, leading to a crazy code.
  2. Email Clients: Email clients, like Outlook and Gmail, all render HTML and CSS differently. So the same message looks different on different clients. It becomes difficult to design and create one email that looks perfect on all clients.
  3. Lots of hacks: Even well-designed email campaigns need to rely on client-specific hacks to make things work.
  4. No JavaScript: The web’s favorite language has no place in email, as email clients (rightly) strip it due to security concerns.
  5. Inline styles: Unfortunately, most email clients force you to rely on inline styles and attributes for nearly everything in email.

If we check HTML email template provided by Mailgun https://github.com/mailgun/transactional-email-templates, we can see HTML code like this:

As you can see, the HTML is not clean and not easy to maintain.

Are there other solutions which can help us solve this problem?

MJML

MJML stands for "Mailjet Markup Language". MJML has been designed to reduce the pain of coding a responsive email. Leveraging its semantic syntax and a rich standard components library, making your email responsive is not an issue anymore.

Below is an example

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>

        <mj-image width="100px" src="/assets/img/logo-small.png"></mj-image>

        <mj-divider border-color="#F45E43"></mj-divider>

        <mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>

      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
  1. The syntax is very similar with HTML
  2. You can set attributes as you like to change the style.
  1. You can check the great tutorial https://mjml.io/getting-started/1
  2. And an online editor is available for you to play with MJML and check the real-time result https://mjml.io/try-it-live
  3. And you can also check the complete guide https://documentation.mjml.io/

With MJML, you can quickly build good looking responsive HTML email, there are also many MJML Responsive Email Templates for you to learn and use.

Develop Workflow

  1. We can write MJML code in the Django template.
  2. When rendering the Django template, convert the MJML code to the final HTML code, and use the HTML sending Email.

How to send MJML in Django

django-mjml can help us get the work done in elegant way.

Create Django Project

$ mkdir django_email_project && cd django_email_project

$ python3.10 -m venv venv
$ source venv/bin/activate
(venv)$
(venv)$ pip install django==4.1.1
(venv)$ django-admin startproject django_project .
(venv)$ python manage.py migrate
(venv)$ python manage.py runserver

Now check on http://127.0.0.1:8000/ and you will see Django welcome page, then press Ctrl+C to terminate the dev server.

Install django-mjml

(venv)$ pip install django-mjml

Update django_project/settings.py

INSTALLED_APPS = (
  ...,
  'mjml',
)

MJML_BACKEND_MODE = "cmd"
MJML_EXEC_CMD = "node_modules/.bin/mjml"

Notes:

  1. django-mjml supports different modes, here we use the simple CMD mode, the django-mjml will run CMD command to convert the MJML code to HTML
  2. The MJML command will be installed via Nodejs

Next, we will install MJML command:

$ npm install mjml

We will see a package.json created under the root directory

{
  "dependencies": {
    "mjml": "^4.13.0"
  }
}

Let's test

$ ./node_modules/.bin/mjml --help

Options:
  -r, --read                 Compile MJML File(s)                        [array]
  -m, --migrate              Migrate MJML3 File(s) (deprecated)          [array]
  -v, --validate             Run validator on File(s)                    [array]
  -w, --watch                Watch and compile MJML File(s) when modified[array]
  -i, --stdin                Compiles MJML from input stream
  -s, --stdout               Output HTML to stdout
  -o, --output               Filename/Directory to output compiled files[string]
  -c, --config               Option to pass to mjml-core
  -V, --version              Show version number                       [boolean]
      --noStdoutFileComment  Add no file comment to stdout             [boolean]
      --help                 Show help                                 [boolean]

Now MJML command has been installed successfully

Templates

$ mkdir django_project/templates

Update TEMPLATES in django_project/settings.py, so Django can know where to find the templates

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['django_project/templates'],                 # update
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Create django_project/templates/email-templates.html

{% load mjml %}

{% mjml %}
<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text font-size="20px" color="#F45E43" font-family="helvetica">Hello World</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
{% endmjml %}

Test

MailHog is Web and API based SMTP testing

We can use MailHog to help us check Email on local.

$ docker run --rm -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
  1. 1025 is the SMTP port
  2. 8025 is the Mailhog web dashboard port

Update django_project/settings.py, so the Django will send email to localhost:1025, which mailhog is serving on that port.

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_PORT = 1025

Run code below in Django shell to test

from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string

html_body = render_to_string("email-templates.html")

message = EmailMultiAlternatives(
    subject='Django HTML Email',
    body="mail testing",
    from_email='[email protected]',
    to=['[email protected]']
)
message.attach_alternative(html_body, "text/html")
message.send(fail_silently=False)

Notes:

  1. If you check html_body in the Django shell, you can see it is already been converted from MJML to HTML

We can also check the HTML email on Mailhog dashboard http://localhost:8025/

As you can see, MJML can help us develop responsive HTML email in elegant way.

Use Case

When you build Django project, you might develop user Signup/Login feature with the great django-allauth project.

django-allauth supports HTML email template but you need to add the templates on your own.

With django-mjml, you can write MJML in Django template, while you can still use Django template syntax.

For example, you can create account/email/email_confirmation_message.html in the templates directory, to improve the style of the Confirm Email Address

Tips

  1. django-mjml supports different modes of deployment. For example, you can launch a standalone server which focus on converting MJML to HTML
  2. For simplicity, mjml cmd installed under node_modules is enough in most cases.

Reference

Getting Started with MJML A Complete Guide To HTML Email

Launch Products Faster with Django

Unlock the power of Django combined with Hotwire through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer who loves writing code, tutorials about Django, and modern frontend techs.

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer