---
title: Announcing Bamboo, Email with a Functional Twist
teaser: Bamboo is a testable, composable and adapter-based email library for Elixir.
tags: elixir,email
author: Paul Smith
published_on: 2016-04-04
---

[Bamboo](https://github.com/thoughtbot/bamboo) is a testable, composable and
adapter based email library for Elixir.

You might be thinking, what could *possibly* be so cool about sending email in
Elixir? Can it be any better than what I've used in the past?

Hopefully we can show you what the problems were with existing solutions and how
we solved them with features unique to Elixir.

## Those pesky email addresses

Sometimes you only send to one person and all you need is the address. That’s
pretty simple to do

`Bamboo.Email.new_email(to: user.email)`

But often times you want to also give the person’s name, or send to a *list* of
people. Let’s take an example where users can subscribe to a post. When someone
comments on a post, each subscribed user gets an email.

Typically you would need to do something like this:

```elixir
def new_comment_on_post(comment, recipients) do
  recipients = for user <- recipients do
    # Emails can either be a string, or like in this example, a 2 item tuple.
    {user.name, user.email}
  end

  new_email
  |> to(recipients)
  |> subject("New comment on a post you are subscribed to")
  |> text_body("There is a new comment")
  |> html_body("There is a <strong>new comment</strong>")
end
```

You would have to do this every time you send an email to recipients. Instead,
Bamboo lets you define a protocol for [`Bamboo.Formatter`] that will let Bamboo
know how to format your recipients. It would look something like this.

[`Bamboo.Formatter`]: https://hexdocs.pm/bamboo/Bamboo.Formatter.html

```elixir
defimpl Bamboo.Formatter, for: MyApp.User do
  def format_email_address(user, _opts) do
    {user.name, user.email}
  end
end
```

Now when you send an email you can just pass a single user, or list of users and
Bamboo will format them correctly.

```elixir
new_email
|> to(recipients) # Will format the users with the Bamboo.Formatter protocol
```

## Defaults come for free

In a lot of libraries it can be hard to use things together, but with Bamboo and
Elixir's pipe operator, you get defaults for free. Set a default layout, default
from address or whatever you need by using functions and Elixir's much loved
pipe operator.

```elixir
defmodule MyApp.Email do
  import Bamboo.Email

  def welcome_email(recipient) do
    base_email
    |> to(recipient)
    |> subject("Welcome!")
    |> text_body("Welcome to the app")
  end

  defp base_email do
    new_email
    |> from("myapp@thoughtbot.com")
    |> put_header("Reply-To", "support@thoughtbot.com")
  end
end
```

## Testing used to be a pain

Bamboo was designed to make unit and integration testing simple. Because
composing emails is split from actually delivering the emails, unit testing is
very straightforward.

```elixir
defmodule MyApp.EmailTest do
  use ExUnit.Case

  test "welcome email" do
    user = %User{name: "Paul", email: "paul@gmail.com"}

    email = MyApp.Email.welcome_email(user)

    assert email.to == user
    assert email.subject == "This is your welcome email"
    # The =~ asserts that the left hand side contains the text on the right
    assert email.html_body =~ "Welcome to the app"
  end
end
```

Then when you want to integration test, you can assert that the welcome email
was sent. Here's an example of a controller for handling new registrations in
Phoenix.

```elixir
defmodule MyApp.RegistrationControllerTest do
  use MyApp.ConnCase
  use Bamboo.Test

  test "sends welcome email" do
    user_params = [name: "Paul", email: "paul@gmail.com"]

    post conn, registration_path(conn, :post), user_params

    newly_registered_user = Repo.get_by!(User, user_params)
    assert_delivered_email MyApp.Email.welcome_email(newly_registered_user)
  end
end

defmodule MyApp.RegistrationController do
  use MyApp.Web, :controller

  def create(conn, %{"user" => user_params}) do
    user = insert_user(user_params)

    MyApp.Email.welcome_email(user) |> MyApp.Mailer.deliver_later

    redirect(conn, to: "/")
  end
end
```

## How do I get that password reset link?

Bamboo comes with a [`EmailPreviewPlug`] for using in Phoenix or other Plug
based frameworks. This lets you see emails that were sent when using
[`Bamboo.LocalAdapter`]. This is great for trying out password reset features,
or seeing how an email will look.

[`EmailPreviewPlug`]: https://hexdocs.pm/bamboo/Bamboo.EmailPreviewPlug.html
[`Bamboo.LocalAdapter`]: https://hexdocs.pm/bamboo/Bamboo.LocalAdapter.html

## Flexibility is at the core

Adapters make it much easier to switch providers if something goes wrong, they
shut down, or you can get a better price elsewhere.

Bamboo ships with adapters for Mandrill and Sendgrid. There are also third party
adapters available, and [building your own] is straightforward for most services.

[building your own]: https://hexdocs.pm/bamboo/Bamboo.Adapter.html

## Keeps things fast

Studies show that speed directly correlates to customer satisfaction and
spending. Let's keep things fast.

Bamboo lets you easily send emails in the background without any dependencies
outside of what comes with Elixir. Just add the
[`Bamboo.TaskSupervisorStrategy`] to your app, and send with
[`Mailer.deliver_later`].

If you need something more robust you can easily create your own strategy for
delivering later with [`Bamboo.DeliverLaterStrategy`].

[`Bamboo.TaskSupervisorStrategy`]: https://hexdocs.pm/bamboo/Bamboo.TaskSupervisorStrategy.html#child_spec/0
[`Mailer.deliver_later`]: https://hexdocs.pm/bamboo/readme.html#delivering-emails-in-the-background
[`Bamboo.DeliverLaterStrategy`]: https://hexdocs.pm/bamboo/Bamboo.DeliverLaterStrategy.html

## Give it a try

For more examples and in-depth documentation, see [the docs] on hex.pm.

Get started with Bamboo today, we think you'll love it.

[the docs]: https://hexdocs.pm/bamboo/readme.html
