There’s one thing that everyone loves: getting mail! But there’s one thing that all developers would rather avoid: sending mail. Unfortunately, this paradox perfectly describes the joys and horrors of getting your application to send a single email.
I recently worked on building out a password reset feature for one of our applications. In order for this feature to work, I had to figure out how to get my Rails application to send an email to our user with a password reset token in case they had forgotten their password. I thought that handling the authentication and token aspect of this would be complicated, but it turned out that learning about mailers was the more fun part. I had never actually worked with Rails mailers before, and honestly, I thought that I was in over my head (this also might be partly attributed to the fact that I had just come back from a two-week vacation and felt like I had completely forgotten how to code).
So, I did what any developer would do: I cried and went home. Okay, okay, I’m just kidding! What I actually did was read through the documentation, play around with my application and, in the process, taught myself how to use Rails Action Mailer. I never thought that I’d say this but, getting that feature to work and seeing that email pop up was incredibly exciting. In fact, I don’t think I’ve ever been more excited about sending and receiving an email. But don’t let me tell you how thrilling it was — let’s create our own mailer and experience it together!
Generating Some Mail(ers)
Rails has a wonderful built-in mailing system called Action Mailer, which allows us to send email from our application as long as we have a mailer model. Mailers are actually a lot like controllers: they have their own app/mailers
directory, and each mailer has its own associated view as well. Each mailer also inherits from ActionMailer::Base
, and just like controllers, can be generated really easily.
For our bookstore app, we won’t start off with anything too fancy just yet. Instead, let’s stick with a simple mailer that will be responsible for one little thing: sending an order confirmation email whenever a user successfully places an order (did your mind immediately jump to using a state machine? I hope so!)
To start, we’ll use Rails to generate a mailer:
♥ bin/rails generate mailer Order
Running this command in the terminal generates a few different files for us. We now have an app/mailers
directory, with an order_mailer.rb
and application_mailer.rb
file. It also generates three files inside of app/views
: order_mailer
, layouts/mailer.text.erb
, and layouts/mailer.html.erb
, as well as test units for our order mailer (order_mailer_test.rb
).
Depending on how many mailers this application will have, it might not makes sense to generate all of these files. If we decided to manually create our mailer rather than generating it, we’d need to keep one thing in mind: our mailer must be a file inside of the mailers
directory, and it must inherit from ActionMailer::Base
(unless, of course, we wanted to use a mailer from another library, such as the Devise::Mailer
from the devise
gem).
The mailer model has methods defined on it that allows us to actually specify how and where an email is sent. Right now, however, our mailer models look pretty empty! Inside of our generated ApplicationMailer
, the only setup we have is our layout configuration and our from
address:
1 2 3 4 |
|
While our order_mailer.rb
is completely empty:
1 2 |
|
Since mailers are so much like controllers, we can approach writing them in a similar way. The first thing we’ll do is write some actions. Just like with controllers, we want our methods to adhere to the single-responsiblity principle, which means that they should be handling only one thing at a time. We’ll start by writing a confirmation_email
method, which will take an Order
object as its parameter.
1 2 3 4 5 |
|
Just like in controllers, any instance variables that we define in our method — in this case, @order
— become available for us to use inside of our views. This will be important when we want to render the user’s information via our @order
instance. But…we’re not actually mailing anything right now, are we? Of course not! In order to actually create our message and render our email templates, we need to use the mail
method.
The mail
method is defined on ActionMailer::Base
(hence why every mailer should always inherit from it so that it has access to this very crucial method). If we look at the documentation for this method, we can see that it accepts a headers hash, which is where we can specify the most-used headers in an email message. Some of the options we can pass in include subject
, to
, from
, cc
, and date
, among others. For now, we’ll just pass in a to
option and a subject
option:
1 2 3 4 5 6 7 |
|
If we wanted to get really fancy, we could specify default
values for any of these headers (except for date
) inside of our OrderMailer
class. Alternatively, we could also write our mail
method as a block, which would allow us to render specific templates — a piece of functionality that might be nice as we add more methods to this mailer over time. We could also use the block syntax in order to render plain text directly without using a template, which would look something like this:
1 2 3 4 |
|
But let’s hold off on all these bells and whistles. Let’s just get this method into our state machine and actually send this bad boy.
Send Me Some Mail
Now for the fun part: sending and receiving our mail! There are two methods we can use to send an email: deliver_now
and deliver_later
. The former sends our email inline (in the same request-response cycle), while the latter sends emails in the background by integrating with Active Job.
We already wrote our confirmation_email
method, so now we just need to invoke it. But, we defined it on our mailer class. However, we don’t need to instantiate a new instance of our OrderMailer
class (like we would have to do with a service object, for example). Instead, we can just call our confirmation_email
method on our mailer class directly. Since brevity is the soul of wit, here’s a truncated version of the state machine in our order.rb
file, which is where we’ll invoke this method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
You’ll remember that our confirmation_email
method takes an Order
as a parameter, which is why we’re passing in self
, the Order
object, into the method, before chaining on deliver_now
at the end of it. Now, after our completed
event is called, this email will be sent. But how do we know what our email will say, exactly? Well, we can head over to our email templates to find out.
When we generated our mailer, one of the files that was generated was app/views/layouts/mailer.html.erb
. If we take a look inside of this file, we’ll see that it’s pretty simple; in fact, all it’s going to do for now is yield
to whatever template needs to be rendered. If we wanted to add styles or formatting that would apply to all of our mailers, this is where it would go:
1 2 3 4 5 |
|
For things pertaining specifically to our OrderMailer
template, we’ll need to visit the view for that mailer, which will live inside of app/views/order_mailer/confirmation_email.html.erb
. We can again think of how controllers work with their associated views (for example, an index
action corresponds to an index.html.erb
file). Similarly, our OrderMailer
class knows about its own specific view because its name is the same as the mailer’s method (confirmation_email
). This is where we can put the text for our email template; for now, it won’t be anything too special and will just use our @order
instance from the confirmation_email
method we wrote in the OrderMailer
to retrieve and render the order number and user’s email:
1 2 3 |
|
Awesome! Now, in development, we can test this out by placing an order, triggering the send_confirmation_email
method in our state machine, and using our OrderMailer
to send an email in a sychronous request to our user’s email address. That’s a lot to do, but we made it happen!
Letter Opener + Instant Delivery
Before we get too email-happy, here’s a thought: how much do you really like email? I don’t know about you, but I would really rather not get an email every single time I test out my mailer in development. Thankfully, there’s a gem that was created to solve precisely this very problem: letter_opener
.
This gem intercepts our mailer and allows us to preview an email from within our browser instead of actually sending the email to an email address. One of the great benefits of this — in addition to both saving space in our inbox and not having to set up email delivery in our development environment — is us not having to worry about sending test emails by accident to someone else’s email address!
Adding letter_opener
to our application is pretty easy, and the documentation is easy to follow. First, we’ll add the gem to the :development
group in our Gemfile
:
gem "letter_opener", group: :development
After we run bundle install
in our terminal, we’ll need to do one last step: setting up our mailer configurations. Basically, all this means is that we need to specifically set up our development environment such that it will use our letter_opener
gem as its delivery method. In fact, that’s pretty much the only line we need to add in our config/environments/development.rb
file:
config.action_mailer.delivery_method = :letter_opener
The delivery_method
acepts a delivery method object and defaults to :smtp
. Since we want letter_opener
to handle our mail deliveries, we’ll just set our delivery method on Action Mailer to the gem that we want to use.
Now that we’ve set this up, any email that is sent by Action Mailer will be intercepted and open up in a new tab in our browser, rather than actually being sent to an email address. These files will be stored temporarily in tmp/letter_opener
.
But as lovely and helpful as it is to have all these test emails popping up in our browser, there’s one thing that would be even nicer to have: all of these emails being triggered outside of the request-response cycle. In other words, what we want to do is to run these requests asychronously. Well, what does the documentation say about making this happen?
“Active Job’s default behavior is to execute jobs
:inline
. So, you can usedeliver_later
now to send emails, and when you later decide to start sending them from a background job, you’ll only need to set up Active Job to use a queueing backend (Sidekiq, Resque, etc).”
Okay, it sounds like we need to learn a little bit about Active Job and set up a queueing backend to send our emails in a job. But let’s save that for another blog post. Tune in again next week, when I’ll delve into the basics of Active Job and asychronous processes. Until then, have fun opening those emails!
tl;dr?
- Rails mailers inherit from
ActionMailer::Base
, and work just like controllers, with actions and corresponding views. Check out this fantastic post on sending emails using Action Mailer to dive into the details. - There are a lot of different header options that you can pass to
ActionMailer::Base
. Read more about them over here. - Curious about how to go about configuring Action Mailer to make the mailing magic happen? The Rails Guides have a great tutorial on how to do that.