Lewis Cianci I'm a passionate mobile-first developer, and I've been making apps with Flutter since it first released. I also use ASP.NET 5 for web. Given the chance, I'll talk to you for far too long about why I love Flutter so much.

Sending emails in Phoenix using Swoosh

7 min read 2150 110

Sending Emails In Phoenix Using Swoosh

Phoenix is an excellent web framework that has been making waves in the development community. It’s fast, reliable, and scalable, which are all key factors in determining the success of any framework. But what really sets Phoenix apart is its unique approach to handling requests and its fail-safety mechanism, making it a fantastic choice for mission-critical applications that require high availability. In other words, Phoenix is a solid choice for developers who want a framework that can keep up with the demands of modern web development.

Most websites today exist because of their ability to engage with the average user. A common method to lead this engagement is through email. Fortunately, we can use the Phoenix.Swoosh library to send engaging emails to our customers.

In this article, we’ll pretend we’re setting up a new business called “The Cookie Shop.” Our business isn’t ready just yet, but people can indicate their interest by leaving their details. After doing so, they will receive an email confirming that they have entered their details correctly. It’s a simple example, but a great candidate to review if you are getting started with Phoenix Swoosh to send emails.

Jump ahead:

Setting up our Phoenix project

Setting up our Phoenix project (assuming we already have our environment set up), is as easy as typing the following into the terminal. Normally, Phoenix would use a Postgres database, but to get set up quicker in our environment, we’ll use an SQLite database. That’s as simple as specifying the database to use via the database argument, like so. Because it’s a sample project, we’ll just call it hello:

 mix phx.new hello --database sqlite3

