Want to see the full-length video right now for free?
Elixir & Phoenix, so hot right now! On this week's episode we'll take a look at some of the great features of both Elixir and Phoenix through the lens of Bamboo, a library for sending emails in Phoenix apps, recently released by thoughtbot developer Paul Smith.
Elixir is a functional programming language, and Bamboo embraces and encourages
this functional nature in its design. The default way to build an email using
Bamboo is with the new_email
function, to which you pass a struct defining how
to build the email:
new_email(
to: "john@gmail.com",
from: "support@myapp.com",
subject: "Welcome to the app.",
html_body: "<strong>Thanks for joining!</strong>",
text_body: "Thanks for joining!"
)
This certainly works, but with with Bamboo you can take advantage of Elixir's pipe operator to build the email in multiple steps, passing the resulting output of each previous step on to the next:
new_email
|> to("foo@example.com")
|> from("me@example.com")
|> subject("Welcome!!!")
|> html_body("<strong>Welcome</strong>")
|> text_body("welcome")
This approach makes it both very easy and very clear to manage things like default mailer settings, without having to use more complex constructs like Rails' class macros. Everything behaves the same way, and any additional functionality is added by simply creating a new function that takes in an email as an input, and produces a new email based on the input.
Another Elixir feature that Bamboo takes advantage of is protocols. Briefly, an Elixir protocol is an interface that data can conform to, allowing for polymorphism in our Elixir code.
In Bamboo, the Bamboo.Formatter defines an interface for converting data,
for instance a User
struct, into an email address. The implementation would
look something like:
defimpl Bamboo.Formatter, for: MyApp.User do
# Used by `to`, `bcc`, `cc` and `from`
def format_email_address(user, _opts) do
fullname = "#{user.first_name} #{user.last_name}"
{fullname, user.email}
end
end
Here we've defined the format_email_address
function implementation for our
User
struct, allow Bamboo to automatically format email address when sending
messages. Internally, Bamboo will then call out to the format_email_address
,
allowing our custom implementation to take over. Check out the code for the
full details.
Another feature that makes Bamboo interesting is the use of the Adapter
Pattern to actually handle sending emails. This makes it very easy to swap in
the Bamboo.TestAdapter. This adapter takes advantage of Elixir's
processes system and the ability to send
and receive
messages.
With the TestAdapter
enabled, Bamboo will capture the email to allow for test
helpers like assert_delivered_email(email)
to be used. This works by managing
the messages sent from the test process, but for the most part Bamboo handles
this and we don't need to dig too deep into the details.
One final feature of Elixir that Bamboo takes advantage of is the underlying Erlang platform and the associated Elixir supervisors. With supervisors, it's expected that your code will be running as a child process of some parent "supervisor" process, making it very easy spin off new processes.
Bamboo takes advantage of this to allow for delivering emails in the
background, without the need to implement a job queue. While it's possible to
go with a more complex implementation for sending later, it's really nice that
the nature of Erlang and Elixir make it easy to simply take advantage of the
process management and supervisor functionality to provide the deliver_later
functionality out of the box.