At the risk of dating myself, I remember our world without email. It was a time of isolation, where asynchronous communication required “Answering” or “Fax” machines. The truly desperate turned to pen and paper, requiring a team of trucks and airplanes to ensure a message arrived at its intended destination. Oh, and the most terrifying aspect of these dark days? Spam was a processed meat that (allegedly) PEOPLE ATE!
shudders
Thankfully, we’ve come a long way from those times. Email has taken the world by storm, rising from the tools of the geeky to the tool of the layman into the stratosphere of an annoying tool used by marketers and thieves. Regardless of the many predictions of the demise of Email as a communication mechanism of choice, it is here to stay. We all use it and, as a Rails developer, you will no doubt need it to speak to your users and send them processed meat. NO! I mean, incredible important communiques that will improve their lives every day!
Today, we’ll talk about the basics of sending email with Rails, along with some gotchas, pro-tips, and services and gems you should know about.
The Basics
Rails uses the ActionMailer module to handle the email. The ActionMailer guide is excellent, and well worth reading. I’ll cover some of what the guides runs through, but you should read them yourself.
Creating a Mailer
In order to send mail, Rails needs one or more “mailers”, which are subclasses of ActionMailer::Base
.
class VikingMailer < ActionMailer::Base
# Default Mail Values
default from: 'bloodyvikings@breakfast.com', to: { User.pluck(:email) }
end
In the above snippet, I’ve added a couple of default values. You can override these values in the individual mailer methods. Other items that can have defaults are subject
and headers
.
It’s a good idea to use the Rails generator to create a mailer
rails g mailer VikingAMailer
create app/mailers/viking_mailer.rb
invoke erb
create app/views/viking_mailer
invoke test_unit
create test/mailers/viking_mailer_test.rb
create test/mailers/previews/viking_mailer_preview.rb
The generator creates the mailer file, a view folder for the mailer, a test file and preview file for the mailer. Looks a lot like generating a controller, doesn’t it? It is.
Each method in the Mailer class is like methods on a Rails controller. If you want to send a welcome email to new users, add a welcome_email
method to your mailer class along with a view of the same name in app/views/viking_mailer directory.
class VikingMailer < ActionMailer::Base
# Default Mail Values
default from: 'bloodyvikings@breakfast.com', to: { User.pluck(:email) }
def welcome_email(user)
@user = user
# I am overriding the 'to' default
mail(to: @user.email, subject: 'Do you have any spam?')
end
end
The app/views/viking_mailer/welcome_email.html.erb file might look like this:
<p>Dear <%= @user.name %>,</p>
<p>We want Spam for breakfast.</p>
<p>Yours truly,</p>
<p>Bloody Vikings</p>
Notice, the @user
instance variable is available in the view, just like regular controllers and views. Mailer views can also have layouts, by the way.
As long as we’re on the topic of views, it’s possible to specify the format of the email view, just like Rails views. Above, the view was welcome.html.erb. If we add a welcome.text.erb, then ActionMailer will add it to the outgoing MIME message. This provides control over the text-only and HTML version of your email, this fulfilling your wildest MIME dreams.
Configuration
In order to send email, ActionMailer needs to be configured properly. There are a few ways to actually send email, but the most common way uses the Simple Mail Transfer Protocol (SMTP).
Here is a good article on the basics of SMTP.
A common example of the config from the guides:
config.action_mailer.delivery_method = :sendmail
# Defaults to:
# config.action_mailer.sendmail_settings = {
# location: '/usr/sbin/sendmail',
# arguments: '-i -t'
# }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default_options = {from: 'no-reply@example.com'}
This configuration will use the UNIX sendmail
program, raise delivery errors, and make your default sender no-reply@example.com
. The perform_deliveries
option is, as you might have guessed, a way to tell Rails not to send any mail ever. In some situations, like in a development environment, you may not want to futz with sending mail.
Next Level
Let’s take things up a notch, shall we? Now that you understand the basics of sending email, what more can be done? Plenty.
Headers
One of the parts of an “electronic mail” is the header. It is made up of fields like to/from addresses, subject, message ID, and send dates. There are many others, which you can pass as a hash argument to the mail
method:
def welcome_email(user)
...rest of method...
mail(to: @user.email, subject: "Word", reply_to: 'dev@null.com')
end
In fact, the mail
method only accepts a single hash parameter for headers, delivery options, and template options. By “delivery_options”, I mean you can specify SMTP mailer settings that usually reside in the Rails application configuration. Template options are things like the path to your mail templates.
Specifying a Format Inline
The mail
method only takes a hash. Well, that, and a block. You can pass a block to the mail
method to specify how to handle specific formats.
def welcome_email(user)
...rest of method...
mail(to: @user.email, subject: "Word", reply_to: 'dev@null.com') do |format|
format.html { render 'cool_html_template'}
format.text { render text: 'Get a real mail client!'}
end
end
Now it REALLY looks like a Rails controller. That Rails core team sure is clever!
Fancy Email Address Format
If you have the recipient’s name, feel free to format your to
email address like this:
#{@user.name} <#{@user.email}>
The user will feel like you really know them and aren’t Spammy McSpammer.
Attachments
What good is email if you can’t send attachments? Thankfully, ActionMailer makes sending attachments a piece of cake. Simply add a File
to the attachments
hash inside your Mailer method.
def welcome_email(user)
attachments["viking-breakfast.mp4"] = File.read('spamspamspam.mp4')
mail....
end
Now, the viking video will show up as an attachment on the email. But, what about inline attachments?
What are inline attachments, you ask? Only the greatest thing since flavored processed meat! The best example is a image in your super-fancy HTML email. Inline attachments are what allow you to include that image in the email content, and not dangling off the end like some stupid cat video or .dot file (what the heck are those, anyway?).
Happily, adding inline attachments is just as simple:
attachments.inline['viking-header.jpg'] = File.read('viking-header.jpg')
Then, in your view, do this:
<%= image_tag attachments['viking-header.jpg'].url %>
And your HTML email will be sure to catch their attention.
Interceptors
Interceptors are hooks into the email sending process. ActionMailer provides this framework to allow the altering of an email before it leaves for the interwebs. Why would you want to do that, you ask? A great answer is: to prevent your staging/QA environment from sending emails to your users. An interceptor can be created to check the Rails environment (development, production, staging) and send the email to a staging address instead of the intended recipient. Now, you can see what the email looks like before releasing it to production. Later, I’ll show you a gem that does this very thing.
To create an interceptor, define a Ruby class, write the hook method, and add the interceptor to the configuration.
# From the guides
class VikingEmailInterceptor
def self.delivering_email(message)
message.to = ['bloddyvikings@breakfast.com']
message.subject = "We don't have any!"
end
end
Then, in an initializer (like config/initializers/viking_email_interceptor.rb):
ActionMailer::Base.register_interceptor(VikingEmailInterceptor) if Rails.env.staging?
Now all your mails will be caught by the VikingEmailInterceptor
and they’ll only go to “bloodyvikings@breakfast.com”.
Sending Email Asynchronously
Sending email is a fire-and-forget operation, most of the time. There can be some latency when calling the mail server or building the mail view or whatever. Sending the mail asynchronously is often desirable, as it means the user doesn’t have to wait for something they won’t see in the app.
In Rails 3.x – 4.1.x, the task of backgrounding email falls to the developer. Here’s a great example of doing this with Sidekiq. This will work in either Rails 3 or 4, but you have to have Sidekiq (and thus, Redis) set up and ready to go.
Rails 4.2 provides makes this a bit easier with ActiveJob. ActiveJob is a standard facade to the various queuing systems, such as Sidekiq, Resque, or Delayed Job. The first method in Rails to take advantage of the new ActiveJob queuing is the deliver_later
method on ActionMailer! Granted, you’ll need to configure one of the aforementioned queuing systems, but once you do, backgrounding your email will be simple.
Incidentally, if you need help choosing or setting up a queuing system, check out this series!
Services
Email is big, big, BIG business. As such, you probably want to know if your emails are reaching your vikings, if they are being opened, how many emails you’re sending, etc. If you have configured your Rails app to use a local SMTP server with sendmail, then scaling and tracking are difficult to impossible. Using a service centralizes the sending of your email and provides many of the functions I just mentioned.
At last count, there are somewhere in the neighborhood of 1.25 billion Email Service providers. Here are some of the big dogs:
- SendGrid – Probably the most well-known email service provider. We’ve used this as a Heroku plugin and the interface is great for investigating your email activity.
- Mandrill by MailChimp – Offers pay-as-you-go pricing, which is very reasonable, along with a nice API. We’ve actually used Postfix (http://www.postfix.org/) configured to consume Mandrill so that we don’t have any special ActionMailer configuration to use the service. All the config is performed on Postfix. (Note: The Postfix stuff is NOT required to use Mandrill)
- Postmark – I have never used Postmark, but it looks very interesting. It adds the ability to receive email, convert it to JSON, and send it on to your Rails app.
- Amazon SES – Amazon’s Simple Email Service offers much of the same as the previous services at a very reasonable price. I’ve not used it, but it’s on my list, simply because it’s Amazon.
- Many, many more, such as Mailjet, SocketLabs, and Postage.
Configuration of these services is well beyond the scope of this article, but if you’re serious about email, you have to use one.
Another service that provides something a bit different is Apostle.io Apostle.io provides the ability to define your email templates using their service. This removes your mail views from Rails and puts them on Apostle.io, so non-Rails developers and vikings can create/modify the templates. It integrates very simply with Rails and most of the services I mention above.
Common Gotchas
Everything with Rails has a sharp edge that requires your utmost caution, and sending email is no different. Here’s just a couple, but I am sure every Rails developer could add one.
Mailers don’t have any request context, like controllers. That missing context has a lot of information, but the most important is the ‘host’ value. This value is used for forming links. To fix this, set the config.action_mailer.default_url_options = {host: ‘vikings.com’}
or pass it to the url_for
helper. You can use only_path
to generate relative urls, but that is useless for an email. Similarly, all the _path
url helpers will be useless in emails, so always use the _url
helpers
The next one deals with testing your mails. I don’t think very many people do this, and that is like eating Spam without checking the expiration date. It’s a shame, really, because testing a mailer is pretty simple. In fact, the Guides have a whole section dedicated to it. This example is from that section:
class UserMailerTest < ActionMailer::TestCase
test "invite" do
# Send the email, then test that it got queued
email = UserMailer.create_invite('me@example.com',
'friend@example.com', Time.now).deliver
assert_not ActionMailer::Base.deliveries.empty?
# Test the body of the sent email contains what we expect it to
assert_equal ['me@example.com'], email.from
assert_equal ['friend@example.com'], email.to
assert_equal 'You have been invited by me@example.com', email.subject
assert_equal read_fixture('invite').join, email.body.to_s
end
end
Now, whether you cringe at testing multiple things in a single test or any other stylistic issue, the point is: Testing a mailer is similar to testing a controller. Do it.
Rails 4.1 added ActionMailer previews, which makes quick spot-checking of your email easy. When you generate a mailer with Rails 4.1 or higher, it creates a “preview” file in the test directory. This preview file allows you to (duh) preview the various mail methods for a mailer. Here is an example in the guides.
I haven’t used ActionMailer previews outside of briefly checking them out. ActionMailer Previews don’t allow you to change the recipient or other variables in the mailer, so you have to hardcode them. I have other, preferred options to see how my emails look. Here, let me show you….
Gems
There are many, many gems that do various things with Rails and mail. Here are two of my favorite:
- Recipient interceptor – Provides a simple interceptor to change the recipients of all the emails sent by your app to one or more email addresses that you specify. This is the classic example of changing the staging environment to send emails to the test team to be validated, as I mentioned above. You can also add a subject prefix (like
[STAGING]
). Take a look at the code to see how simple an interceptor is to create. - Mailcatcher – Mailcatcher creates a simple SMTP server that catches all the emails sent by your Rails app and provides a web interface to view them. I’ve used MailCatcher for ages and really like it. It is a far better option than ActionMailer Previews, in my humble opinion.
Signing Off
Now you know all about sending mail with Rails. This post could’ve gone on forever, so I tried to mix in some of the basics and some of the next level bits. My guess is some of you are saying things like:
- “Why didn’t he cover X???”
- “Oh, he’s dead wrong about Y!!”
- “Does he really eat Spam??”
If so, put those thoughts in the comments and I’ll email you my responses.
Frequently Asked Questions about Sending Mail with Rails
How do I set up Action Mailer in Rails?
Setting up Action Mailer in Rails involves a few steps. First, you need to configure your mailer settings in the environment configuration file. This includes setting the delivery method, SMTP settings, and default URL options. Next, you need to create a mailer by running the command ‘rails generate mailer UserMailer’. This will generate a mailer class where you can define your email methods. Finally, you need to create views for your emails, which are typically HTML and text templates.
How can I send emails asynchronously in Rails?
Sending emails asynchronously can improve the performance of your Rails application. You can achieve this by using Active Job, which is a framework for creating background jobs. To send emails asynchronously, you simply need to call the ‘deliver_later’ method instead of the ‘deliver_now’ method when sending an email. This will enqueue the email to be delivered in the background.
How do I test emails in Rails?
Testing emails in Rails can be done using the Action Mailer test helpers. These helpers provide methods to check if an email has been delivered, to retrieve the email, and to assert various aspects of the email, such as the sender, recipient, subject, and body. You can also use third-party services like Mailtrap to capture and inspect emails in a safe, isolated environment.
How do I handle incoming emails in Rails?
Handling incoming emails in Rails can be done using Action Mailbox, which is a framework for processing inbound emails. Action Mailbox routes incoming emails to mailbox classes based on the recipient address. These mailbox classes can then process the emails, for example, by creating a new post or comment in your application.
How do I send emails with attachments in Rails?
Sending emails with attachments in Rails is straightforward. In your mailer method, you can call the ‘attachments’ method and pass in the filename and the file data. The file data can be read from a file or can be any binary data. The ‘attachments’ method will automatically encode the file data and add it as an attachment to the email.
How do I customize the layout of my emails in Rails?
Customizing the layout of your emails in Rails is similar to customizing the layout of your views. You can create a layout file in the ‘app/views/layouts’ directory and specify the layout in your mailer class. You can also use the ‘layout’ method in your mailer methods to specify a different layout for each email.
How do I handle email delivery errors in Rails?
Handling email delivery errors in Rails can be done using rescue blocks. When an error occurs during email delivery, Action Mailer will raise an exception. You can catch this exception in a rescue block and take appropriate action, such as logging the error or retrying the delivery.
How do I send emails using a third-party service in Rails?
Sending emails using a third-party service in Rails typically involves setting the delivery method to :smtp and configuring the SMTP settings in the environment configuration file. The SMTP settings include the address, port, authentication method, and credentials of the third-party service. Some services also provide a Rails gem that simplifies the setup.
How do I preview emails in Rails?
Previewing emails in Rails can be done using the Action Mailer preview feature. This feature allows you to create preview classes that define sample emails. You can then view these sample emails in your browser during development.
How do I send emails in different languages in Rails?
Sending emails in different languages in Rails can be done using the I18n (Internationalization) library. You can define translations for your emails in the locale files and use the ‘t’ method in your views to display the translated text. You can also set the locale for each email based on the recipient’s preferred language.
Glenn works for Skookum Digital Works by day and manages the SitePoint Ruby channel at night. He likes to pretend he has a secret identity, but can't come up with a good superhero name. He's settling for "Roob", for now.