Current section
Files
Jump to
Current section
Files
lib/bamboo_phoenix.ex
defmodule Bamboo.Phoenix do @moduledoc """ Render emails with Phoenix templates and layouts. This module allows rendering emails with Phoenix layouts and views. Pass an atom (e.g. `:welcome_email`) as the template name to render both HTML and plain text emails. Use a string if you only want to render one type, e.g. `"welcome_email.text"` or `"welcome_email.html"`. ## Examples _Set the text and HTML layout for an email._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def welcome_email do new_email() |> put_text_layout({MyAppWeb.LayoutView, "email.text"}) |> put_html_layout({MyAppWeb.LayoutView, "email.html"}) |> render(:welcome) # Pass atom to render html AND plain text templates end end _Set both the text and HTML layout at the same time for an email._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def welcome_email do new_email() |> put_layout({MyAppWeb.LayoutView, :email}) |> render(:welcome) end end _Render both text and html emails without layouts._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def welcome_email do new_email() |> render(:welcome) end end _Make assigns available to a template._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def welcome_email(user) do new_email() |> assign(:user, user) |> render(:welcome) end end _Make assigns available to a template during render call._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def welcome_email(user) do new_email() |> put_html_layout({MyAppWeb.LayoutView, "email.html"}) |> render(:welcome, user: user) end end _Render an email by passing the template string to render._ defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def html_email do new_email |> render("html_email.html") end def text_email do new_email |> render("text_email.text") end end ## HTML Layout Example # my_app_web/email.ex defmodule MyApp.Email do use Bamboo.Phoenix, view: MyAppWeb.EmailView def sign_in_email(person) do base_email() |> to(person) |> subject("Your Sign In Link") |> assign(:person, person) |> render(:sign_in) end defp base_email do new_email |> from("Rob Ot<robot@changelog.com>") |> put_header("Reply-To", "editors@changelog.com") # This will use the "email.html.eex" file as a layout when rendering html emails. # Plain text emails will not use a layout unless you use `put_text_layout` |> put_html_layout({MyAppWeb.LayoutView, "email.html"}) end end # my_app_web/views/email_view.ex defmodule MyAppWeb.EmailView do use MyAppWeb, :view end # my_app_web/templates/layout/email.html.eex <html> <head> <link rel="stylesheet" href="<%= static_url(MyApp.Endpoint, "/css/email.css") %>"> </head> <body> <%= render @view_module, @view_template, assigns %> </body> </html> # my_app_web/templates/email/sign_in.html.eex <p><%= link "Sign In", to: sign_in_url(MyApp.Endpoint, :create, @person) %></p> # my_app_web/templates/email/sign_in.text.eex # This will not be rendered within a layout because `put_text_layout` was not used. Sign In: <%= sign_in_url(MyApp.Endpoint, :create, @person) %> """ import Bamboo.Email, only: [put_private: 3] defmacro __using__(view: view_module) do verify_phoenix_dep() quote do import Bamboo.Email import Bamboo.Phoenix, except: [render: 3] @doc """ Render an Phoenix template and set the body on the email. Pass an atom as the template name (:welcome_email) to render HTML *and* plain text emails. Use a string if you only want to render one type, e.g. "welcome_email.text" or "welcome_email.html". Scroll to the top for more examples. """ def render(email, template, assigns \\ []) do Bamboo.Phoenix.render_email(unquote(view_module), email, template, assigns) end end end defmacro __using__(opts) do raise ArgumentError, """ expected Bamboo.Phoenix to have a view set, instead got: #{inspect(opts)}. Please set a view e.g. use Bamboo.Phoenix, view: MyAppWeb.MyView """ end defp verify_phoenix_dep do unless Code.ensure_loaded?(Phoenix) do raise "You tried to use Bamboo.Phoenix, but Phoenix module is not loaded. " <> "Please add phoenix to your dependencies." end end @doc """ Render a Phoenix template and set the body on the email. Pass an atom as the template name to render HTML *and* plain text emails, e.g. `:welcome_email`. Use a string if you only want to render one type, e.g. `"welcome_email.text"` or `"welcome_email.html"`. Scroll to the top for more examples. """ def render(_email, _template_name, _assigns) do raise "function implemented for documentation only, please call: use Bamboo.Phoenix" end @doc """ Sets the layout when rendering HTML templates. ## Example def html_email_layout do new_email # Will use MyAppWeb.LayoutView with email.html template when rendering html emails |> put_html_layout({MyAppWeb.LayoutView, "email.html"}) end """ def put_html_layout(email, layout) do email |> put_private(:html_layout, layout) end @doc """ Sets the layout when rendering plain text templates. ## Example def text_email_layout do new_email # Will use MyAppWeb.LayoutView with email.text template when rendering text emails |> put_text_layout({MyAppWeb.LayoutView, "email.text"}) end """ def put_text_layout(email, layout) do email |> put_private(:text_layout, layout) end @doc """ Sets the layout for rendering plain text and HTML templates. ## Example def text_and_html_email_layout do new_email # Will use MyAppWeb.LayoutView with the email.html template for html emails # and MyAppWeb.LayoutView with the email.text template for text emails |> put_layout({MyAppWeb.LayoutView, :email}) end """ def put_layout(email, {layout, template}) do email |> put_text_layout({layout, to_string(template) <> ".text"}) |> put_html_layout({layout, to_string(template) <> ".html"}) end @doc """ Sets an assign for the email. These will be available when rendering the email """ def assign(%{assigns: assigns} = email, key, value) do %{email | assigns: Map.put(assigns, key, value)} end @doc false def render_email(view, email, template, assigns) do email |> put_default_layouts |> merge_assigns(assigns) |> put_view(view) |> put_template(template) |> render end defp put_default_layouts(%{private: private} = email) do private = private |> Map.put_new(:html_layout, false) |> Map.put_new(:text_layout, false) %{email | private: private} end defp merge_assigns(%{assigns: email_assigns} = email, assigns) do assigns = email_assigns |> Map.merge(Enum.into(assigns, %{})) email |> Map.put(:assigns, assigns) end defp put_view(email, view_module) do email |> put_private(:view_module, view_module) end defp put_template(email, view_template) do email |> put_private(:view_template, view_template) end defp render(%{private: %{view_template: template}} = email) when is_atom(template) do render_html_and_text_emails(email) end defp render(email) do render_text_or_html_email(email) end defp render_html_and_text_emails(email) do view_template = Atom.to_string(email.private.view_template) email |> Map.put(:html_body, render_html(email, view_template <> ".html")) |> Map.put(:text_body, render_text(email, view_template <> ".text")) end defp render_text_or_html_email(email) do template = email.private.view_template cond do String.ends_with?(template, ".html") -> email |> Map.put(:html_body, render_html(email, template)) String.ends_with?(template, ".text") -> email |> Map.put(:text_body, render_text(email, template)) true -> raise ArgumentError, """ Template name must end in either ".html" or ".text". Template name was #{ inspect(template) } If you would like to render both and html and text template, use an atom without an extension instead. """ end end defp render_html(email, template) do # Phoenix uses the assigns.layout to determine what layout to use assigns = Map.put(email.assigns, :layout, email.private.html_layout) Phoenix.View.render_to_string( email.private.view_module, template, assigns ) end defp render_text(email, template) do assigns = Map.put(email.assigns, :layout, email.private.text_layout) Phoenix.View.render_to_string( email.private.view_module, template, assigns ) endend