We’ll also take this time to install the phoenix_swoosh dependency. Add the dependency to your mix.exs file. After adding it, it should look like this:

  # Specifies your project dependencies.
  # Type `mix help deps` for examples and options.
  defp deps do
      {:phoenix, "~> 1.6.15"},
      {:phoenix_ecto, "~> 4.4"},
      {:ecto_sql, "~> 3.6"},
      {:ecto_sqlite3, ">= 0.0.0"},
      {:phoenix_html, "~> 3.0"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_view, "~> 0.17.5"},
      {:floki, ">= 0.30.0", only: :test},
      {:phoenix_live_dashboard, "~> 0.6"},
      {:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
      {:swoosh, "~> 1.3"},
      {:telemetry_metrics, "~> 0.6"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.18"},
      {:jason, "~> 1.2"},
      {:plug_cowboy, "~> 2.5"},
      {:phoenix_swoosh, "~> 1.0"}, # < our added package

A note on email styling

When it comes to sending emails to our customers, we have to think about why we’re sending emails in the first place. Traditionally, emails are sent when a user has signed up to a list, and that’s what we’ll be doing in this example.

Why do we define templates in the first place? It’s true that you can send plain text emails to people, with no styling at all, but they would look very dry and boring and would be easily overlooked. Plus, with the receiving mail client not having any style information, there’s a chance that it could choose a very basic font for our email. In our case, the email could look like this:

We're better than friends.
We're cookie friends.

Hey there,

What can we say. We're super excited to have you here, and we intend to reward you handsomely.

When our cookies are ready, you'll be the first to know.

Cookie Team

This email looks drab. It wouldn’t excite anyone. However, with some basic styling, it could become something like this:

We’re better than friends.

We’re cookie friends.

Hey there, customer_name,
What can we say. We’re super excited to have you here, and we intend to reward you handsomely.

When our cookies are ready, you’ll be the first to know.
Cookie Team

There’s a few things to note about this updated email:

  1. Styling. It’s not just plain text. Immediately, the visual appeal of the email is improved
  2. Addressing the user by their name. In this case, we’re using “customer_name” where the users’ name would be. Using the name given at signup makes the email more engaging

So, the first step on this journey (after setting up dependencies, like we did above) is to define a template. Let’s do that now.

Setting up an email template

Within our templates directory (lib/hello_web/templates), we want to create an emails folder, and then a welcome folder within that folder. In the end, the complete path would be lib/hello_web/templates/emails/welcome. For our small example, this may seem like overkill, but in larger applications, splitting our emails out by their topic will lead to a more maintainable app in the long run.

Within the welcome folder, create a welcome.html.eex file. This will be an HTML file with Embedded Elixir, with the following contents:

<h1>We're better than friends.</h1>
<h2>We're cookie friends</h2>
<p>Hey there, <%= @name %>,</p>
<p>What can we say. We're super excited to have you here, and we intend to reward you <i>handsomely</i>.</p>
<p>When our cookies are ready, you'll be the first to know</p>
<p>Best, </p>
<p>Cookie Team</p>

As we can see, this is a basic HTML file with simple formatting. However, it also receives the parameter name. We use this parameter to directly address the user, and it’s easy to see how we could use other parameters within our email.

With our template set up, let’s leverage Phoenix.Swoosh to populate the template and then send the email. To do this, create a useremail.ex file within the lib directory. In this file, we’ll tell Swoosh how to construct the email, and also the template that should be used. The contents of this file will be as follows:

defmodule Hello.UserEmail do
  use Phoenix.Swoosh,
  template_root: "lib/hello_web/templates/emails",
  template_path: "welcome"
  def welcome(user) do
    |> to({user.name, user.email})
    |> from({"The Cookie Shop", "[email protected]"})
    |> subject("#{user.name}, you've made the list")
    |> render_body("welcome.html", %{name: user.name})

Let’s break down what’s happening here, so we can follow how our variables are being passed through to the HTML template.

The welcome function accepts the user parameter. We supply these values via a tuple in the “to” field. We also use string interpolation to supply the users’ name to be used in the subject field. Finally, we render the body of our email via the welcome.html file.

In my case, the email generation would only work if I explicitly specified the template_root and template_path, so make sure to set these to the path where the email template resides.

Setting up the controller

Now, we need to configure the route that our app should use to send this email. Emails are normally sent in response to an HTTP POST request, which contains the details we need to send an email. To capture the field details in our POST request, our page_controller.ex file would look like this:

defmodule HelloWeb.PageController do
  use HelloWeb, :controller
  alias Hello.UserEmail
  alias Hello.Mailer
  def index(conn, _params) do
    render(conn, "index.html")
  def signup(conn, params) do
    email = Hello.UserEmail.welcome(%{name: params["name"], email: params["email"]})
    render(conn, "thanks.html")

In this controller, we’ve done the following:

  1. Added a reference to UserEmail (the file that contains our email generation logic) and Mailer (which is responsible for sending the email)
  2. Added the signup function, which achieves the following:
    1. Creates an email object from the result of the welcome function in UserEmail. We pass the name and email parameters into the welcome function
    2. With the generated mail object, use the Mailer.deliver(email) function to schedule the sending of this email
  3. Render the thanks.html page to indicate that the details have been received successfully

Creating the HTML pages

The next thing we need to do is create two pages. The first will be the landing page that the customer will see when they navigate to our site, and the second will thank them for registering.

To set up our landing page, we’ll need to add some new fields to our index.html so it accepts the name and email of the user. To do this, modify the index.html.heex file to have the following contents:

<div class="container">
  <h1>Get Ready for the Best Cookies Ever!</h1>
  <p>We're excited to announce the opening of our new cookie business! Our cookies are baked with love and care using only the finest ingredients, and we can't wait to share them with you.</p>
  <p>Be the first to know when we open our doors by signing up below.</p>
  <form action="#" method="post">
    <input name="name" placeholder="Your name" required>
    <input type="email" name="email" placeholder="Your email address">
    <input type="submit" value="Sign up">
    <input type="hidden" value={csrf_token_value()} name="_csrf_token" />

This will create a simple form with the fields that we require. It will also include a hidden field, which will contain the CSRF token, which is important when preventing Cross Site Request Forging attacks.

The second page we will create will be the page that thanks the user for signing up. We’ll call this page thanks.html.heex, and it will be adjacent to our index page, which is located in the page folder. In this case, the contents of this file will be as follows:

<div class="container">
<h1>You're on the list!</h1>
    <p>Keep an eye on your emails for more details 🍪</p>
    <p> - The Cookie Co </p>

Now our app should be set up to send emails. Granted, we haven’t configured any mail servers just yet, but that’s not a problem yet.

Finally, because our app sends a POST request to our PageController, we need to tell the router to accept an HTTP POST request, and where to route the request to. We can update the router.ex to facilitate this:

  scope "/", HelloWeb do
    pipe_through :browser
    get "/", PageController, :index
    post "/", PageController, :signup # < New line added to route POST to correct location

To recap, the logical flow of our app is as follows:

  1. User navigates to our web page
  2. Receives index.html, with the registration form
  3. User fills out the form and hits submit
  4. HTTP POST request is routed to PageController, the signup action (as configured in our router.ex file
    • The signup action executes the welcome function within the UserEmail module, supplying the name and email fields that it parsed from the POST payload
      • Email is generated and sent
  5. thanks.html is rendered, so the user receives a thank you message for signing up

Inspecting generated emails with the Swoosh Mailbox Viewer

Maybe you think it’s a bit weird that we’re generating and sending emails without actually having configured any mail providers. By default, Swoosh sends mail to a development mailbox, which we can see at http://localhost:4000/dev/mailbox. If we navigate there now, we can see some of the emails we have sent:

Swoosh Mailbox Viewer

This is helpful to confirm that our mail is being queued for sending by Swoosh, and also helpful to confirm that our HTML styling is working as expected. Once we’re happy with how the emails look, we can continue to configure Swoosh to use an external mail provider.

From here, you can configure Swoosh to use any number of currently supported email providers to send your email to recipients.

Wrapping up

In our Phoenix project, we may need to send emails to engage with the people who use our site. Fortunately, using Phoenix Swoosh makes this task fairly easy to accomplish. When it comes time to add emails to your Phoenix project, don’t forget to configure the email systems in the prod.exs file, and don’t check secrets in to source control. If you keep those things in mind, you’ll have email sending functionality added to your project in no time 📨

As usual, you can clone a complete copy of the project in this GitHub repository. Enjoy!

Get setup with LogRocket's modern error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Lewis Cianci I'm a passionate mobile-first developer, and I've been making apps with Flutter since it first released. I also use ASP.NET 5 for web. Given the chance, I'll talk to you for far too long about why I love Flutter so much.

Leave a Reply