Fun Sending Mail on Rails

Glenn Goodrich
Ruby Editor

Viking Ship

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 many 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/vikingemailinterceptor.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 a 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.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://www.kiffingish.com/blog/ Kiffin Gish

    Another email platform which is open source and therefore free to use is the Zarafa Collaboration Platform which in principle should also be easy to integrate into your application. See: http://www.zarafa.com/content/zcp-back-end-components

    • ggsp

      Thanks!