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/mailersdirectory.view templates for each
actioninapp/views/directory.nameunit test stub in
test/functionaldirectory.test fixtures for each
actionintest/fixtures/directory.name
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.rbNow 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:
:bccThe blind carbon copy recipients of the email. Can be a single email address or an array of email addresses. Sets the
Bccemail header.:ccThe carbon copy recipients of the email. Can be a single email address or an array of email addresses. Sets the
Ccemail header.:dateThe timestamp of when the email was sent. Sets the email's
Dateheader.:fromThe email address the email will be sent from. Sets the
Fromemail header.:reply_toTells the email client what email address to reply to. Sets the
Reply-Toemail header.:subjectThe subject of the email. Sets the
Subjectemail header. If the subject is omitted, Action Mailer will attempt to:toThe addresses the email will be delivered to. Can be a single email address or an array of email addresses. Sets the
Toemail 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
endAs 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.erbor.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
endThis 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
endCreating 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
endAdding 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
endAs 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
endConfiguration
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_methodThe method of delivering email. One of
:smtp,:sendmail,:file, or:test. Defaults to:smtp.perform_deliveriesWhether or not to actually perform email delivery. Defaults to
true. You may want to set this tofalsein your development environment.raise_delivery_errorsWhether or not to raise an exception when email delivery fails. Defaults to
true, except in the development environment where it is set tofalse.
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
:addressThe host address of the SMTP server. Defaults to
localhost.:portThe port to connect the SMTP server on. Defaults to
25.:domainThe HELO domain the client identifies itself as. Defaults to
localhost.localdomain.:user_nameThe user name to log in to the SMTP server with. Defaults to
nil.:passwordThe password to log in to the SMTP server with. Defaults to
nil.:authenticationOne of
:plain,:login, or:cram_md5.:enable_starttls_autoDetects whether or not to automatically detect
STARTTLSin your SMTP server and use it if available. One oftrueorfalse.Defaults totrue.
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
:locationThe location of the Sendmail binary. Defaults to
/usr/bin/sendmail.:argumentsThe arguments to pass to the Sendmail binary when delivery mail. Defaults to
-i -t.
File Delivery Options
:locationThe location to write the email messages to. Defaults to
"#{Rails.root}/tmp/mails"if itRails.rootis 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
endReceiving 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
endAs 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.





Add a comment



Add a comment