Overview

Sending emails is something probably almost all apps will have to be able to do at some point in time. So how do we go about doing such a thing with Phoenix and what libraries can we use for this, if any.

The mindset in the Elixir ecosystem is not to make use of a library right off the bat. Since setting up and sending emails is a common task that takes quite some effort to take from scratch, a library is very welcome. Luckily we have a few very good options in Elixir land. As a side note, if you want to see what options you have for libraries in certain categories, Elixir libhunt is a very nice place to investigate the stats for some similar libraries. It is like the Ruby Toolbox, if you are coming from ruby. You can check the comparison of some popular email sending libraries here.

We are not going for the number one in this case(gen_smtp) as, although it looks really nice, it’s a bit too low-level OTP for our use case. The two main ones that work well with Phoenix are Swoosh and Bamboo. They are very similar and today I’m going with the one created by Thoughtbot, Bamboo, mainly because I’ve used that before. However, there is little difference between the two and both actually support most transactional email providers out of the box. For the sake of this tutorial, I’ll use a free account setup with sendgrid. Sendgrid provides a generous trial period and also has a pretty extensive free number of emails you can send every month. So more than enough for some simple experiments.

Ok, so let’s get this show on the road. My main goal for this post is to let my app send a welcome mail for new signups of my app. The app is a student management app for music teachers, and there is a signup form that is built using LiveView that is used to sign up either as a teacher or as a student. The authentication mechanism is built using Pow.

Starting with project

Let’s start by adding Bamboo to our mix project.

# mix.exs
...
  def deps do
    [{:bamboo, "~> 1.3"}]
  end
...

And make sure we pull it in:

$ mix deps.get

We’ll need a mailer module for sending the actual emails and one or more email modules for creating emails to be sent. We’ll start with the mailer module:

defmodule StudentManager.Mailer do
  use Bamboo.Mailer, otp_app: :student_manager
end

And here is an example email module for crafting your emails. We create a base_email/0 function for some general options and two functions for a teacher and student welcome email respectively. This should all be fairly self-explanatory.

defmodule StudentManager.Email do
  use Bamboo.Phoenix, view: StudentManagerWeb.EmailView

  def welcome_email(%{roles: ["teacher"]} = user) do
    base_email()
    |> to(user.email)
    |> subject("Welcome to StudMan App")
    |> assign(:user, user)
    |> render("welcome_teacher.html")
  end

  def welcome_email(%{roles: ["student"]} = user) do
    base_email()
    |> to(user.email)
    |> subject("Welcome to StudMan App")
    |> assign(:user, user)
    |> render("welcome_student.html")
  end

  defp base_email() do
    new_email()
    |> from("support@studman.nl")
  end
end

If you look closely you’ll notice I’m using pattern matching to send out a student or teacher mail based on the role that gets assigned when they sign up. I’m also using Bamboo.Phoenix here in order to take advantage of the template engine included in Phoenix. This really makes it a breeze to create HTML emails. Normally you would configure this to also send out a plain text version, but for now, we’ll just create the HTML version.

One last thing we need to do is add some configuration for our mailer in config/config.exs.

use Mix.Config
config :student_manager, StudentManager.Mailer,
  adapter: Bamboo.SendGridAdapter,
  api_key: "my_sendgrid_api_key"

So all very straightforward stuff. Cool! Just following the guides from Bamboo should get you up and running quickly. Because of that, it’s easy to add our email in the signup flow like this:

def handle_event("save", %{"user" => user_params}, socket) do
    case create_user(socket, user_params) do
      {:ok, user} ->
        Email.welcome_email(user) |> Mailer.deliver_later()
        
        {:stop,
         socket
         |> put_flash(:info, "user created")
         |> redirect(to: "/")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, changeset: changeset)}
    end
  end

The fact that I’m using LiveView here makes it pretty easy to hook into the registration process. If we were using the regular sign-up process of Pow we would need to do a little more work to get this email delivered. Let’s see how that would work. Luckily the author of Pow also has us covered. The library provides callback hooks which you can use to hook into the sign-up process in certain spots. In our case, the after_create hook for the registration_controller is the hook we want to grab.

Lets’s create a file for our controller_callbacks: student_manager_web/pow/callbacks_controller.ex with the following contents:

defmodule StudentManagerWeb.Pow.CallbacksController do
  @moduledoc false

  use Pow.Extension.Phoenix.ControllerCallbacks.Base

  def before_respond(Pow.Phoenix.RegistrationController, :create, {:ok, user, conn}, _config) do
    user
    |> StudentManager.Email.welcome_email()
    |> StudentManager.Mailer.deliver_later()

    {:ok, user, conn}
  end
end

Now to be able to trigger these callbacks, we’ll need to add this in our pow config in config/config.exs

# Pow Configuration
config :student_manager, :pow,
  user: StudentManager.Accounts.User,
  repo: StudentManager.Repo,
  web_module: StudentManagerWeb,
  controller_callbacks: StudentManagerWeb.Pow.ControllerCallbacks

That should do it for sending out a welcoming email on signup. Hope this was helpful!

Categories: #Elixir Tags: #Elixir #Learning #Bamboo