---
title: Action Mailer and Active Job sitting in a tree...
teaser: Getting started with Action Mailer and Active Job.
tags: ruby,rails,web
author: Elle Meredith
published_on: 2016-05-25
---

Almost every application I ever worked on had some requirement to send emails.
Whenever I need to implement sending emails (using [Action Mailer]),
I also implement a background job for it.
Since version 4.2, Rails has built-in support
for executing background jobs using Active Job.

[Action Mailer]:http://edgeguides.rubyonrails.org/action_mailer_basics.html

Every time I need to start setting up Active Job for email sending,
I find myself looking up the required syntax and suggested specs.
This article solves this problem by gathering in one place
all the answers I usually look for elsewhere.

## Active Job

[Active Job] is a framework for declaring jobs
and making them run on a variety of asynchronous queuing backends.
It provides us with a consistent DSL,
so we can easily switch queuing backends
(for example between development and production environments)
without having to rewrite our jobs.

Active Job has built-in adapters for popular queuing backends
([Sidekiq], [Resque], [Delayed Job], and others).
To get an up-to-date list of the adapters
see the API Documentation for [ActiveJob::QueueAdapters].

A good candidate for a background job is
anything that involves external services or that
might slow the responsiveness of the application,
for example processing payments, sending emails,
or external syntax highlighting services.

[Active Job]:http://edgeguides.rubyonrails.org/active_job_basics.html
[Sidekiq]:https://github.com/mperham/sidekiq
[Resque]:https://github.com/resque/resque
[Delayed Job]: https://github.com/collectiveidea/delayed_job
[ActiveJob::QueueAdapters]:http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html

## Getting started

If you are using [Suspenders], most of the following setup
will already be done for you,
mainly around configuration of Action Mailer or Delayed Job.

[Suspenders]:https://github.com/thoughtbot/suspenders

If you are starting afresh,
you will need to select and set up a queuing backend.
In this case I am using Delayed Job.

The following is my configuration around Action Mailer and its testing set up.
It adds a [dotenv-rails] gem
and configures environments using `.env` variables locally.
It adds mailer spec helpers,
and it clears Action Mailer deliveries before each spec.

[dotenv-rails]:https://github.com/bkeepers/dotenv

```ruby
# Gemfile
group :development, :test do
  gem "dotenv-rails"
end

# .env
APPLICATION_HOST=localhost:3000
SMTP_ADDRESS=smtp.example.com
SMTP_DOMAIN=example.com
SMTP_PASSWORD=password
SMTP_USERNAME=username

# config/environments/production.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  address: ENV.fetch("SMTP_ADDRESS"),
  authentication: :plain,
  domain: ENV.fetch("SMTP_DOMAIN"),
  enable_starttls_auto: true,
  password: ENV.fetch("SMTP_PASSWORD"),
  port: 587,
  user_name: ENV.fetch("SMTP_USERNAME")
 }
config.action_mailer.default_url_options = { host: ENV.fetch("APPLICATION_HOST") }

# config/environments/test.rb
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { host: "example.com" }

# spec/support/action_mailer.rb
RSpec.configure do |config|
  config.before(:each) do
    ActionMailer::Base.deliveries.clear
  end
end

# spec/support/mailer_helpers.rb
module MailerHelpers
  def emails
    ActionMailer::Base.deliveries
  end

  def last_email
    emails.last
  end
end

RSpec.configure do |config|
  config.include MailerHelpers, type: :mailer
end
```

Configuring Active Job is much simpler:

```ruby
# config/application.rb
config.active_job.queue_adapter = :delayed_job

# config/environments/test.rb
config.active_job.queue_adapter = :inline

# spec/support/active_job.rb
RSpec.configure do |config|
  config.include ActiveJob::TestHelper

  config.before(:each) do
    clear_enqueued_jobs
  end
end

ActiveJob::Base.queue_adapter = :test
```

In our test suite set up,
we set `ActiveJob::Base.queue_adapter = :test`.
The `:test` adapter will not run jobs but will just store them,
and will allow us to access the `enqueued_jobs` in our specs.

To load the files in `spec/support`, you need this line in your `rails_helper.rb`:

```ruby
# spec/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file }
```

Now that we are ready to get started,
let's go through the feature that we would like to build.
In this example, when a user signs up for a new user account,
we will send them a welcome email with a link to confirm their email address.

## Creating the mailer

    $ rails g mailer user_mailer invite
      create  app/mailers/user_mailer.rb
      create  app/mailers/application_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/layouts/mailer.text.erb
      create    app/views/layouts/mailer.html.erb
      create    app/views/user_mailer/invite.text.erb
      create    app/views/user_mailer/invite.html.erb
      invoke  rspec
      create    spec/mailers/user_mailer_spec.rb
      create    spec/fixtures/user_mailer/invite
      create    spec/mailers/previews/user_mailer_preview.rb

You will notice that we now have an `ApplicationMailer`
where we can define defaults such as the `from` email address.

The default email layout lives at `app/views/layouts/mailer.*`.

We also have email previews, which are available at:
`http://localhost:3000/rails/mailers/user_mailer/invite*`.

Email's subject and body can be set using `I18n` so in our case, we can define:

```yml
# config/locales/en.yml
en:
  user_mailer:
    invite:
      subject: Welcome
      body: To confirm your email, please visit %{confirmation_url}
```

Our `UserMailer` specs might look something like:

