lib/pow/phoenix/mailer/mail.ex

defmodule Pow.Phoenix.Mailer.Mail do
  @moduledoc """
  Module that renders html and text version of e-mails.

  ## Custom layout

  By default no layout is used to render the templates. You can set
  `:pow_mailer_layout` in `conn.private` the same way as you set
  `:phoenix_layout` to render a layout for the template.

  This is how you can set the mailer layout:

      defmodule MyAppWeb.Router do
        # ...

        pipeline :pow_email_layout do
          plug :put_pow_mailer_layout, {MyAppWeb.LayoutView, :email}
        end

        # ...

        scope "/" do
          pipe_through [:browser, :pow_email_layout]

          pow_routes()
        end

        # ...

        defp put_pow_mailer_layout(conn, layout), do: put_private(conn, :pow_mailer_layout, layout)
      end

  You can use atom or binary as template. If a binary is used then only format
  that it ends with will be used, e.g. using "email.html" will result in no
  text layout being used.
  """
  alias Plug.Conn
  alias Pow.{Config, Phoenix.ViewHelpers, Plug}

  @type t :: %__MODULE__{}

  defstruct [:user, :subject, :text, :html, :assigns]

  @doc """
  Returns a populated `%Pow.Phoenix.Mailer.Mail{}` map.

  If the configuration has `:web_mailer_module`, it will be used to find the
  template view module to call.
  """
  @spec new(Conn.t(), map(), {module(), atom()}, Keyword.t()) :: t()
  def new(conn, user, {view_module, template}, assigns) do
    config       = Plug.fetch_config(conn)
    web_module   = Config.get(config, :web_mailer_module)
    view_assigns = Keyword.merge([conn: conn, user: user], assigns)
    view_module  = ViewHelpers.build_view_module(view_module, web_module)

    subject = render_subject(view_module, template, view_assigns)
    text    = render(view_module, template, conn, view_assigns, :text)
    html    = render(view_module, template, conn, view_assigns, :html)

    struct(__MODULE__, user: user, subject: subject, text: text, html: html, assigns: assigns)
  end

  defp render_subject(view_module, template, assigns) do
    view_module.subject(template, assigns)
  end

  defp render(view_module, template, conn, assigns, format) do
    view_assigns = prepare_assigns(conn, assigns, format)

    Phoenix.View.render_to_string(view_module, "#{template}.#{format}", view_assigns)
  end

  defp prepare_assigns(conn, assigns, format) do
    layout =
      conn.private
      |> Map.get(:pow_mailer_layout, false)
      |> layout(format)

    assigns
    |> Keyword.put(:layout, layout)
    |> Keyword.merge(assigns)
  end

  defp layout({view, layout}, format) when is_atom(layout), do: {view, "#{layout}.#{format}"}
  defp layout({view, layout}, format) when is_binary(layout) do
    case String.ends_with?(layout, ".#{format}") do
      true  -> {view, layout}
      false -> false
    end
  end
  defp layout(false, _format), do: false
end