Rails Email Setup for Development, Staging, and Production
05 Mar 2022I’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!