The correct emails configuration in Rails

It’s time to configure your project correctly.

Alessandro Rodi
3 min readMar 14, 2018

Sending emails is Rails easy. Do it also correctly by following this guide.

Photo by ål nik on Unsplash

Always deliver_later

Let’s start with something easy: don’t use deliver or deliver_now. Always use deliver_later. deliver_now is synchronous and therefore your users will have to wait for the email to be sent before receiving an answer. The server may take longer to answer and you really don’t want your users to wait for it. Also because of the fact that the mail server answers, doesn’t mean that the email has been delivered, and especially that it has been delivered correctly.

So always use deliver_later

Environments configurations

To use deliver_later we need something that actually delivers our emails in a background job. ActionMailer will use ActiveJob for this task, so we need to have it configured properly.

development

In your development environment, you can use the method you prefer, either :async or :inline. I personally prefer inline but it doesn’t matter that much because you should absolutely use the gem letter_opener. Add to your Gemfile gem 'letter_opener', group: :development and change your development.rb as follows:

# config/environments/development.rbconfig.action_mailer.delivery_method = :letter_opener
config.active_job.queue_adapter = :inline

Letter opener will open the emails for you in the browser so you don’t need to configure any mail server for local development.

production

I will not cover the mail SMTP configuration, please follow the Rails guide for that. But what you should do is to set the active job adapter to async or, if you have already one of them, use sidekiq or a similar method.

# config/environments/production.rbconfig.active_job.queue_adapter = :async

test

For tests, configure the queue adapter to test and also the delivery method.

# config/environments/test.rbconfig.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test

That’s particularly important for the next section where we’ll see how to properly test your emails.

Testing your emails

Let’s get to the main topic. How should you test your emails? The configuration we used above allows us to:

  1. Not mock the Mailer: test queue adapter does that for us;
  2. Not send the emails, the test delivery method will prevent that for us.

Given the following configuration for the testing environment, your emails will always be enqueued and never executed for delivery: this prevents you from mocking them and you can check that they are enqueued correctly.

Always split the test into two parts:

  1. One unit test to check that the email is enqueued correctly and with the correct parameters
  2. One unit test for the mail to check that the subject, sender, receiver, and content are correct.

Given the following scenario:

class User
after_update :send_email
def send_email
ReportMailer.update_mail(id).deliver_later
end
end

Write a test to check the email is enqueued correctly:

include ActiveJob::TestHelperRSpec.describe User do
# other tests
it 'enqueues an email on update' do
expect { user.update(name: 'Hello') }.
to have_enqueued_job(ActionMailer::MailDeliveryJob).
with('ReportMailer', 'update_mail', 'deliver_now', user.id)
end
end

and write a separate test for your email

Rspec.describe ReportMailer do
describe '#update_email' do
subject(:mailer) { described_class.update_email(user.id) }
it { expect(mailer.subject).to eq ‘whatever’ }
...
end
end
  • You have tested exactly that your email has been enqueued and not a generic job.
  • Your test is fast
  • You needed no mocking

When you write a system test, feel free to decide if you want to really deliver emails there, since speed doesn’t matter that much anymore. I personally like to configure the following:

RSpec.configure do |config|
config.around(:each, :mailer) do |example|
perform_enqueued_jobs do
example.run
end
end
end

and assign the :mailer attribute to the tests where I want to really send emails.

--

--

Alessandro Rodi

Open Source Software Engineer at Renuo AG. Located in Zürich. I do stuff. Sometimes.