9780596521424
actionmailer_id326030.html

Chapter 11. Action Mailer

Action Mailer is a framework for sending and receiving email. It makes sending templated email, gracefully degrading HTML email, email with attachments, and receiving email, simple, while abstracting away complex issues like MIME encoding and quoting. This chapter will cover all the most important aspects of Action Mailer including the generation of mailers, sending email, receiving email, and the configuration and testing of mailers.

Sending Email

A mailer's job is to encapsulate the creation and delivery of the different email messages that you need for a particular component of your application. For example, an OrderMailer would define all of the email messages for communicating with a customer regarding an order they've placed at your online store.

There are two parts to a mailer: the mailer class and the view. The mailer class is stored in app/mailers. The mailer's views are stored in a subdirectory, named after the mailer, beneath app/views with the rest of your application's views. Therefore, the OrderMailer class would be defined in app/mailers/order_mailer.rb and it would look for its view templates in the app/views/order_mailer directory.

Action Mailer does not send deliver email to the Mail Transfer Agent (MTA) itself, but instead leverages the excellent and RFC 2822 compliant mail library for Ruby. The mail library handles all of the heavy lifting including parsing, generating, and delivering email messages to the MTA.

Because mail is a library for creating and generating email messages, it has to be able to deal with complexities of email addresses themselves. Email addresses come in many formats, but thankfully RFC 2822 describes proper email address formatting. Email addresses can be provided to your mailers in any address format that is compliant with the RFC 2822 Address Specification. RFC 2822 addresses take the general form:

"Full Name or Phrase" <username@host> (Comment Area)

Here are some examples of some valid RFC 2822 email addresses:

joe@example.com
Joe Smith <joe@example.com>
Joe Smith <joe@example.com> (Product Manager)

Mailers

You define each different mail that you want to send in a mailer class through what are called mailer actions. A mailer action is just an instance method defined in the mailer class. The body of the mailer action is where you set up instance variables that will be passed to the view template for rendering, add attachments to the email, and set up the headers for the mail message.

Rails provides a generator for creating new mailers. The generator is passed the mailer name in underscored or camel case format followed by zero or more actions:

rails generate mailer name [action...]

The generator creates the following files:

  • mailer class in app/mailers directory.

  • view templates for each action in app/views/name directory.

  • unit test stub in test/functional directory.

  • test fixtures for each action in test/fixtures/name directory.

Let's generate a new mailer class named OrderMailer with a single action confirmation:

$ rails generate mailer OrderMailer confirmation
      create  app/mailers/order_mailer.rb
      invoke  erb
      create    app/views/order_mailer
      create    app/views/order_mailer/confirmation.text.erb
      invoke  test_unit
      create    test/functional/order_mailer_test.rb

Now let's open up app/mailers/order_mailer.rb and take a look at the mailer that was generated for us:

class OrderMailer < ActionMailer::Base
  default :from => "from@example.com"

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.actionmailer.order_mailer.confirmation.subject
  #
  def confirmation
    @greeting = "Hi"

    mail :to => "to@example.org"
  end
end

Mailers are a subclass of ActionMailer::Base, which itself is a subclass of AbstractController::Base. Mailers and controllers both have the same superclass so that they are both act consistently with one another when rendering view templates and utilizing view helpers.

This mailer has a single action, confirmation. Mailer actions are instance methods on mailer classes, just as controller actions are instance methods on controller classes. Any instance variables set within the mailer action will be available in the view template for rendering. This works the in same way that you're used to with Action Controller. In this case, the @greeting instance variable will be passed to the view template, confirmation.text.erb, when it is rendered. Just like controllers, a mailer action will by default render a template with the same name as the mailer action being executed.

At the end of the confirmation mailer action the mail method is called. The mail method renders any view templates and create an instance of a Mail::Message object that is ready for delivery. The mail method takes a hash of email headers, which will be set on the resulting Mail::Message object.

Here’s a list of commonly used options to set email headers:

:bcc

The blind carbon copy recipients of the email. Can be a single email address or an array of email addresses. Sets the Bcc email header.

:cc

The carbon copy recipients of the email. Can be a single email address or an array of email addresses. Sets the Cc email header.

:date

The timestamp of when the email was sent. Sets the email's Date header.

:from

The email address the email will be sent from. Sets the From email header.

