The correct emails configuration in Rails
It’s time to configure your project correctly.
Sending emails is Rails easy. Do it also correctly by following this guide.
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:
- Not mock the Mailer:
test
queue adapter does that for us; - 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:
- One unit test to check that the email is enqueued correctly and with the correct parameters
- 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.