loading...

Sending email with Elixir and Phoenix 1.5

joseph_lozano profile image Joseph Lozano ・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

live/session_live/new.ex
live/session_live/new.html.eex

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" %>
<div>
  <%= label f, :email %>
  <%= email_input f, :email %>
  <%= error_tag f, :email %>
</div>

<div>
  <%= submit "Save", phx_disable_with: "Saving..." %>
</div>
</form>

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}
  end

  def handle_event("login", %{"login" => %{"email" => email}}, socket) do
    socket =
      case Accounts.get_user_by(email: email) do
        nil ->
          socket
          |> 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
          Accounts.deliver_login_email(user)
            socket
            |> put_flash(:info, "Login email sent")
            |> push_redirect(to: socket.assigns.return_to)
          end
      end

    {:noreply, socket}
  end
end

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)
end

def deliver_login_email(%User{} = _user) do
  require Logger
  Logger.warn "deliver_login_email not implemented"
  :ok
end

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"},
  ...
end

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

config :feenix, Feenix.Mailer, adapter: Swoosh.Adapters.Local

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
end

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"
end

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
    new()
    |> to(user.email)
    |> from({"Feenix", "login@localhost"})
    |> subject("Your login link")
    |> render_body("login_email.html", %{})
  end
end

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:

<div>
  <a href="localhost:4000" />
<div>

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
  user
  |> LoginEmail()
  |> Mailer.deliver()
end

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!

Posted on by:

joseph_lozano profile

Joseph Lozano

@joseph_lozano

LA -> NYC. Currently looking for new opportunities.

Discussion

markdown guide