:reply_to

Tells the email client what email address to reply to. Sets the Reply-To email header.

:subject

The subject of the email. Sets the Subject email header. If the subject is omitted, Action Mailer will attempt to

:to

The addresses the email will be delivered to. Can be a single email address or an array of email addresses. Sets the To email header.

As can be seen from the example above, you can also set a default hash of mail headers with the class method default. In the example, the :from key in the mail headers hash will default to from@example.org in each mailer action, unless explicitly overridden. You can set a default for any mail header except for date.

Let's modify the generated mailer, app/mailers/order_mailer.rb, to be suitable for sending the confirmation email for simple PDF sales:

Example 11.1. A Simple Order Mailer

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    @order = order
    mail(:to => order.email, :subject => 'Your PDF purchase')
  end
end

As you can see, unlike controller actions, mailer actions can take arguments. In this case the mailer action takes an order and passes it to the view template for rendering. We'll see more how this works later in the chapter.

Rendering the Email Body

You can render the body of the email by rendering a view template, or by rendering an in-line ERB fragment or text in the mailer action. View templates can be rendered using any template handler registered with Rails such as ERB, Builder, or even a custom handler such as HAML. For more information on template handlers see ???.

Implicit Rendering

Action Mailer will implicitly render a template with the same name as the mailer action if it exists. To automatically render the email body from a view template you need to:

  • Create a view template in the mailer's views directory with the same name as the mailer action.

  • Give the template the file extension for the content type and template handler you want to use to render the view with such as .text.erb or .html.erb.

Just like in with controllers, you pass information from the action to the view using instance variables. Simply assign any information you require in the view to an instance variable in the mailer action and it will be available when the template is rendered.

The mailer from Example 11.1, “A Simple Order Mailer” renders the default ERB template confirmation.text.erb. For more information about rendering ERb templates see ???. Let's take a closer look at the template for the order confirmation email: app/views/order_mailer/confirmation.text.erb:

Thank you for your purchase of <%= number_to_currency(@order.total) %>.
Your PDF will be delivered to <%= @order.email %>.

The template looks just like any other ERB view template in Rails. We are able to access the @order instance variable in the view template because the mailer action set the @order instance variable. You'll also notice that the view is using the number_to_currency view helper provided by Action View. You can use all of the same view helpers you're accustomed to using in your controller views in your mailer views.

Explicit Rendering

Sometimes you need more flexibility or control over the rendering of templates. Since mailers and controllers share a common parent class, you have access to all of the same rendering infrastructure you're used to from developing your controller actions. In order to explicitly call render you need to pass the mail method a block. Within the block the method you call on the block variable dictates the content type of the resulting email. The method called on block variable can be any defined MIME type defined in the application. Refer ??? for the default list of MIME types provided by a Rails application. The method name for each MIME type is dictated by the extension column of the table. If you call multiple different methods on the block variable, such as text and html then you'll end up with a mult-part email. We'll look at mult-part emails in the section called “HTML Email”.

If you call the method for the MIME type without a block then Action Mailer will render a template for the content type:

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    @order = order
    mail(:to => order.email, :subject => 'Your PDF purchase') do |format|
      format.text
    end
  end
end

This is the explicit version of Example 11.1, “A Simple Order Mailer”. The mailer looks for a view template for the text MIME type in the mailer's view templates folder and renders it.

If you pass the method a block then you can call render like you're used to in Action Controller. This gives you a lot more flexibility because you can use the render options such as :action, :template, :file, :text, and :inline, just as you would in a call to render within a controller action. The following example renders some literal text as the email body:

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    mail(:to => order.email, :subject => 'Your PDF purchase') do |format|
      format.text { render :text => "Thank you for your order!" }
    end
  end
end

Creating and Sending Mail

In order to send an email you first have to create an instance of the class Mail::Message that encapsulates your message. This is done by calling a class method on the mailer with the same name as the mailer action you want to call. So, to create a mail object for the confirmation mailer action of the OrderMailer, you'd call the class method OrderMailer.confirmation. When you call the class method, which is generated dynamically for each action in a mailer, Action Mailer creates an instance of your mailer class, calls the mailer action with any provided arguments, and returns a Mail::Message object representing the message.

In order for our OrderMailer example to work with the we're going to need a simple Order class. Create the file app/models/order.rb and add this simple one liner that allows us to create simple orders with an email address and a total:

