Check out our new series - Moving to Elixir

×

#15: Sending Email with Bamboo Part 2

Elixir 1.4

Phoenix 1.2.1

Bamboo 0.8

Source code on GitHub

Part 1 source code on GitHub


In episode 14 we set up Bamboo to work in our movie app and sent a simple email when a movie was deleted.

In this episode let’s explore how we can compose and send multiple emails with HTML templates.

Now since this is a phoenix app, we can take use the Bamboo.Phoenix module to easily render layouts and templates.

Before we add our templates, let’s go to “lib/email.ex” and make some updates.

The first thing we’ll do is update the module to use Bamboo.Phoenix … with an EmailView that we’ll create.

In our “movie_removal_email” function, we’re passing our data in as arguments into the “new_email” function.

Another way to write this is by piping our data through functions.

Now let’s declare what layout we want to use with “put_html_layout” we’ll use our “LayoutView” and a layout that we’ll create called “email”.

Then we’ll define a template we want to render for this email. Let’s call it “movie_removal.html”.

One note about rendering templates with Bamboo, using a string will only render the template you specificied, in this case HTML.

If we wanted to use both HTML and text templates we would use the name of the template as an atom.

And since we’re now using a template we can remove the “text_body” and “html_body” functions.

In our email, let’s display some information about the movie.

To do that we need to assign the movie, so let’s add another function “assign” and we’ll use it to assign our movie that’s being removed to the variable “movie” in our template.

Then we’ll need to update our function to accept “movie” in as an argument.

lib/teacher/email.ex

defmodule Teacher.Email do
  use Bamboo.Phoenix, view: Teacher.EmailView

  def movie_removal_email(movie) do
    new_email
    |> from("no-reply@elixircasts.io")
    |> to("hello@elixircasts.io")
    |> put_html_layout({Teacher.LayoutView, "email.html"})
    |> subject("A movie was removed")
    |> assign(:movie, movie)
    |> render("movie_removal.html")
  end
end

Now let’s create the templates that we need. We’ll start with our new email layout by creating a new file in our layout directory named “email.html.eex”.

Then we’ll paste in a simple HTML document that renders our template.

Template path: web/templates/layout/email.html.eex

<html>
  <head>
    <link rel="stylesheet" href="<%= static_url(Teacher.Endpoint, "/css/style.css") %>">
  </head>
  <body>
    <div class="container">
      <%= render @view_module, @view_template, assigns %>
    </div>
  </body>
</html>

And then we’ll create our template. First we need to create a new “email” directory and inside it we’ll create a file named “movie_removal.html.eex”.

And we’ll fill it out with the content we want to send in our email. In this case we can access the “movie” variable we assigned and display different attributes. Here we’re displaying our movie’s title and the year it was released.

Template path: web/templates/email/movie_removal.html.eex

<p><%= @movie.title %> which was released in <%= @movie.year %> was removed.</p>

Now we need to create the view for our email templates to use. We’ll create a new file in our views directory and call it “email_view” and inside we’ll fill it out with a basic view module.

web/views/email_view.ex

defmodule Teacher.EmailView do
  use Teacher.Web, :view
end

Now let’s open our “movie_controller” and since we updated our “movie_removal_function” to accept a movie as an argument, we’ll pass our movie into the function.


defmodule Teacher.MovieController do
  use Teacher.Web, :controller
  …
  defp send_removal_notification(movie) do
    Email.movie_removal_email(movie) |> Mailer.deliver_later()
  end

  def delete(conn, %{"id" => id}) do
    …
    Repo.delete!(movie)
    send_removal_notification(movie)
    …
  end

Great, now we can go to the command line and start our server.

$ mix phoenix.server

Now let’s delete a an email and if we go to our “/sent_emails” route we see the new emails is displayed along with the movie data that we told it to show.

Our removal email looks good, but now let’s send one when a movie is added to our site.

Let’s go back to “email.ex” and since sending these two emails will have a lot of common functionality, let’s move the common parts into a function both can share.

Both emails will need to use the “new_email”, “from”, “to”, and “put_html_layout” functions so let’s grab these and then let’s create a new private function called “base email” that we can move them into.

Then let’s update our “movie_removal_email” function to first call “base_email”.

Great now we can build our email when a movie is added.

Let’s create a new function “movie_creation_email” and then we’ll call “base_email”. We’ll give it a subject and assign it the movie that was just added and then we’ll render a different template we’ll call “movie_creation.html”.

lib/teacher/email.ex

defmodule Teacher.Email do
  use Bamboo.Phoenix, view: Teacher.EmailView

  def movie_removal_email(movie) do
    base_email
    |> subject("A movie was removed")
    |> assign(:movie, movie)
    |> render("movie_removal.html")
  end

  def movie_creation_email(movie) do
    base_email
    |> subject("A movie was added")
    |> assign(:movie, movie)
    |> render("movie_creation.html")
  end

  defp base_email do
    new_email
    |> from("no-reply@elixircasts.io")
    |> to("hello@elixircasts.io")
    |> put_html_layout({Teacher.LayoutView, "email.html"})
  end
end

Now let’s create our template. We’ll go to our templates slash email directory and create a new file named “movie_creation.html.eex”. And then we’ll add the content for our email. Since we’re assigning our movie, let’s display its title here.

Template path: web/templates/email/movie_creation.html.eex

<p><%= @movie.title %> was added to the site.</p>

Then we can go to “movie_controller.ex” and we’ll add a new private function named “send_creation_notification” that will be in charge of building and sending our email.

We’ll build our email by piping our movie into our Email.movie_creation_email function. Then we’ll send it by piping the result into Mailer.deliver_later.

Now we can call it in our create function right after the movie is inserted successfully into the database.

web/controllers/movie_controller.ex

defmodule Teacher.MovieController do
  use Teacher.Web, :controller
  …
  defp send_creation_notification(movie) do
    movie
    |> Email.movie_creation_email()
    |> Mailer.deliver_later()
  end

  def create(conn, %{"movie" => movie_params}) do
    changeset = Movie.changeset(%Movie{}, movie_params)

    case Repo.insert(changeset) do
      {:ok, movie} ->
        send_creation_notification(movie)
        …
      end
  end
  …
end

Since we made changes to “email.ex” we’ll need to restart the server since changes to “/lib” are not automatically recompiled.

$ mix phoenix.server

Now we can add a movie to the site.

And if we look at our development logs, we can see our email was triggered.

So let’s go to our “/sent_emails” route and great we see our email is displayed with all the correct information.