The Rails 4 Way (2014)
Chapter 16. Action Mailer
It’s a cool way to send emails without tons of code.
—Jake Scruggs
Integration with email is a crucial part of most modern web application projects. Whether it’s sign-up confirmations, password recovery, or letting users control their accounts via email, you’ll be happy to hear that Rails offers great support for both sending and receiving email, thanks to its Action Mailer framework.
In this chapter, we’ll cover what’s needed to set up your deployment to be able to send and receive mail with the Action Mailer framework and by writing mailer models, the entities in Rails that encapsulate code having to do with email handling.
16.1 Setup
By default, Rails will try to send email via SMTP (port 25) of localhost. If you are running Rails on a host that has an SMTP daemon running and it accepts SMTP email locally, you don’t have to do anything else in order to send mail. If you don’t have SMTP available on localhost, you have to decide how your system will send email.
When not using SMTP directly, the main options are to use sendmail or to give Rails information on how to connect to an external mail server. Most organizations have SMTP servers available for this type of use, although it’s worth noting that due to abuse many hosting providers have stopped offering shared SMTP service.
Most serious production deployments use 3rd-party SMTP services that specialize in delivering automated email, avoiding user spam filters and blacklists.
16.2 Mailer Models
Assuming the mail system is configured, let’s go ahead and create a mailer model that will contain code pertaining to sending and receiving a class of email. Rails provides a generator to get us started rapidly. Our mailer will send out a notices to any user of our sample application who is late entering their time.
$ rails generate mailer LateNotice
create app/mailers/late_notice.rb
invoke haml
create app/views/late_notice
invoke rspec
create spec/mailers/late_notice_spec.rb
A view folder for the mailer is created at app/views/late_notice and the mailer itself is stubbed out at app/mailers/late_notice.rb:
1 classLateNotice < ActionMailer::Base
2 default from: "from@example.com"
3 end
Kind of like a default Active Record subclass, there’s not much there at the start.
16.2.1 Preparing Outbound Email Messages
You work with Action Mailer classes by defining public mailer methods that correspond to types of emails that you want to send. Inside the public method, you assign any variables that will be needed by the email message template and then call the mail method, which is conceptually similar to the render method used in controllers.
Continuing with our example, let’s write a late_timesheet mailer method that takes user and week_of parameters. Notice that it sets the basic information needed to send our notice email (see Listing 16.1).
Listing 16.1: Adding a mailer method
1 classLateNotice < ActionMailer::Base
2 default from: "system@timeandexpenses.com"
3
4 def late_timesheet(user, week_of)
5 @recipient = user.name
6 @week = week_of
7 attachments["image.png"] = File.read("/images/image.png")
8 mail(
9 to: user.email,
10 subject: "[Time and Expenses] Timesheet notice"
11 )
12 end
13 end
Inside the method we’ve created we have access to a few methods to set up the message for delivery, including the mail method shown above:
attachments
Allows you to add normal and inline file attachments to your message
1 attachments["myfile.zip"] = File.read("/myfile.zip")
2 attachments.inline["logo.png"] = File.read("/logo.png")
headers
Allows you to supply a hash of custom email headers
1 headers("X-Author" => "Obie Fernandez")
Sets up the email that will get sent. It accepts a hash of headers that a Mail::Message will accept and allows an optional block. If no block is specified, views will be used to construct the email with the same name as the method in the mailer. If a block is specified these can be customized.
Note also the change of the default from address to one set up for our application. Here is a sample list of the headers that you can include in the hash passed to the mail method or in the default macro. In addition to these, you may pass any email header that is needed when sending, ie { "X-Spam" => value }.
subject
The subject line for the message.
to
The recipient addresses for the message, either as a string (for a single address) or an array (for multiple addresses). Remember that this method expects actual address strings not your application’s user objects.
users.map(&:email)
from
Specifies the from address for the message as a string (required).
cc
Specifies carbon-copy recipient (Cc:) addresses for the message, either as a string (for a single address) or an array for multiple addresses.
bcc
Specifies blind recipient (Bcc:) addresses for the message, either as a string (for a single address) or an array for multiple addresses.
reply_to
Sets the email for the reply-to header.
date
An optional explicit sent on date for the message, usually passed Time.now. Will be automatically set by the delivery mechanism if you don’t supply a value, and cannot be set using the default macro.
The mail method can either take a block or not if you want to do custom formats similar to Rails routes.
1 mail(to: "user@example.com") do |format|
2 format.text
3 format.html
4 end
The body of the email is created by using an Action View template (regular Haml or ERb) that has the instance variables in the mailer available as instance variables in the template. So the corresponding body template for the mailer method in Listing 16.1 could look like:
1 Dear #{@recipient},
2
3 Your timesheet for the week of #{@week} is late.
And if the recipient was Aslak, the email generated would look like this:
Date: Sun, 12 Dec 2004 00:00:00 +0100
From: system@timeandexpenses.com
To: aslak.hellesoy@gmail.com
Subject: [Time and Expenses] Late timesheet notice
Dear Aslak Hellesoy,
Your timesheet for the week of Aug 15th is late.
16.2.2 HTML Email Messages
To send mail as HTML, make sure your view template generates HTML and that the corresponding template name corresponds to the email method name. For our method this would be in (or .) You can also override this template name in the block.
1 mail(to: "user@example.com") do |format|
2 format.text
3 format.html { render "another_template" }
4 end
16.2.3 Multipart Messages
If a plain text and HTML template are present for a specific mailer action, the text template and the HTML template will both get sent by default as a multipart message. The HTML part will be flagged as alternative content for those email clients that support it.
16.2.3.1 Implicit Multipart Messages
As mentioned earlier in the chapter, multipart messages can also be used implicitly, without invoking the part method, because Action Mailer will automatically detect and use multipart templates, where each template is named after the name of the action, followed by the content type. Each such detected template will be added as separate part to the message.
For example, if the following templates existed, each would be rendered and added as a separate part to the message, with the corresponding content type. The same body hash is passed to each template.
· signup_notification.text.haml
· signup_notification.text.html.haml
· signup_notification.text.xml.builder
· signup_notification.text.yaml.erb
16.2.4 Attachments
Including attachments in emails is relatively simple, just use the method in your class.
1 classLateNotice < ActionMailer::Base
2 def late_timesheet(user, week_of)
3 @recipient = user.name
4 attachments["image.png"] = File.read("/images/image.png")
5 mail(
6 to: user.email,
7 from: "test@myapp.com",
8 subject: "[Time and Expenses] Timesheet notice"
9 )
10 end
11 end
If you wanted to attach the image inline, use attachments.inline.
attachments.inline["image.png"] = File.read("/images/image.png")
You can access this attachment in the template if need be via attachments hash, then calling url on that object for the image’s relative content id (cid:) path.
1 Dear #{@recipient},
2
3 Your timesheet is late, here's a photo depicting our sadness:
4
5 = image_tag attachments['image.png'].url, alt: "Invoicing"
16.2.5 Generating URLs
Generating application URLs is handled through named routes or using the url_for helper. Since mail does not have request context like controllers do, the host configuration option needs to be set. The best practice for this is to define them in the corresponding environment configuration although it can be defined on a per mailer basis.
# config/environments/production.rb
config.action_mailer.default_url_options = { host: 'accounting.com' }
In your mailer you can now generate your url. It is important to note that you cannot use the _path variation for your named routes since the must be rendered as absolute URLs.
1 classLateNotice < ActionMailer::Base
2 def late_timesheet(user, week_of)
3 @recipient = user.name
4 @link = user_url(user)
5 mail(
6 to: user.email,
7 from: "test@myapp.com",
8 subject: "[Time and Expenses] Timesheet notice"
9 )
10 end
11 end
When generating URLs through url_for, the controller and action also need to be specified. If you have provided a default host then the :only_path option must be provided to tell the helper to generate an absolute path.
= url_for(controller: "users", action: "update", only_path: false)
16.2.6 Mailer Layouts
Mailer layouts behave just like controller layouts. To be automatically recognized they need to have the same name as the mailer itself. In our previous case would automatically be used for our HTML emails. You can also add custom layouts if your heart desires, either at the class level or as a render option.
1 classLateNotice < ActionMailer::Base
2 layout "alternative"
3
4 def late_timesheet(user, week_of)
5 mail(to: user.email) do |format|
6 format.html { render layout: "another" }
7 end
8 end
9 end
We’ve now talked extensively about preparing email messages for sending, but what about actually sending them to the recipients?
16.2.7 Sending an Email
Sending emails only involves getting a object from your mailer and delivering it.
1 aslak = User.find_by(name: "Aslak Hellesoy")
2 message = LateNotice.late_timesheet(aslak, 1.week.ago)
3 message.deliver
16.2.8 Callbacks
As of Rails 4, the ability to define action callbacks for a mailer was added. Like their Action Controller counterparts, one could specify before_action, after_action and around_action callbacks to run shared pre and post processing code within a mailer.
Callbacks can accept one or more symbols, representing a matching method in the mailer class:
before_action :set_headers
Or you can pass the callback a block to execute, like this:
before_action { logger.info "Sending out an email!" }
A common example of why you would use a callback in a mailer is to set inline attachments, such as images, that are used within the email template.
1 classLateNotice < ActionMailer::Base
2 before_action :set_inline_attachments
3
4 def late_timesheet(user, week_of)
5 @recipient = user.name
6 mail(
7 to: user.email,
8 from: "test@myapp.com",
9 subject: "[Time and Expenses] Timesheet notice"
10 )
11 end
12
13 protected
14
15 def set_inline_attachments
16 attachments["logo.png"] = File.read("/images/logo.png")
17 end
18 end
Action callbacks are covered in detail in Chapter 4 in the “Action Callbacks” section.
16.3 Receiving Emails
To receive emails, you need to write a public method named receive on one of your application’s ActionMailer::Base subclasses. It will take a Mail::Message82 object instance as its single parameter. When there is incoming email to handle, you call an instance method named receive on your Mailer class. The raw email string is converted into a Mail::Message object automatically and your receive method is invoked for further processing. You don’t have to implement the receive class method yourself, it is inherited from ActionMailer::Base.83
That’s all pretty confusing to explain, but simple in practice. Listing 16.2 shows an example.
16.2: The simple MessageArchiver mailer class with a receive method
1 classMessageArchiver < ActionMailer::Base
2
3 def receive(email)
4 person = Person.where(email: email.to.first).first!
5 person.emails.create(
6 subject: email.subject,
7 body: email.body
8 )
9 end
10 end
The receive class method can be the target for a Postfix recipe or any other mail-handler process that can pipe the contents of the email to another process. The rails runner command makes it easy to handle incoming mail:
$ rails runner 'MessageArchiver.receive(STDIN.read)'
That way, when a message is received, the receive class method would be fed the raw string content of the incoming email via STDIN.
16.3.1 Handling Incoming Attachments
Processing files attached to incoming email messages is just a matter of using the attachments attribute of Mail::Message, as in Listing 16.3. This example assumes that you have a Person class, with a has_many association photos, that contains a Carrierwave attachment.84
1 classPhotoByEmail < ActionMailer::Base
2
3 def receive(email)
4 from = email.from.first
5 person = Person.where(email: from).first
6 logger.warn("Person not found [#{from}]") andreturnunless person
7
8 if email.has_attachments?
9 email.attachments.each do |file|
10 person.photos.create(asset: file)
11 end
12 end
13 end
14 end
There’s not much more to it than that, except of course to wrestle with the configuration of your mail-processor (outside of Rails) since they are notoriously difficult to configure.85 After you have your mail-processor calling the rails runner command correctly, add a crontab so that incoming mail is handled about every five minutes or so, depending on the needs of your application.
16.4 Server Configuration
Most of the time, you don’t have to configure anything specifically to get mail sending to work, because your production server will have sendmail installed and Action Mailer will happily use it to send emails.
If you don’t have sendmail installed on your server, you can try setting up Rails to send email directly via SMTP. The ActionMailer::Base class has a hash named smtp_settings that holds configuration information. The settings here will vary depending on the SMTP server that you use.
The sample (as shown in Listing 16.3) demonstrates the SMTP server settings that are available (and their default values). You’ll want to add similar code to your config/environment.rb file:
16.3: SMTP settings for ActionMailer
1 ActionMailer::Base.smtp_settings = {
2 address: 'smtp.yourserver.com', # default: localhost
3 port: 25, # default: 25
4 domain: 'yourserver.com', # default: localhost.localdomain
5 user_name: 'user', # no default
6 password: 'password', # no default
7 authentication: :plain # :plain, :login or :cram_md5
8 }
16.5 Testing Email Content
Ben Mabey’s email_spec86 gem provides a nice way to test your mailers using RSpec. Add it to your Gemfile and first make the following additions to your spec/spec_helper.rb.
1 RSpec.configure do |config|
2 config.include(EmailSpec::Helpers)
3 config.include(EmailSpec::Matchers)
4 end
Mailer specs reside in spec/mailers, and email_spec provides convenience matchers for asserting that the mailer contains the right attributes.
reply_to
Checks the reply-to value.
deliver_to
Verifies the recipient.
deliver_from
Assertion for the sender.
bcc_to
Verifies the Bcc.
cc_to
Verifies the Cc.
have_subject
Performs matching of the subject text.
include_email_with_subject
Performs matching of the subject text in multiple emails.
have_body_text
Match for text in the body.
have_header
Check for a matching email header.
These matchers can then be used to assert that the generated email has the correct content included in it.
1 require "spec_helper"
2
3 describe InvoiceMailer do
4 let(:invoice) { Invoice.new(name: "Acme", email: "joe@example.com") }
5
6 describe "#create_late" do
7 subject(:email) { InvoiceMailer.create_late(invoice) }
8
9 it "delivers to the invoice email" do
10 expect(email).to deliver_to("joe@example.com")
11 end
12
13 it "contains the invoice name" do
14 expect(email).to have_body_text(/Acme/)
15 end
16
17 it "has a late invoice subject" do
18 expect(email).to have_subject(/Late Invoice/)
19 end
20 end
21 end
If you’re attempting to test whether or not the mailer gets called and sends the email, it is recommended to simply check via a mock that the deliver method got executed.
16.6 Previews
By default in Rails, all email messages sent in development via Action Mailer are delivered in test mode. This means if you send an email from your application, the output of that message would display in your development log. While this can show you if the output is correct, it does not indicate if the email message is rendered correctly. A way around this would be to connect your development environment to an actual SMTP server. Even though this would allow you to view the email in your mail client of choice, it’s also a very bad idea, as you could potentially email real people.
New to Action Mailer as of Rails 4.1 is previews, which provides a means of rendering plain text and HTML mail templates in your browser without having to deliver them.
To create a preview, define a class that inherits from ActionMailer::Preview. Methods defined within the class must return a Mail::Message object, which can be created by calling a mailer method.
1 classLateNoticePreview < ActionMailer::Preview
2 def late_timesheet
3 user = FactoryGirl.create(:user)
4 LateNotice.late_timesheet(user, 1.week.ago)
5 end
6 end
By default, all previews are located in the test/mailers/previews directory, however this directory path can be overridden using the preview_path configuration option.
1 # For those using RSpec
2 config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews"
To obtain a listing of all Action Mailer previews available within your application, navigate to http://localhost:3000/rails/mailers/ while running a local development server instance.
16.7 Conclusion
In this chapter, we learned how Rails makes sending and receiving email easy. With relatively little code, you can set up your application to send out email, even HTML email with inline graphics attachments. Receiving email is even easier, except perhaps for setting up mail-processing scripts and cron jobs. We also briefly covered the configuration settings that go in your application’s environment specific configuration related to mail.