Order = Struct.new(:email, :total)

We included email and total attributes on the Order class, as those are the attributes we're displaying in the confirmation.text.erb view template we defined previously. Now we can use the mailer to create a mail instance for us and take a look at some of its properties. You can use the Rails console to try out the following code:

order = Order.new("cody@example.com", 19.95)
mail = OrderMailer.confirmation(order)

mail.to      # => ["cody@example.com"]
mail.from    # => ["order-confirm@example.com"]
mail.subject # => "Your PDF purchase"

Mail::Message objects know how to deliver themselves, so to actually deliver the message you call the deliver instance method on the mail object:

mail.deliver

We'll take a look at how to configure the delivery method later on in this section. You can view the encoded message, which is what is delivered to the MTA, of the message by calling the encoded method on the mail object:

mail.encoded

Here is what the output of the call to encoded looks like for the example on my system:

Date: Sun, 18 Sep 2011 01:21:55 -0400
From: order-confirm@example.com
To: cody@example.com
Message-ID: <4e757ff37c95d_e5c48582c1a483471@Sequoia.local.mail>
Subject: Your PDF purchase
Mime-Version: 1.0
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

Thank you for your purchase of $19.95.
Your PDF will be delivered to cody@example.com.

Generating URIs

Action Mailer also includes support for generating URIs using helpers that use url_for and named routes in mailers and views. See the section called “Named Routes” for complete coverage of named routes.

You can use view helpers that utilize url_for and named routes just as you would in your controller views. Here we render a URI using url_for and a hash of routing options:

