Blog
0 pixels scrolled
  • Home
  • Work
  • Services
  • About
  • Blog
  • Contact
How I use Action Mailer
Jared Norman on April 10, 2019
How I use Action Mailer
Jared Norman on April 10, 2019
Posted in Rails
← Back to the blog

Almost every Rails application I’ve worked on has sent e-mail, but almost none of them have taken completely consistent approaches to how they send e-mail. Sometimes mailers will be called directly from controllers. Other times they’ll be wrapped in an ActiveJob or Sidekiq worker, or use the .deliver_later API that you get through ActiveJob. By working with all these different patterns I’ve found one way that helps make my mailers easier to write, reuse, and test.

Before I get into things, it’s worth nothing that there are two ways to use mailers in newer versions of Rails. They are equivalent for the purposes of this article, and for convenience I’m going to use the “old” way, the one supported by more versions of Rails: UserMailer.welcome_email(user_id: user.id).deliver. In newer Rails versions you can use the “new” API, UserMailer.with(user_id: user.id).welcome_email.deliver, which makes it easier to reuse header and instance variable processing. This pattern applies equally if you are using the new API.

A Minimal Mailer Example

Let’s examine the first mailer example in the Action Mailer Basics guide. (I’ve switched it to use the “old” mailer API.)

class UserMailer < ApplicationMailer
  default from: 'notifications@example.com'
 
  def welcome_email(user)
    @user = user
    @url  = 'http://example.com/login'
    mail(to: @user.email, subject: 'Welcome to My Awesome Site')
  end
end

The mailer provides a default “from” address, and contains a single e-mail: a welcome e-mail presumably sent to users new to a site. The Rails guide goes on to show a controller action that sends the e-mail. It looks something like this, again modified to use the “old” API:

class UsersController < ApplicationController
  # POST /users
  # POST /users.json
  def create
    @user = User.new(params[:user])
 
    respond_to do |format|
      if @user.save
        # Tell the UserMailer to send a welcome email after save
        UserMailer.welcome_email(user: @user).deliver_later
 
        format.html { redirect_to(@user, notice: 'User was successfully created.') }
        format.json { render json: @user, status: :created, location: @user }
      else
        format.html { render action: 'new' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end
end

In my opinion, there’s nothing strictly wrong or bad about any of this. I’d give this PR a :+1:.

That said, I don’t think it’s ideal. If it were up to me, I’d prefer the consistency of having all the mailers in an app being used the same way.

Mailer Parameters

Mailers take parameters or arguments, and Rails doesn’t dictate what they should be. In the example, we’re sending in a user model in the user parameter, and the e-mail address is being pulled off that user to use as the recipient for the e-mail. Technically we can pass anything we want in.

All my mailers take an arguments to specify the recipients of the e-mail you’re sending. If you’re mailer sends to a single address, you might have recipient argument. If it sends to multiple people you could have a recipients argument. If you need to BCC and CC different addresses you can have arguments for that. Try to be consistent in how you name these parameters across all the mailers in your app.

The reason I take in the recipients (and sender if necessary) as email addresses is that it makes it easier to reuse the mailer in other contexts. Down the road you can send the e-mail to whoever you want, without modifying the mailer itself.

On top of the recipients, there’s often additional data or records that your mailer might need to do its job. In the example it’s just a static URL, but for example, in an ecommerce order confirmation e-mail you might need the order that’s being confirmed.

If the app uses “presenter” objects of some kind, I’ll lean towards passing those into my mailers rather than ActiveRecord models. Presenters are classes that encapsulate the logic of “presenting” a given model or collection of models to the frontend. While you don’t need a special gem to implement this pattern, there are gems like Draper to help with it.

In general, I think mailer parameters should be treated how I treat instance variables in a Rails controllers: only load the things I absolutely need to in order to render this page (or e-mail).

The Job

The example called .deliver_later, which will use ActiveJob to perform the delivery asynchronously. By default in development this will just use an in-process thread pool, but in most real applications you’ll be using something like Sidekiq, Resque, or Delayed::Job to perform those asynchronous jobs.

It’s important to send your e-mails in a job: sending an e-mail can fail for a variety of reasons, and if it does fail it can almost always be safely retried, and most job processing systems are configured to automatically retry jobs that raise errors. If the e-mail can’t be sent, we wait and try again so that it’s eventually sent successfully.

Rather than relying on deliver_later, I (almost) always write a job (also called a worker) for my mailers. This gives a specific place for the me to put in the logic that goes between the controller (or service) signalling that it wants an e-mail to be sent and the code responsible for actually constructing and sending the e-mail (the mailer.)

The worker is responsible for querying the database for the records the mailer tneeds, instantiating any presenters, and deciding what e-mail addresses to send this e-mail to.

Putting It Together

Given what I’ve discussed, let’s take a look at the mailer/job I would have written. I’ve omitted the URL string, since that’s a fairly unusual thing to want to pass through since the template will have access to Rails’ routing helpers. First, the mailer:

class UserMailer < ApplicationMailer
  default from: 'notifications@example.com'
 
  def welcome_email(recipient:, login_url:, user:)
    @user = user
    mail(to: recipient, subject: 'Welcome to My Awesome Site')
  end
end

Here’s the job:

class WelcomeMailerWorker < ApplicationJob
  def perform(user_id)
    user = User.find(user_id)

    UserMailer.welcome_email(
      recipient: user.email,
      user: user
    ).deliver
  end
end

Now, this is a minimal example of this pattern, and there’s barely any argument to be made that this is better than what’s done in the Rails guides.

Work ServicesAboutBlogCareersContact


Privacy policyTerms and conditions© 2019 Super Good Software Inc. All rights reserved.