Tutorial
Implementing HTML Emails in Phoenix with Swoosh and Premailex
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.