Your account has been suspended.
Log in at: <%= url_for(:controller => 'accounts',
                       :action     => 'show',
                       :id         => @account %>.

One thing to note is that if you're using a hash of routing options you must always pass in the :controller option, as the mailer doesn't know what the current controller is for a request.

This next example uses a named route instead of a hash of routing options to generate the URI:

Your account has been suspended.
Log in at: <%= account_url(@account) %>.

Tip

Make sure you always generate absolute URIs in your email messages. Relative URIs don't work in email because there is no current resource for the URI to be relative to. This means that you'll always want to use the name_of_route_url form of named routes, which generates an absolute URI, and not the name_of_route_path form, which generates a relative URI.

Everything works as you'd expect if you're generating the email during the normal Rails request / response lifecycle. When generating URIs during a normal request, Rails uses the request object to determine what the host portion of the generated URI will be. However, without a request object, Rails doesn't know what to use as the host of generated URIs and will raise an exception.

This means that to generate URIs in mail messages outside of the normal request / response lifecycle, such as in a background job or from the Rails console, you need to provide the :host option so that the URI can be correctly generated. So, our example for a hash of routing options changes to:

Your account has been suspended.
Log in at: <%= url_for(:controller => 'accounts',
                       :action     => 'show',
                       :id         => @account,
                       :host       => 'example.com') %>.

And the named route example changes to:

Your account has been suspended.
Log in at: <%= account_url(@account, :host => 'example.com') %>.

This works, but as you can tell already, will quickly get tedious. Instead of passing in the :host option to every url_for or named route call you can instead set a default :host for all of your mailers. You can do this by setting the ActionMailer::Base.default_url_options accessor to a hash of default URI options:

ActionMailer::Base.default_url_options[:host] = 'example.com'

Or you can set the config.action_mailer.default_url_options configuration setting in your applications environment configuration:

config.action_mailer.default_url_options = { :host => 'example.com' }

See the section called “Action Mailer Configuration” for more about configuring Action Mailer in your Rails application.

Note

If you set a default :host for your mailers then you will want to use the :only_path => false option when using url_for. This will ensure the generation of absolute URIs. By default, the url_for view helper will generate a relative URI when it is not explicitly passed the :host option. Unfortunately, setting :only_path => false in the default_url_options does not work because the url_for view helper will always override the default value.

HTML Email

Sending email in HTML format allows for a much richer experience than simple plain text email. With HTML email you can style the email content to match your corporate look and feel. Most email clients today support rendering HTML email messages. Action Mailer makes sending very rich HTML email messages extremely simple. To send the confirmation email from Example 11.1, “A Simple Order Mailer” in HTML format, all you need to do is rename the view template from confirmation.text.erb to confirmation.html.erb and place HTML content in the view. Action Mailer will automatically change the content type of the email to text/html for you.

However, sending only HTML content in an email can be problematic when the recipient, such as a cellphone, can't render it. It is also impossible to know what type of device will be receiving the email you send. The solution to this problem is the multipart/alternative MIME type. Setting the content type of an email to multipart/alternative indicates that the body of the email contains the message in multiple alternative formats. This allows you to send a single email with the body in both plain-text and HTML, and instructs the recipient device to display the message in the format that it supports the best.

With Action Mailer all you have to do to take advantage of the multipart/alternative MIME type is create a different view template for each alternative representation of the email body. So, to create a mailer action that creates gracefully degrading HTML email with both the text/plain and text/html content in the body we'd create the following two view templates: app/views/order_mailer/confirmation.text.erb and app/views/order_mailer/confirmation.html.erb.

Action Mailer automatically detects that there are alternative representations of the email body and handles everything for you. This means that you don't even need to modify the code in your mailer action to make this work. Each alternate representation of the email body will be included in the email, allowing the recipient device to render the most appropriate format. However, if you want to be explicit you can declare that you're supporting multiple formats in your mailer action:

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    @order = order
    mail(:to => order.email, :subject => 'Your PDF purchase') do |format|
      format.text
      format.html
    end
  end
end

Adding Attachments

Adding attachments to email messages couldn't be easier with Action Mailer. All you have to do to add attachments to an email message is to add content to the hash-like attachments accessor available within mailer actions. You set a key in the attributes hash to either the content of the attachment or a hash of data in the event you need to manually specify any of the attributes of the attachment. Some examples will clarify how this works. Let's add an mp3 file to the order confirmation email we defined earlier:

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    @order = order
    
    attachments['sample.mp3'] = File.read(Rails.root + 'files/sample.mp3')
    mail(:to => order.email, :subject => 'Your PDF purchase')
  end
end

As you can see, we just set the sample.mp3 key in the attachments hash to the binary data of the mp3 file we want to send as the attachment. The mail library uses the file name of the attachment you set to automatically set the Content-Type, Content-Disposition, and Content-Transfer-Encoding of the attachment for you. It will also automatically Base64 encode the attachment's data. If you need to specify any of the properties of the attachment manually then you can set the attachment to a hash of the attachment's attributes and content:

class OrderMailer < ActionMailer::Base
  default :from => 'order-confirm@example.com'
  
  def confirmation(order)
    @order = order
    attachments['sample.mp3'] = {
      :mime_type => 'audio/mpeg',
      :content => File.read(Rails.root + 'files/sample.mp3')
    }
    mail(:to => order.email, :subject => 'Your PDF purchase')
  end
end

Configuration

There is very little configuration that needs to be performed on Action Mailer. All settings can be set either on ActionMailer::Base or through the environment's configuration via the config.action_mailer settings. The main settings are as follows:

delivery_method

The method of delivering email. One of :smtp, :sendmail, :file, or :test. Defaults to :smtp.

perform_deliveries

Whether or not to actually perform email delivery. Defaults to true. You may want to set this to false in your development environment.

raise_delivery_errors

Whether or not to raise an exception when email delivery fails. Defaults to true, except in the development environment where it is set to false.

So, for example, to set the delivery method to Sendmail you could set the class accessor on ActionMailer::Base:

ActionMailer::Base.delivery_method = :sendmail

Or you could configure the setting in the Rails application's environment:

config.action_mailer.delivery_method = :sendmail

All of the Action Mailer settings follow this same pattern. Now let's look more closely at the various delivery methods available in Action Mailer.

Action Mailer supports email delivery via four different email delivery methods: delivery via SMTP, delivery via Sendmail, delivery to a file, and test delivery. By default Action Mailer will deliver via SMTP on port 25 of localhost. To choose a method of email delivery you set the ActionMailer::Base.delivery_method attribute or config.action_mailer.delivery_method environment setting. You can configure the delivery method to one of four symbols: :smtp, :sendmail, :file, or :test. Based on the delivery method chosen you can configure the options that go along with the delivery method. There is a class settings hash for each delivery method on ActionMailer::Base with the name method_settings where method is the chosen delivery method. So for a delivery method of :smtp you'd set the ActionMailer::Base.smtp_settings hash to configure SMTP delivery.

SMTP Delivery Options

:address

The host address of the SMTP server. Defaults to localhost.

:port

The port to connect the SMTP server on. Defaults to 25.

:domain

The HELO domain the client identifies itself as. Defaults to localhost.localdomain.

:user_name

The user name to log in to the SMTP server with. Defaults to nil.

:password

The password to log in to the SMTP server with. Defaults to nil.

:authentication

One of :plain, :login, or :cram_md5.

:enable_starttls_auto

Detects whether or not to automatically detect STARTTLS in your SMTP server and use it if available. One of true or false. Defaults to true.

Gmail Delivery

Setting up delivery of mail through Gmail is useful during development and possibly even in production for small applications. Configuring Action Mailer to use Gmail for delivery is very simple. Use the following example and replace the :domain, :user_name, and :password options with values relevant to your domain and Gmail account.

config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
  :address => 'smtp.gmail.com',
  :domain => '<yourdomain>',
  :port => 587,
  :user_name => '<username>',
  :password => '<password>',
  :authentication => 'plain',
  :enable_starttls_auto => true
}

