Rails Email Setup for Development, Staging, and Production

I’ve always found setting up email for a Rails app to be kind of a pain. You need to update a lot of files, you need to figure out how to look at sent emails, and you need to make sure you don’t end up sending test emails to the world!

This article describes what’s become my default strategy for configuring email in Rails so that it’s easy to work with email within the development, staging, and production environments. I’m going to talk about the approach I take to ActionMailer configuration and the other tools I use. (If you just wanna see the code, it’s in this gist here.

Setting up a place for Rails email configuration

The first thing I want to do is to set up a place for ActionMailer-specific configuration. Most guides will tell you to add this configuration to the per-environment config files like config/environments/production.rb.

I don’t like touching these files because it complicates future Rails upgrades. (I want to minimize diffs when running rails app:update.) I instead prefer to read the config information from a YAML file using an initializer. Here’s how to do that!

Create a file called config/mailer.yml and create a section for the usual environments. It should look like this:

development:

production:

test:

Next, we’ll create an initializer to read this file when the app boots up. We’ll call it config/initializers/action_mailer.rb. We’ll end up reading this with Rails’ config_for (a neat little bit of functionality!)

Rails.application.config_for(:mailer).keys.each do |option_name|
  Rails.configuration.action_mailer[option_name] = Rails.application.config_for(:mailer)[option_name]
end

That’s it for this part! Now let’s move on to the individual environment setup.

Setting up a test email

I’m not going to go into too much detail here, but if you don’t have an email that’s easy to send from your app, it might be worth it to set up a simple test email that you can send via the command line. I usually set up something like:

namespace :mail do
  desc "Send a couple of test emails"
  task send_tests: :environment do
    %w[ a@example.com b@example.com ].each do |recipient|
      puts "Mailing #{recipient}..."
      TestMailer.with(recipient: recipient).test_mail.deliver_later
    rescue => exception
      log_data = { task: "mail:send_tests", recipient: recipient, exception: exception }
      Rails.logger.error(log_data)
    end
  end

I’ve shown dummy email addresses in this sample, but I actually use live email addresses. (If you wanna email me, it’s easy enough for you to find my address!)

I won’t show you how to set up TestMailer here, but the Rails docs have everything to get you started.

Setting up Rails email in development

In development, I like to use MailCatcher, a simple SMPT server that will catch and display your app’s sent emails in a browser. It’s free and open source.

Here’s a quick rundown of how to set it up, or you can visit the MailCatcher home page.

First, install MailCatcher using the gem command. (That is: do not install it with Bundler and not put it in your Gemfile.)

gem install mailcatcher

I run MailCatcher via Foreman. So the next thing I do is to add it to my Procfile.dev file, like so:

mailcatcher: mailcatcher --foreground

Now, we return to the mailer.yml file and add the setup information for ActionMailer. It looks like this:

development:
  smtp_settings:
    address: "localhost"
    port: 1025

The above is enough to get your app to send emails to MailCatcher. However, there are other things you’ll need to do to have real-world emails behave well in your development environment. So, my development email configuration looks like this.

development:
  asset_host: "http://www.railsgigs.test:3000"
  default_url_options:
    host: "www.railsgigs.test"
    port: 3000
  smtp_settings:
    address: "localhost"
    port: 1025

Most of that is to ensure that the app has enough info to generate URLs for any links or assets in the email.

Now you can send emails in your Rails development environment and quickly look at them!

Setting up Rails email in staging

In staging, instead of MailCatcher, I use MailTrap by RailsWare. It’s functionally similar to MailCatcher and actually has many more features that I won’t go into here. I usually deploy on Heroku, where it’s trivial to install MailTrap, and there’s a free tier!

I usually don’t maintain a staging-specific configuration. Instead, I run in production mode with the needed configuration read from the environment. (For more on this approach, read the The Twelve Factor App.

So, here’s what mailer.yml file looks like, with most of the configuration coming from the environment. The smpt_settings are what’s needed to get MailTrap working, and the other bits are stuff I find I need for links and assets in emails.

production:
  asset_host: <%= "https://#{ENV["URL_HOST"]}" %>
  default_url_options:
    host: <%= ENV["URL_HOST"] %>
  smtp_settings:
    user_name: <%= ENV["SMTP_USER_NAME"] %>
    password: <%= ENV["SMTP_PASSWORD"] %>
    domain: <%= ENV["SMTP_DOMAIN"] %>
    address: <%= ENV["SMTP_ADDRESS"] %>
    port: <%= ENV["SMTP_PORT"] %>
    authentication: <%= ENV["SMTP_AUTHENTICATION"]&.to_sym %>
    enable_starttls_auto: true

On Heroku, where I usually deploy, you define these environment variables on the “Settings” tab. Here’s what the values look like for this app:

SMTP_ADDRESS:         smtp.mailtrap.io
SMTP_AUTHENTICATION:  cram_md5
SMTP_DOMAIN:          smtp.mailtrap.io
SMTP_PASSWORD:        this_is_not_the_password
SMTP_PORT:            2525
SMTP_USER_NAME:       this_is_not_the_user_name

Setting up Rails email in production

We’ve already done most of the work when setting up the staging environment. We need to update the environment variables we used above in the staging environment. Here’s an example of how I set them in my production environment where I’m using SendGrid. It should be about the same for any email service.

SMTP_ADDRESS:         smtp.sendgrid.net
SMTP_AUTHENTICATION:  plain
SMTP_DOMAIN:          railsgigs.com
SMTP_PASSWORD:        this_is_not_the_password
SMTP_PORT:            587
SMTP_USER_NAME:       this_is_not_the_user_name

And, of course, you don’t need a way to preview the email here because you’re sending live emails!

And that’s it!

Thanks for reading! I hope this saves you from figuring stuff out and provides you with an enjoyable experience! If you’ve got any questions, hit me up on Bluesky or email.

And don’t forget, you can also find the code in this gist here!