DEV Community


Sending email with Elixir and Phoenix 1.5

Joseph Lozano
Currently looking for new opportunities.
・3 min read

This is part 2 in a series. In part 1 we created the users model using a generator. In this section, we'll be building the login flow by hand.

Let's start by creating the following files

Enter fullscreen mode Exit fullscreen mode

Let's start with our template, new.html.eex

# lib/feenix_web/live/session_live/new.html.eex
<h1>Log in</h1>
<%= f = form_for :login, "#",
id: "login-form", phx_submit: "login" %>
  <%= label f, :email %>
  <%= email_input f, :email %>
  <%= error_tag f, :email %>

  <%= submit "Save", phx_disable_with: "Saving..." %>
Enter fullscreen mode Exit fullscreen mode

This is a form very similar to our New User form, but we only need the email.

Next, let's do the LiveView

# lib/feenix_web/live/session_live/new.ex
defmodule PokerWithFriendsWeb.SessionLive.New do
  use PokerWithFriendsWeb, :live_view

  alias PokerWithFriends.Accounts
  alias PokerWithFriends.Accounts.User

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}

  def handle_event("login", %{"login" => %{"email" => email}}, socket) do
    socket =
      case Accounts.get_user_by(email: email) do
        nil ->
          |> put_flash(:error, "You need to create an account first")
          |> push_redirect(to: Routes.user_new_path(socket, :new))

        user ->
          # TODO: error handling for email
            |> put_flash(:info, "Login email sent")
            |> push_redirect(to: socket.assigns.return_to)

    {:noreply, socket}
Enter fullscreen mode Exit fullscreen mode

This isn't as bad as it first looks

mount is doing nothing special

handle_event/3 is receiving the login event with the email. It then checks if a user with that email exists. If not, it forwards to the User/new page, if so, we call Accounts.deliver_login_email(user), leaving a note for ourselves to handle errors. In both cases, we add a helpful flash message

Next, let's create those functions in our Account module.

# lib/feenix/accounts.ex

def get_user_by(params) do
  Repo.get_by(User, params)

def deliver_login_email(%User{} = _user) do
  require Logger
  Logger.warn "deliver_login_email not implemented"
Enter fullscreen mode Exit fullscreen mode

Ok, we need to implement our login email. For that, I am going to be using swoosh

Add swoosh and phoenix_swoosh to your mix deps

# mix.exs
defp deps do
  {:swoosh, "~> 0.25"},
  {:phoenix_swoosh, "~> 0.2"},
Enter fullscreen mode Exit fullscreen mode

In the our config, let's set swoosh to use the local adapter

config :feenix, Feenix.Mailer, adapter: Swoosh.Adapters.Local
Enter fullscreen mode Exit fullscreen mode

Then, we need to define a mailer. I'll put it in the top level of feenix_web

# lib/feenix_web/mailer.ex
defmodule Feenix.Mailer do
  @moduledoc false
  use Swoosh.Mailer, otp_app: :feenix
Enter fullscreen mode Exit fullscreen mode

In our router, there is a line if Mix.env() in [:dev, :test] do which has the live_dashboard route. Inside that if, let's add a new dev scope for our mailbox

scope "/dev" do
  pipe_through [:browser]
  forward "/mailbox", Plug.Swoosh.MailboxPreview, base_path: "/dev/mailbox"
Enter fullscreen mode Exit fullscreen mode

Next, we need to create our emails. I'll do this in a new feenix_web/emails folder

# lib/feenix_web/emails/login_email.ex
defmodule FeenixWeb.LoginEmail do
  @moduledoc false
  use Phoenix.Swoosh,
    view: FeenixWeb.EmailView,
    layout: {FeenixWeb.LayoutView, :email}

  def login_email(user) do
    |> to(
    |> from({"Feenix", "login@localhost"})
    |> subject("Your login link")
    |> render_body("login_email.html", %{})
Enter fullscreen mode Exit fullscreen mode

And as you can probably guess from looking at the code, let's add a login_email.html.eex template.

Here is your login link:

  <a href="localhost:4000" />
Enter fullscreen mode Exit fullscreen mode

We haven't created our token yet, but this is a great start.

Now, let's head back to our accounts context, and finish the deliver_login_email function.

# lib/feenix/accounts.ex
alias FeenixWeb.{LoginEmail, Mailer}
def deliver_login_email(%User{} = user) do
  |> LoginEmail()
  |> Mailer.deliver()
Enter fullscreen mode Exit fullscreen mode

Now, let's head over to the /dev/emails endpoint we set up and verify that we got the email!

In part 3, we will create the tokens that will actually allow us to login. Until then, happy coding!

Discussion (0)

Forem Open with the Forem app