Place the configuration settings into the configuration for the environment you want to use Gmail delivery in, such as production.rb.

Sendmail Delivery Options

:location

The location of the Sendmail binary. Defaults to /usr/bin/sendmail.

:arguments

The arguments to pass to the Sendmail binary when delivery mail. Defaults to -i -t.

File Delivery Options

:location

The location to write the email messages to. Defaults to "#{Rails.root}/tmp/mails" if it Rails.root is defined and "#{Dir.tmpdir}/mails" otherwise.

Test Delivery Settings

There are no settings for test delivery. However, it is worth noting that email for the test environment’s mail back-end is delivered into the ActionMailer::Base.deliveries class accessor. This allows you to inspect the list of delivered email in test cases. Here’s an example within a typical mailer test:

class CustomerMailerTest < ActionMailer::TestCase
  def test_welcome_email
    customer = customers(:some_customer_in_your_fixtures)
 
    # Send the email and ensure it's been queued
    email = AuthorMailer.welcome_email(customer).deliver
    assert !ActionMailer::Base.deliveries.empty?
 
    # Test the body of the sent email contains what we expect it to
    assert_equal [customer.email], email.to
    assert_equal "Thanks for ordering your thing", email.subject
    assert_match(/You are pretty cool, #{customer.name}/, email.encoded)
  end
end

Receiving Email

Receiving email with Rails consists of two things: connecting a source of mail to your mailer and handling the mail in your application. Unfortunately, connecting a source of incoming mail to your application isn't that simple. However, once you've got your mailer connected to a source of new mail messages, things are pretty simple.

There are many techniques for connecting a mail server to your Rails application, but due to their complexity, they are out of the scope of this book. They are highly dependent on the specific mail server that you're running. There are several good resources on the topic, which you should definitely check out:

Keep in mind when reading the articles that the TMail library has been replaced by the Mail library in Rails 3.

Once you've figured out how to connect your Rails application to your mail server you need to work on the code that actually receives the mail in your application.

The code that connects your Rails application to the source of email will call a class method receive on your mailer class. The receive class method instantiates an instance of your mailer and calls the instance method receive. This means that in order to receive mail with your mailer you need to define an instance method on your mailer class named receive. The receive method takes a Mail::Message object as its only argument and is where you'll define the logic that handles the incoming mail.

Here is a simple example that receives an email and creates a new support ticket for an account based on the email's contents:

class DropboxMailHandler < ActionMailer::Base
  def receive(email)
    account = Account.find_by_dropbox(email.to.first)
    account.tickets.create(
      :subject => email.subject,
      :body    => email.body
    )
  end
end

As you can see, the receive method receives an instance of a Mail::Message object. You can then access any of the mail object's properties in your business logic that handles the incoming email. The dropbox attribute on the account is simply a special email address set for the application that is unique to a particular account.

Site last updated on: December 11, 2012 at 10:21:28 PM PST
Cover for Rails 3 in a Nutshell

View 2 comments

  1. Special_Dragonfly – Posted Dec. 9, 2009

    typo: ERB or ERb ?

  2. codyfauser – Posted Jan. 8, 2010

    Just fixed the typo, thanks for the heads up.

Add a comment