- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index
Chapter 19
Action Mailer
Action Mailer is a simple Rails component that allows your applications to send and receive e-mail. Using Action Mailer, your online store could send out order confirmations, and your incident tracking system could automatically log problems submitted to a particular e-mail address.
19.1 Sending E-mail
Before you start sending e-mail you’ll need to configure Action Mailer. Its default configuration works on some hosts, but you’ll want to create your own configuration anyway, just to make it an explicit part of your application.
E-mail Configuration
E-mail configuration is part of a Rails application’s environment. If you want to use the same configuration for development, testing, and production, add the configuration to environment.rb in the config directory; otherwise, add different configurations to the appropriate files in the config/environments directory.
You first have to decide how you want mail delivered.
ActionMailer::Base.delivery_method = :smtp | :sendmail | :test
The :test setting is great for unit and functional testing. E-mail will not be delivered, but instead will be appended to an array (accessible as ActionMailer::Base.deliveries). This is the default delivery method in the test environment.
The :sendmail setting delegates mail delivery to your local system’s sendmail program, which is assumed to be in /usr/sbin. This delivery mechanism is
Prepared exclusively for Rida Al Barazi
SENDING E-MAIL 400
not particularly portable, as sendmail is not always installed in this directory on different operating systems. It also relies on your local sendmail supporting the -i and -t command options.
You achieve more portability by leaving this option at its default value of :smtp. If you do so, though, you’ll need also to specify some additional configuration to tell Action Mailer where to find an SMTP server to handle your outgoing e-mail. This may be the machine running your web application, or it may be a separate box (perhaps at your ISP if you’re running Rails in a noncorporate environment). Your system administrator will be able to give you the settings for these parameters. You may also be able to determine them from your own mail client’s configuration.
ActionMailer::Base.server_settings = {
:address |
=> "domain.of.smtp.host.net", |
:port |
=> 25, |
:domain |
=> "domain.of.sender.net", |
:authentication => :login, |
|
:user_name |
=> "dave", |
:password |
=> "secret", |
}
:address => and :port =>
Determines the address and port of the SMTP server you’ll be using. These default to localhost and 25, respectively.
:domain =>
The domain that the mailer should use when identifying itself to the server. This is called the HELO domain (because HELO is the command the client sends to the server to initiate a connection). You should normally use the top-level domain name of the machine sending the e-mail, but this depends on the settings of your SMTP server (some don’t check, and some check to try to reduce spam and socalled open-relay issues).
:authentication =>
One of :plain, :login, or :cram_md5. Your server administrator will help choose the right option. There is currently no way of using TLS (SSL) to connect to a mail server from Rails. This parameter should be omitted if your server does not require authentication.
:user_name => and :password =>
Required if :authentication is set.
Other configuration options apply regardless of the delivery mechanism chosen.
ActionMailer::Base.perform_deliveries = true | false
Prepared exclusively for Rida Al Barazi
Report erratum
SENDING E-MAIL 401
If perform_deliveries is true (the default), mail will be delivered normally. If false, requests to deliver mail will be silently ignored. This might be useful to disable e-mail while testing.
ActionMailer::Base.raise_delivery_errors = true | false
If raise_delivery_errors is true (the default), any errors that occur when initially sending the e-mail will raise an exception back to your application. If false, errors will be ignored. Remember that not all e-mail errors are immediate—an e-mail might bounce four days after you send it, and your application will (you hope) have moved on by then.
ActionMailer::Base.default_charset = "utf-8"
The character set used for new e-mail.
Sending E-mail
Now that we’ve got everything configured, let’s write some code to send e-mails.
By now you shouldn’t be surprised that Rails has a generator script to create mailers. What might be surprising is where it creates them. In Rails, a mailer is a class that’s stored in the app/models directory. It contains one or more methods, each method corresponding to an e-mail template. To create the body of the e-mail, these methods in turn use views (in just the same way that controller actions use views to create HTML and XML). So, let’s create a mailer for our store application. We’ll use it to send two different types of e-mail: one when an order is placed and a second when the order ships. The generate mailer script takes the name of the mailer class, along with the names of the e-mail action methods.
depot> ruby script/generate mailer OrderMailer confirm sent
exists |
app/models/ |
create |
app/views/order_mailer |
exists |
test/unit/ |
create |
test/fixtures/order_mailers |
create |
app/models/order_mailer.rb |
create test/unit/order_mailer_test.rb |
|
create |
app/views/order_mailer/confirm.rhtml |
create |
test/fixtures/order_mailers/confirm |
create |
app/views/order_mailer/sent.rhtml |
create |
test/fixtures/order_mailers/sent |
Notice that we’ve created an OrderMailer class in app/models and two template files, one for each e-mail type, in app/views/order_mailer. (We also created a bunch of test-related files—we’ll look into these later in Section 19.3, Testing E-mail, on page 408).
Each method in the mailer class is responsible for setting up the environment for sending a particular e-mail. It does this by setting up instance
Prepared exclusively for Rida Al Barazi
Report erratum
SENDING E-MAIL 402
variables containing data for the e-mail’s header and body. Let’s look at an example before going into the details. Here’s the code that was generated for our OrderMailer class.
class OrderMailer < ActionMailer::Base
def confirm(sent_at = Time.now)
@subject |
= |
'OrderMailer#confirm' |
@recipients = |
'' |
|
@from |
= |
'' |
@sent_on |
= |
sent_at |
@headers |
= |
{} |
@body |
= |
{} |
end
def sent(sent_at = Time.now) @subject = 'OrderMailer#sent'
# ... same as above ...
end end
Apart from @body, which we’ll discuss in a second, the instance variables all set up the envelope and header of the e-mail that’s to be created:
@bcc = array or string
Blind-copy recipients, using the same format as @recipients.
@cc = array or string
Carbon-copy recipients, using the same format as @recipients.
@charset = string
The characterset used in the e-mail’s Content-Type: header. Defaults to the default_charset attribute in server_settings, or "utf-8".
@from = array or string
One or more e-mail addresses to appear on the From: line, using the same format is @recipients. You’ll probably want to use the same domain name in these addresses as the domain you configured in server_settings.
@headers = hash
A hash of header name/value pairs, used to add arbitrary header lines to the e-mail.
@headers["Organization"] = "Pragmatic Programmers, LLC"
@recipients = array or string
One or more e-mail addresses for recipients. These may be simple addresses, such as dave@pragprog.com, or some identifying phrase followed by the e-mail address in angle brackets.
@recipients = [ "andy@pragprog.com",
"Dave Thomas <dave@pragprog.com>" ]
Prepared exclusively for Rida Al Barazi
Report erratum
SENDING E-MAIL 403
@sent_on = time
ATime object that sets the e-mail’s Date: header. If not specified, the current date and time will be used.
@subject = string
The subject line for the e-mail.
The @body is a hash, used to pass values to the template that contains the e-mail. We’ll see how that works shortly.
|
E-mail Templates |
|
|
The generate script created two e-mail templates in app/views/order_mailer, |
|
|
one for each action in the OrderMailer class. These are regular ERb rhtml |
|
|
files. We’ll use them to create plain-text e-mails (we’ll see later how to cre- |
|
|
ate HTML e-mail). As with the templates we use to create our application’s |
|
|
web pages, the files contain a combination of static text and dynamic con- |
|
|
tent. Here’s the template in confirm.rhtml that is sent to confirm an e-mail. |
|
File 147 |
Dear <%= @order.name %> |
|
|
Thank you for your recent order from The Pragmatic Store. |
|
|
You ordered the following items: |
|
|
<%= render(:partial => "./line_item", :collection => @order.line_items) %> |
|
|
We'll send you a separate e-mail when your order ships. |
|
|
There’s one small wrinkle in this template. We have to give render( ) the |
|
|
explicit path to the template (the leading ./) as we’re not invoking the view |
|
|
from a real controller, and Rails can’t guess the default location. |
|
|
The partial template that renders a line item formats a single line with the |
|
|
item quantity and the title. Because we’re in a template, all the regular |
|
|
helper methods, such as truncate( ), are available. |
|
File 146 |
<%= sprintf("%2d x %s", |
|
|
line_item.quantity, |
|
|
truncate(line_item.product.title, 50)) %> |
|
|
We now have to go back and fill in the confirm( ) method in the OrderMailer |
|
|
class. |
|
File 144 |
class OrderMailer < ActionMailer::Base |
|
|
def confirm(order) |
|
|
@subject |
= "Pragmatic Store Order Confirmation" |
|
@recipients |
= order.email |
|
@from |
= 'orders@pragprog.com' |
@body["order"] = order end
end
Prepared exclusively for Rida Al Barazi
Report erratum
|
SENDING E-MAIL |
404 |
|
Now we get to see what the @body hash does: values set into it are available |
|
|
as instance variables in the template. In this case, the order object will be |
|
|
stored into @order. |
|
|
Generating E-mails |
|
|
Now that we have our template set up and our mailer method defined, |
|
|
we can use them in our regular controllers to create and/or send emails. |
|
|
However, we don’t call the method directly. That’s because there are two |
|
|
different ways you can create e-mail from within Rails: you can create an |
|
|
e-mail as an object, or you can deliver an e-mail to its recipents. To access |
|
|
these functions, we call class methods called create_xxx and deliver_xxx, |
|
|
where xxx is the name of the instance method we wrote in OrderMailer. We |
|
|
pass to these class methods the parameter(s) that we’d like our instance |
|
|
methods to receive. To send an order confirmation e-mail, for example, we |
|
|
could call |
|
|
OrderMailer.deliver_confirm(order) |
|
|
To experiment with this without actually sending any e-mails, we can |
|
|
write a simple action that creates an e-mail and displays its contents in a |
|
|
browser window. |
|
File 142 |
class TestController < ApplicationController |
|
|
def create_order |
|
|
order = Order.find_by_name("Dave Thomas") |
|
email = OrderMailer.create_confirm(order) render(:text => "<pre>" + email.encoded + "</pre>")
end end
The create_confirm( ) call invokes our confirm( ) instance method to set up the details of an e-mail. Our template is used to generate the body text. The body, along with the header information, gets added to a new e-mail object, which create_confirm( ) returns. The object is an instance of class TMail::Mail.1 The email.encoded( ) call returns the text of the e-mail we just created: our browser will show something like
Date: Fri, 29 Apr 2005 08:11:38 -0500
From: orders@pragprog.com
To: dave@pragprog.com
Subject: Pragmatic Store Order Confirmation
Content-Type: text/plain; charset=utf-8
Dear Dave Thomas
1TMail is Minero Aoki’s excellent e-mail library; a version ships with Rails.
Prepared exclusively for Rida Al Barazi
Report erratum
|
|
SENDING E-MAIL |
405 |
|
Thank you for your recent order from The Pragmatic Store. |
|
|
|
You ordered the following items: |
|
|
|
1 x Programming Ruby, 2nd Edition |
|
|
|
2 x Pragmatic Project Automation |
|
|
|
We'll send you a separate e-mail when your order ships. |
|
|
|
If we’d wanted to send the e-mail, rather than just create an e-mail object, |
|
|
|
we could have called OrderMailer.deliver_confirm(order). |
|
|
|
Delivering HTML-Format E-mail |
|
|
|
The simplest way of creating HTML e-mail is to create a template that |
|
|
|
generates HTML for the e-mail body and then set the content type on the |
|
|
|
TMail::Mail object to text/html before delivering the message. |
|
|
|
We’ll start by implementing the sent( ) method in OrderMailer. (In reality, |
|
|
|
there’s so much commonality between this method and the original con- |
|
|
|
firm( ) method that we’d probably refactor both to use a shared helper.) |
|
|
File 144 |
class OrderMailer < ActionMailer::Base |
|
|
|
def sent(order) |
|
|
|
@subject |
= "Pragmatic Order Shipped" |
|
|
@recipients |
= order.email |
|
|
@from |
= 'orders@pragprog.com' |
|
|
@body["order"] = order |
|
|
|
end |
|
|
|
end |
|
|
|
Next, we’ll write the sent.rhtml template. |
|
|
File 148 |
<h3>Pragmatic Order Shipped</h3> |
|
|
|
<p> |
|
|
|
This is just to let you know that we've |
|
|
|
shipped your recent order: |
|
|
|
</p> |
|
|
|
<table> |
|
|
|
<tr><th>Qty</th><th></th><th>Description</th></tr> |
|
|
|
<%= render(:partial => "./html_line_item", :collection => @order.line_items) %> |
|
|
|
</table> |
|
|
|
We’ll need a new partial template that generates table rows. This goes in |
|
|
|
the file _html_line_item.rhtml. |
|
|
File 145 |
<tr> |
|
|
|
<td><%= html_line_item.quantity %></td> |
|
<td>×</td>
<td><%= html_line_item.product.title %></td>
</tr>
Prepared exclusively for Rida Al Barazi
Report erratum