Once you start sending HTML email, odds are good that you’ll want to start sending pretty HTML email, complete with graphics. You can include HTML links to resources on your site, but many mail programs block those, because they’re potential violations of privacy. Instead, it often makes more sense to send the graphics as part of the email message, as a multipart attachment.
There are fewer and fewer people who deeply object to HTML email, but there are still a lot of people who get annoyed by huge messages that take a long time to download. If you wouldn’t feel comfortable sending a message to a relative on dial-up, you probably shouldn’t send it to anyone, unless they’ve specifically requested something they know is large.
ActionMailer can handle most of the logistics for this, using the same tools shown earlier, but it needs one additional library to make it work. From the command line, or the Heroku Gems & Plugins Manager, add the InlineAttachment gem:
gem install InlineAttachment
One of the nicer features of multipart email is that it can
include a plain-text version as well as HTML and graphics, so app/views/awards/show.html.erb can revert to
the checkbox-free version shown earlier in Example 17-5. Similarly,
because the awards_controller
no
longer needs to evaluate the checkbox, app/controllers/awards_controller.rb can
revert to the version shown in Example 17-2. (The code for this
example is in ch17/students012.)
Most of the work needed to send the message now falls into the
AwardMailer
model, in app/models/award_mailer.rb, and two views.
The new AwardMailer
has a much more
complicated certificate method, shown with changes highlighted in Example 17-10.
Example 17-10. A model for sending multipart email messages
class AwardMailer < ActionMailer::Base def certificate(award, email) subject award.name recipients email from 'School System <[email protected]>' sent_on Time.now content_type'multipart/alternative'
# explicitly enumerate our alternative parts, as we want control # for the HTML partpart :content_type => 'text/plain',
:body => render_message('certificate.text.plain.erb', :award => award)
# unique content identifier for the image we'll use - adjust domain for you@cid = Time.now.to_f.to_s + "[email protected]"
part 'multipart/related' do |p|
p.parts << ActionMailer::Part.new(:content_type => 'text/html',
:body => render_message('certificate.text.html.erb',
:award => award, :cid => @cid))
p.inline_attachment :content_type => "image/png",
:body => File.read(File.join(RAILS_ROOT, 'public', 'images', 'rails.png')),
:filename => "rails.png",
:cid => "<#{@cid}>"
end
end end
This is a very active model, with much more going on than its text
or HTML predecessors. The first change, which makes the rest possible,
is the shift in content_type
from
text/plain
or text/html
to the much more flexible multipart/alternative
.
When an email client gets a message with a MIME type of multipart/alternative
, it will look through
the message first before simply displaying it, choosing which part to
show the user based on the user’s preferences.
If you just want to send HTML email with multiple pieces,
multipart/related
is
probably a better choice, but the approach outlined here is definitely
more appealing if any of the recipients will be reading the message in
a plain text viewer. You can also use multipart/mixed
to send attachments.
The next few sections define the parts of multipart
. The first part
call defines the plain text message to
send, as the text message normally is, at the start of the
message:
part :content_type => 'text/plain', :body => render_message('certificate.text.plain.erb', :award => award)
It specifies a MIME content type of text/plain
. The :body
parameter calls render_message
, explicitly specifying a view
and passing it the same :award =>
award
parameter that the older version of certificate had used
in Example 17-3. The
view, in app/views/award_mailer/certificate.text.html.erb,
contains exactly the same contents as Example 17-4.
The next part
call is much more
complicated:
@cid = Time.now.to_f.to_s + "[email protected]"
part 'multipart/related' do |p|
p.parts << ActionMailer::Part.new(:content_type => 'text/html',
:body => render_message('certificate.text.html.erb',
:award => award, :cid => @cid))
p.inline_attachment :content_type => "image/png",
:body => File.read(File.join(RAILS_ROOT, 'public', 'images', 'rails.png')),
:filename => "rails.png",
:cid => "<#{@cid}>"
end
The @cid
variable provides a
Content-ID header for the attached graphic. Content-IDs are supposed to be
unique. The Time
object, here called
with now
to get the current time,
to_f
to convert that to a number of
seconds, and to_s
to turn the number
into a string, provides a reasonably unique identifier when placed at
the front of the string. The next piece is more like a filename. It
doesn’t have to be the same as the name of the file that’s sent, but
it’s simpler that way. The @
sign
separates what is effectively the filename from its source, so you
should replace example.com
with the
name of your own domain.
Once the @cid
variable is set,
the part
call begins, defining a
multipart/related
part of the
message. This represents the actual HTML with graphics combination. It
takes a block, using do |p|
to set
context for the calls which follow. The two calls inside of the block
both reference p
to add their
content.
The first references the p.parts
array, part of the TMail library Rails
uses to create and process email, and appends (<<
) to it a new ActionMailer::Part
. The parameters define the
contents of the part, setting the content_type
to text/html
and calling the render_message
method
again to use the certificate.text.html.erb view, shown in
Example 17-10, passing it
both the award
value and the @cid
value, which it needs to connect to the
graphic.
The second call is to the inline_attachment
method, which was the key piece the InlineAttachment gem provided. It also has
a content_type
parameter, set to
image/png
in this case to represent
the graphic. The :body
parameter
identifies which graphic. The File.read
method takes
in a file, at a location identified by the File.join
method and
its arguments. Why not just write something like RAILS_ROOTpublicimages
ails.png? Because
there are too many different ways to specify file locations on different
operating systems, and File.join
helps work around that problem. The :filename
argument specifies the filename that
should be provided for the file—it doesn’t have to be the same as the
original file. The :cid
argument
provides the Content-ID for the graphic as well. The HTML, generated by
the view shown in Example 17-11, will reference
the graphic by Content-ID, not by filename.
Example 17-11. The certificate.text.html.erb view for generating HTML inside a multipart email message, referencing a graphic through its Content-ID
<table width="100%" border="1" cellpadding="10">
<tr>
<td align="center">
<p>
School of Rails<br />
<img src="cid:<%= @cid %>" />
</p>
<br />
<br />
<h1><%=h @award.name %></h1>
<br />
<br />
<p>awarded to</p>
<br />
<br />
<h2><%=h @award.student.name %></h2>
<br />
<h3><%=h @award.year %></h3
</td>
</tr>
</table>
<br />
<br />
<hr />
<p>Courtesy of the School System!</p>
Now, if a user clicks on the “Email me this award” button on an award page, they’ll get a message, complete with a graphic, like that shown in Figure 17-7.
The Rails graphic happened to be around, but you can use any images you’d like. Each will need its own Content-ID, and you’ll need to specify the path for reaching them. Figure 17-8 illustrates the process of assembling, packaging, and sending this message.