Tutorial

Implementing HTML Emails in Phoenix with Swoosh and Premailex

Phoenix 1.7 email swoosh

In this tutorial, we will walk you through how to implement HTML emails in the Phoenix web framework using Swoosh and Premailex. By default, Phoenix comes with the ability to send text emails using the Swoosh library. However, we will add the ability to send HTML emails and use Premailex to inline CSS into the HTML elements as style-attributes. We'll also use Premailex to generate a pure text variant of the emails.

Step 1: Install Premailex

First, we need to add the Premailex package to our project. Open your mix.exs file and add the following line to your dependencies:

# mix.exs
{:premailex, "~> 0.3.18"},

Step 2: Modify the Mailer Module

Next, we need to modify the mailer module to utilize HTML templates and inline CSS. The changes will include adding some new functions that will be used for rendering email bodies and inlining CSS.

Replace your current mailer module in lib/tutorial/mailer.ex with the following:

defmodule Tutorial.Mailer do
  defmacro __using__(_opts \\ []) do
    quote do
      use Swoosh.Mailer, otp_app: :tutorial
      import Swoosh.Email
      import Tutorial.Mailer
    end
  end
  @moduledoc false
  use Swoosh.Mailer, otp_app: :tutorial
  import Swoosh.Email, only: [new: 0, from: 2, html_body: 2, text_body: 2]

  # Base email function should contain all common features
  def base_email do
    new()
    |> from(from_email())
  end

  def render_body(email, template), do: render_body(email, template, %{})

  def render_body(email, template, args) when is_atom(template) and is_map(args) do
    heex = apply(TutorialWeb.EmailHTML, template, [args])
    html_with_layout = render_component(TutorialWeb.EmailHTML.layout(%{inner_content: heex}))

    html_body(
      email,
      html_with_layout
    )
  end

  def render_body(email, "" <> template, args) do
    template = template |> String.split(".") |> List.first() |> String.to_atom()
    render_body(email, template, args)
  end

  def render_body(email, template, args) when is_list(args) do
    render_body(email, template, Map.new(args))
  end

  # Inline CSS so it works in all browsers
  def premail(email) do
    html = Premailex.to_inline_css(email.html_body)
    text = Premailex.to_text(email.html_body)
    text_with_layout = render_component(TutorialWeb.EmailHTML.layout_text(%{inner_content: text}))

    email
    |> html_body(html)
    |> text_body(text_with_layout)
  end

  defp render_component(heex) do
    heex |> Phoenix.HTML.Safe.to_iodata() |> IO.chardata_to_string()
  end

  defp from_email, do: "noreply@example.com"
end

The render_body/3 function takes an email, a template, and a map of arguments. It first converts the template into a string of HTML using apply/3, then wraps this HTML with the layout using render_component/1, and finally sets the resulting HTML as the body# The message was cut off, I am continuing the text here

of the email using html_body/2. The premail/1 function takes an email, inlines the CSS in the HTML body using Premailex.to_inline_css/1, and converts the HTML body to text using Premailex.to_text/1. The resulting HTML and text are then set as the bodies of the email using html_body/2 and text_body/2.

Step 3: Add the Email Template Module

Next, we need to create a module that will handle email templates. This module will use the TutorialWeb module and embed all HTML and text templates located in the "emails" directory.

Add the following code to lib/tutorial_web/emails.ex:

# lib/tutorial_web/emails.ex
defmodule TutorialWeb.EmailHTML do
  use TutorialWeb, :html

  embed_templates "emails/*.html"
  embed_templates "emails/*.text", suffix: "_text"
end

The embed_templates/2 function embeds templates with the specified file extensions. We use it here to embed both HTML and text templates.

Step 4: Create Layout Templates

Now, we need to create layout templates for both HTML and text emails.

Create a new file at lib/tutorial_web/emails/layout.html.heex and add the following content:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta name="viewport" content="width=device-width" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title><%= assigns[:title] %></title>
    <style>
      /* -------------------------------------
          CSS for the template classes
      ------------------------------------- */
    </style>
  </head>
  <body>
    <!-- start preheader -->
    <div class="preheader" style="display: none; max-width: 0; max-height: 0; overflow: hidden; font-size: 1px; line-height: 1px; color: #fff; opacity: 0;">
      <%= assigns[:preheader] %>

    </div>
    <!-- end preheader -->
    <table class="body-wrap">
      <tr>
        <td></td>
        <td class="container" width="800">
          <div class="content">
            <table class="main" width="100%" cellpadding="0" cellspacing="0">
              <tr>
                <td class="content-wrap">
                  <%= @inner_content %>
                </td>
              </tr>
            </table>
            <div class="footer">
              <table width="100%">
                <tr>
                  <td class="content-block">
                    Copyright <%= DateTime.utc_now.year %> Damage Inc. All rights reserved.<br>
                    Damage Inc<br>
                    26955 Fritsch Bridge<br>
                    54933-7180 San Fransisco
                  </td>
                </tr>
              </table>
            </div>
          </div>
        </td>
        <td></td>
      </tr>
    </table>
  </body>
</html>

This is the HTML layout for your emails. It includes some basic CSS for email formatting.

Next, create a text version of the layout. Create a new file at lib/tutorial_web/emails/layout.text.heex and add the following content:

<%= assigns[:title] %>

================================

<%= @inner_content %>

================================
Copyright <%= DateTime.utc_now.year %> Damage Inc. All rights reserved.
Damage Inc
26955 Fritsch Bridge
54933-7180 San Fransisco

Step 5: Use the Updated Mailer

Finally, you can use the updated mailer in a notifier module to send an HTML email with inlined CSS. Here is an example:

defmodule Tutorial.ExampleLoginNotifier do
  import Swoosh.Email
  import Tutorial.Mailer, only: [base_email: 0, premail: 1, render_body: 3]

  def login_link(%{email: email, url: url}) do
    base_email()
    |> subject("Login token")
    |> to(email)
    |> render_body("login_link.html", title: "Login token", url: url)
    |> premail()
  end
end

In this example, we define a login_link/1 function that creates a new email, sets the subject and recipient, renders the body using the "login_link.html" template, and then inlines the CSS and generates a text version using premail/1.

And that's it! You now know how to implement HTML emails in Phoenix using Swoosh and Premailex.

Related Tutorials

Published 24 Nov - 2022

Send Tailwind styled Emails with Phoenix and Swoosh

This tutorial came up as a way to style an email sendout with Tailwind classes.The reason I want to do this is that styling emails is a pain and I u..