See all articles
Sending Email from a Phoenix App

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.

Configuration

Let’s first up create a Phoenix app:

mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
mix phx.new 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:

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:

# lib/email_sender_web/router.ex
defmodule EmailSenderWeb.Router do
  ...
  scope "/", EmailSenderWeb do
    ...
    resources "/messages", MessageController
  end
end

and migrate the database:

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:

# 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:

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:

# 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:

# 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:

# 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):

# test/email_sender/email_test.exs
defmodule EmailSender.EmailTest do
  use ExUnit.Case
  use Bamboo.Test
  test "create" do
    email = EmailSender.Email.create("user@test.com",
                                     "test subject",
                                     "<h1>Hello!</h1>")
    assert email.to == "user@test.com"
    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:

** (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:

# lib/email_sender/email.ex
defmodule EmailSender.Email do
  import Bamboo.Email
  def create(to, subject, body) do
    new_email()
    |> to(to)
    |> from("me@example.com")
    |> 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`:

email = EmailSender.Email.create("jdoe@mail.com", "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:

# 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:

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:

# 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.to, 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:

# 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