See all articles

Sending Email from a Phoenix App

A step-by-step guide to sending emails from a Phoenix web app (Elixir). If you don’t have any on hand - don’t worry, we will show you also how to create a Phoenix app, then how to write your first mailer to be sent from it and how to preview emails during development. Let’s go!

Phoenix is gaining traction as a highly useful web application framework, built on the Elixir language. Today we want to take you through a simple example where you can send email directly from a Phoenix app. It’s all part of learning to embrace new technologies designed to make your web applications easier and more efficient to build.

For sending emails from a Phoenix web app we will use the bamboo library. First, let’s look at the configuration process and walk through creating a simple Phoenix web app - in case you don’t have one you can follow our guide with code snippets.


Let’s first up create a Phoenix app:

1 2 3 4 mix local.hex mix archive.install mix email_sender cd email_sender

If you are creating an app for the first time, make sure your database config is correct - check `config/{dev.exs|test.exs}` files and look for the `config :email_sender, EmailSender.Repo` line.

The next thing to do is to generate a mail resource for our app:

1 mix phx.gen.html Mailing Message messages to:string subject:string body:string

Let’s then add our new resource’s endpoint to the router so we can perform basic CRUD operations (Create, Read, Update, Delete) on it:

1 2 3 4 5 6 7 8 # lib/email_sender_web/router.ex defmodule EmailSenderWeb.Router do ... scope "/", EmailSenderWeb do ... resources "/messages", MessageController end end

and migrate the database:

1 mix ecto.migrate

This way when you open `/messages` you will see a simple interface to manage your message records.

In order to send emails we need to add the `bamboo` library to our dependencies in the `mix.exs` file:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 # mix.exs def application do [ mod: {EmailSender.Application, []}, extra_applications: [:logger, :bamboo, :bamboo_smtp, :runtime_tools] ] end def deps do [ ... {:bamboo, "~> 0.8"}, {:bamboo_smtp, "~> 1.4.0"} ] end

Now we can install the new dependencies:

1 mix deps.get

We also need to configure our application to use the `Bamboo.LocalAdapter` adapter for sending emails in our `dev` environment and the `Bamboo.TestAdapter` adapter in our `test` environment:

1 2 3 4 5 6 7 8 # config/dev.exs ... config :email_sender, EmailSender.Mailer, adapter: Bamboo.LocalAdapter # config/test.exs ... config :email_sender, EmailSender.Mailer, adapter: Bamboo.TestAdapter

For our production config you can use the `Bamboo.SMTPAdapter` adapter, but remember about using environment variables for specifying SMTP credentials:

1 2 3 4 5 6 7 8 9 10 11 12 # config/prod.exs ... config :email_sender, EmailExample.Mailer, adapter: Bamboo.SMTPAdapter, server: System.get_env("SMTP_SERVER") , port: 1025, username: System.get_env("SMTP_USERNAME"), password: System.get_env("SMTP_PASSWORD"), tls: :if_available, # can be `:always` or `:never` ssl: false, # can be `true` retries: 1 ...

Now we need to create our mailer module, that we previously specified in the config files:

1 2 3 4 # lib/email_sender/mailer.ex defmodule EmailSender.Mailer do use Bamboo.Mailer, otp_app: :email_sender end

First mailer

We now have a Phoenix web app ready, so we can move on to writing our first mailer. Let's start by writing a test first so we can progress in the proven style of TDD (Test Driven Development):

1 2 3 4 5 6 7 8 9 10 11 12 13 # test/email_sender/email_test.exs defmodule EmailSender.EmailTest do use ExUnit.Case use Bamboo.Test test "create" do email = EmailSender.Email.create("", "test subject", "<h1>Hello!</h1>") assert == "" assert email.subject == "test subject" assert email.html_body =~ "Hello!" end end

Since an email is just a struct it’s easy to test it. We can check the email’s content using the `=~` operator, which compares if the `html_body` contains the text specified on the right of the operator.

If we run our tests with the `mix test` command, we should see an error similar to the one below:

1 ** (UndefinedFunctionError) function EmailSender.Email.create/3 is undefined (module EmailSender.Email is not available)

Let’s make the test pass by implementing a simple mailer:

1 2 3 4 5 6 7 8 9 10 11 # lib/email_sender/email.ex defmodule EmailSender.Email do import Bamboo.Email def create(to, subject, body) do new_email() |> to(to) |> from("") |> subject(subject) |> html_body(body) end end

Now, in order to send a message from the app, we need to call the new `create` method on the `E``mailSender``.Email` module and pass it to our `Mailer`:

1 2 email = EmailSender.Email.create("", "Hello mail", "<h1>Hi Joe</h1>") EmailSender.Mailer.deliver_now(email) # or EmailSender.Mailer.deliver_later(email)

Let’s run our tests again to ensure we made our test pass. Did yours pass? Ours did - so far so good.

Now we need to connect our `Email` with the `Message` record in the controller, so an email will be sent whenever a new message is created in the system.

Like before, we will start with test-first approach.

Let’s add a new test to the `EmailSenderWeb.MessageControllerTest` module:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # test/email_sender_web/controllers/message_controller_test.exs defmodule EmailSenderWeb.MessageControllerTest do use EmailSenderWeb.ConnCase use Bamboo.Test ... describe "create message" do ... test "email is sent when data is valid", %{conn: conn} do post conn, message_path(conn, :create), message: @create_attrs assert_delivered_email EmailSender.Email.create(@create_attrs[:to], @create_attrs[:subject], @create_attrs[:body]) end ... end ... end

If we run our tests again, we will see a failing test:

1 2 3 4 5 6 7 8 There were 0 emails delivered to this process. If you expected an email to be sent, try these ideas: 1) Make sure you call deliver_now/1 or deliver_later/1 to deliver the email 2) Make sure you are using the Bamboo.TestAdapter 3) Use shared mode with Bamboo.Test. This will allow Bamboo.Test to work across processes: use Bamboo.Test, shared: :true 4) If you are writing an acceptance test through a headless browser, use shared mode as described in option 3.

Let’s make it green by updating the `create` action in the controller:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # lib/email_sender_web/controllers/message_controller.ex defmodule EmailSenderWeb.MessageController do ... def create(conn, %{"message" => message_params}) do case Mailing.create_message(message_params) do {:ok, message} -> email = EmailSender.Email.create(, message.subject, message.body) EmailSender.Mailer.deliver_now(email) conn |> put_flash(:info, "Message created successfully.") |> redirect(to: message_path(conn, :show, message)) {:error, %Ecto.Changeset{} = changeset} -> render(conn, "new.html", changeset: changeset) end end ... end

Now we should see all our tests pass again.

Email preview during development

Let’s not stop there, but instead broaden our options - by adding a feature that previews sent emails while developing the app, by adding an additional route to our router:

1 2 3 4 5 6 7 # lib/email_sender_web/router.ex defmodule EmailSenderWeb.Router do ... if Mix.env == :dev do forward "/sent_emails", Bamboo.EmailPreviewPlug end end

When we create a new message, the new email should appear in the `/sent_emails` mailbox:

Note: Remember that `Bamboo.LocalAdapter` must be used to make the email appear in the mailbox.

And that’s it! Your first mailer from a Phoenix web app is ready to be sent. Pretty easy, right? If you need other IT solutions or help in creating mobile or web apps - contact us! We’d love to see Elixir and Phoenix in more projects and are keen to help out!

Read Similar Articles