```ruby
# spec/mailers/user_mailer_spec.rb
require "rails_helper"

describe UserMailer  do
  describe "invite" do
    context "headers" do
      it "renders the subject" do
        user = build(:user, token: "abc")

        mail = described_class.invite(user)

        expect(mail.subject).to eq I18n.t("user_mailer.invite.subject")
      end

      it "sends to the right email" do
        user = build(:user, token: "abc")

        mail = described_class.invite(user)

        expect(mail.to).to eq [user.email]
      end

      it "renders the from email" do
        user = build(:user, token: "abc")

        mail = described_class.invite(user)

        expect(mail.from).to eq ["from@example.com"]
      end
    end

    it "includes the correct url with the user's token" do
      user = build(:user, token: "abc")

      mail = described_class.invite(user)

      expect(mail.body.encoded).to include confirmation_url(token: user.token)
    end
  end
end
```

Our `UserMailer` looks much simpler:

```ruby
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def invite(user)
    @user = user

    mail to: @user.email
  end
end
```

We will also need to update our mailer views:

```erb
<%# app/views/user_mailer/invite.html.erb %>
<h1>Welcome <%= @user.email %></h1>

<%= t(".body", confirmation_url: confirmation_url(token: @user.token)) %>
```

And if we want to preview our email, we need to update the preview:

```ruby
# spec/mailers/previews/user_mailer_preview.rb
def invite
  user = build(:user, token: "abc")
  UserMailer.invite(user)
end
```

## Creating the Job

Now that we have a working mailer,
we can create the background job and call on it from our `user#invite` method.
We can use a Rails generator for that:

    $ rails g job send_new_user_invitation
      invoke rspec
      create spec/jobs/send_new_user_invitation_job_spec.rb
      create app/jobs/send_new_user_invitation_job.rb

The Rails generator created a `SendNewUserInvitationJob` in `app/jobs`,
which has one method `#perform`.
To enqueue the job to be performed as soon as the queuing system is free,
we can call `SendNewUserInvitationJob.perform_later`
with any arguments we wish to pass it.
If we wanted to run the job immediately, we could instantiate the job,
and call `.perform` on that instance. We will see that in our specs shortly.

Again, let's start with the specs:

```ruby
# spec/jobs/send_new_user_invitation_job_spec.rb
require "rails_helper"

describe SendNewUserInvitationJob do
  describe "#perform" do
    it "calls on the UserMailer" do
      user = double("user", id: 1)
      allow(User).to receive(:find).and_return(user)
      allow(UserMailer).to receive_message_chain(:invite, :deliver_now)

      described_class.new.perform(user.id)

      expect(UserMailer).to have_received(:invite)
    end
  end

  describe ".perform_later" do
    it "adds the job to the queue :user_invites" do
      allow(UserMailer).to receive_message_chain(:invite, :deliver_now)

      described_class.perform_later(1)

      expect(enqueued_jobs.last[:job]).to eq described_class
    end
  end
end
```

And the corresponding code:

```ruby
# app/jobs/send_new_user_invitation_job.rb
class SendNewUserInvitationJob < ActiveJob::Base
  queue_as :user_invites

  def perform(user_id)
    user = User.find(user_id)

    UserMailer.invite(user).deliver_now
  end
end
```

You will note that we pass `user_id` to the job.
Many background jobs use [Redis] for their data store,
which supports simple data structures
and which is why we pass a simple string rather than a `User` object.

Active Job supports [GlobalID] for parameters.
This makes it possible to pass live `ActiveRecord` objects to your job
instead of class/id pairs, which you then have to manually deserialize.
Previously you would pass an `ActiveRecord` id, run a query to find the object
and then perform the task on hand.
With GlobalID, this can be replaced with passing the `ActiveRecord` object
as an argument directly to perform.
Unfortunately this means that it is possible that the object record
would be deleted after the job is enqueued
but before the `perform` method is called,
and thus the [exception handling] would need to be different.
So best practice still suggests to use [small and simple job params].

[Redis]:http://redis.io/
[GlobalID]: http://edgeguides.rubyonrails.org/active_job_basics.html#globalid
[exception handling]: https://github.com/mperham/sidekiq/wiki/Active-Job#limitations
[small and simple job params]: https://github.com/mperham/sidekiq/wiki/Best-Practices#1-make-your-job-parameters-small-and-simple

The last thing to do is get our `User#invite` to call on our flash new job:

```ruby
# spec/models/user_spec.rb
describe User do
  # ...

  describe "#invite" do
    it "enqueues sending the invitation" do
      allow(SendNewUserInvitationJob).to receive(:perform_later)
      user = build(:user)

      user.invite

      expect(SendNewUserInvitationJob).to have_received(:perform_later)
    end
  end
end

# app/models/user.rb
class User < ActiveRecord::Base

  def invite
    SendNewUserInvitationJob.perform_later(id)
  end
end
```

Alternatively we can call `UserMailer.invite(self).deliver_later`,
which will automatically send the invitation email asynchronously in the queue.

## Recap

Let's recap what we have done here.
When we call on `User#invite`,
it enqueues sending the invitation in the background.
Once the queuing system is free,
it will call on the `UserMailer` to deliver the invitation email.

You can check out a demo app for this tutorial at
[https://github.com/elle/active-job-action-mailer-demo][demo-repo]

[demo-repo]:https://github.com/elle/active-job-action-mailer-demo

Active Job makes scheduling background jobs easier,
without requiring you to change your code when changing queuing systems.
Hopefully this tutorial makes setting up Active Job
to work with Action Mailer a bit easier for you.
