Rails Dispatch

Rails news delivered fresh

Presented by Engine Yard

In this week’s screencast, I take you through a tour of the new ActionMailer features in the context of a live application. Read on after the screencast for more in-depth information on the topics covered in the screencast.

Behind the Scenes

The biggest change (and in fact the change that even made the rewrite possible) was replacing the venerable TMail with the new Mail library. The prior versions of ActionMailer used TMail for all its email delivery needs, and because certain functionality was missing in TMail (such as auto quoting and encoding of fields, handling multipart emails smoothly, etc.), ActionMailer had grown a set of fairly complex and fragile methods to shoehorn in the missing functionality.

Mail provides the functionality that ActionMailer needs to do its thing, right out of the gem.

Because Rails could now hand over the responsibility for generating and creating emails to the Mail gem, the internal complexity of ActionMailer fell down to basically the same as your average Rails Controller, that is, getting information from another source, compiling a view (the email) and delivering it to a client.

With that done, it became possible for ActionMailer to reuse much more of the code and public API of ActionController. In Rails 3.0, ActionMailer has become just another kind of controller.

module ActionMailer
  class Base < AbstractController::Base
    #...
  end
end

This gives ActionMailer a whole bunch of cool features for very little effort, like being able to auto detect templates and build multipart emails effortlessly and do things like inline rendering of email bodies and the like.

No More Mysterious Methods

One of the more confusing parts of the prior ActionMailer API for new users was a number of mysterious methods you needed to use. These were methods that started with deliver_ or create_ and were not defined directly by the user but were instead dynamically generated by ActionMailer, one pair for each mailer method the user defined. So if you had a Mailer action called welcome_email, you were not allowed to just call welcome_email, instead you would have to call deliver_welcome_email to directly send the email or create_welcome_email to get a TMail object to play with. You would then go ahead and use these in your Rails App.

ActionMailer 3.0 deprecates these, you can still use them if you wish (or while upgrading), but ActionMailer will whinge and whine at you for forcing it to dredge up bad memories and encourage you to follow the shining path of light to true mailer happiness.

The replacement is much simpler: you send the mailer class the name of the mailer method you defined (welcome_email). This then returns a Mail::Message object to you which you can call :deliver on.

These methods you define (such as the welcome_email above) just return a simple Mail::Message object. You can then either do more processing on this object, or just tell it to deliver itself by calling #deliver on it.

How does this work? Well, first let Rails generate a mailer template for you:

$ rails g mailer notifier
    create  app/mailers/notifier.rb
    invoke  erb
    create    app/views/notifier
    invoke  test_unit
    create    test/functional/notifier_test.rb

Then we will flesh out our mailer class:

class Notifier < ActionMailer::Base
  default :from => "mikel@example.org"

  def welcome_email
    mail(:to => 'mikel@example.com', :subject => "G'Day Mate!")
  end
end

Now we can open up a Rails Console and play with it:

Loading development environment (Rails 3.0.0.beta3)
> mail = Notifier.welcome_email
 => #<Mail::Message:2174444520, Headers: <From: mikel@example.org>...>
> mail.deliver
 => #<Mail::Message:2174444520, Headers: <From: mikel@example.org>...>

Here you can see we are just calling Notifer.welcome_email and getting back a Mail::Message object, which we in turn tell “deliver thyself” and it does so. Mail::Message objects get instantiated with all the ActionMailer defaults on how to deliver themselves, what character sets to use and so forth, so we don’t need any more magic methods to get the object sent!

Of course, in 99% of cases, you just want to send the email and not worry any more about it. In those case you just do:

Loading development environment (Rails 3.0.0.beta3)
> Notifier.welcome_email.deliver
 => #<Mail::Message:2174444520, Headers: <From: mikel@example.org>...>

Which goes ahead and instantiates the email and then calls deliver on the resulting mail object. Simple right?

What is a Mail Object?

But, I am sure you are all asking with bated breath, what is this Mail::Message object? And what, more importantly, can I do with it?

Well, it is a real live Mail::Message object, it is not a subclass of Mail::Message or some half-baked ActionMailer/Mail lovechild, it’s just a plain old message.

This means that you can do anything you want with it before you tell it to deliver itself. You can set headers, change the body, alter its delivery method, rewrite sections of it, sign it with a PGP key or anything else you can think of.

You can find all the various functions of the Mail::Message object by reviewing the Mail rdoc from the source code.

Where to Send the Mail from?

There are a number of options on what part of the Rails stack to send your email from.

If your email sending is, say, a “Welcome to the System” type message, I’d just put it in the UsersController’s create action. Aside from being quite, it makes sense. The Controller is handling the request and telling the mailer what to do. Your user receives his response as HTML and in their email box.

To send off an email like the welcome email above, put the following line in the UsersController.

class UsersController < ApplicationController

  def create
    @user = User.new(params[:user])

    respond_to do |format|
      if @user.save
        Notifier.welcome_email(@user).deliver    # <======= This one!
        format.html { redirect_to(@user, :notice => 'User was successfully created.') }
        format.xml  { render :xml => @user, :status => :created, :location => @user }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
      end
    end
  end
  # More methods
end

Of course the above requires us to change our notifier to accept a user object:

class Notifier < ActionMailer::Base
  default :from => "mikel@example.org"

  def welcome_email(user)
    mail(:to => user.email, :subject => "G'Day Mate!")
  end
end

Which assumes your user has an email method.

The other thing to note with this style of delivery is that you have to know that the time that the email will take to send is not going to block the response for very long. If you are delivering to a local sendmail instance, this might not be a problem, but most likely you will want to look into background processing of email delivery.

Passing in Values to the Templates

Of course, the chances that you want to just send emails that send out static templates are pretty slim. You will most likely want to personalize the content of your emails. The nice thing is that ActionMailer now behaves exactly the same as ActionController in this respect. Simply define your instance variables in the method and they become available in your view.

Say we wanted to add the users name into the email body templates, first we would assign it to an instance variable:

class Notifier < ActionMailer::Base
  default :from => "mikel@example.org"

  def welcome_email(user)
    @name = user.name
    mail(:to => user.email, :subject => "G'Day Mate!")
  end
end

Then in the template, we could just reference it just like we would for ActionController:

Hi <%= @name %>,

Welcome to our new awesome system.

This system is going to change your life and so you will tell all your friends about us!

Regards,

MegloSys

I won’t go into much more detail. Just assign what values you want to instance variables and they will be available in all of your email templates.

Intelligent, Usable Defaults

ActionMailer, in keeping with the Rails ethos of convention over configuration, sets some defaults for you out of the box.

Delivery of Emails

Delivery defaults to an SMTP server running on your localhost on port 25. If you are using a Mac, this makes getting started and sending a couple of test emails ridiculously simple:

$ sudo postfix start
Password:
postfix/postfix-script: starting the Postfix mail system

At which point you should sudo tail -f /var/log/mail.log and watch the emails fly!

On other unix systems, a running sending only SMTP server is only as far away as a service start or firing up sendmail or postfix. On Windows boxes it is a little more involved, but you knew that already right?

Using other delivery methods (such as file, sendmail or test based) are only a configuration option away. The most common configuration changes are setting up an alternate SMTP delivery server other than localhost, such as GMail SMTP. To set this up, just edit the appropriate environments file and add the following:

Mailtest::Application.configure do
  # Other options
  config.action_mailer.smtp_settings = 
    {  :address              => "smtp.gmail.com",
       :port                 => 587,
       :domain               => 'your.host.and.domain.name',
       :user_name            => '<gmail username>',
       :password             => '<gmail password>',
       :authentication       => 'plain',
       :enable_starttls_auto => true  }
end

Once you have done the above, you will need to restart your Rails server to reload the email configurations to start sending email out via Gmail!

There is a word of warning here, you must keep in mind that if you are using an external mail agent, and you send your email during a web request from the user, that web request is going to hang while ActionMailer contacts GMail, authenticates, and sends your email. Depending on the phase of the moon and other scientific measures, this could take a few tenths of a second, to several seconds, so bear this in mind. For any serious system, look at DelayedJob or other backgrounding task managers.

Character Encodings

Another common issue is that not everyone uses UTF-8 or US-ASCII to send their multibyte characters. Never fear! While ActionMailer does default to UTF-8, it doesn’t mean you need to. Let’s just open up the Notifier mailer we made earlier and add in the :charset to our defaults.

class Notifier < ActionMailer::Base
  default :from => "mikel@example.org",
          :charset => 'Shift_JIS'

  def welcome_email
    @name = user.name
    mail(:to => user.email, :subject => "G'Day Mate!")
  end
end

Which then will produce:

Date: Wed, 21 Apr 2010 15:16:24 +1000
From: mikel@example.org
To: mikel@example.com
Message-ID: <4bce8a281d2e3_c405849a732c160b4@baci.lindsaar.net.mail>
Subject: G'Day Mate!
Mime-Version: 1.0
Content-Type: text/plain;
        charset=Shift_JIS
Content-Transfer-Encoding: base64
charset: Shift_JIS

44GK5YWD5rCX44Gn44GZ44GL44CCCg==

Nice.

Tell ’em ’bout tha ’ncoding Son!

ActionMailer used to handle encoding and decoding of header fields and bodies fairly well, but there were a bunch of edge cases, that, well, just broke. These commits moved ActionMailer from being a patch job at encoding and quoting, to implementing the new auto encoding methods from Mail.

What does “auto-encoding” mean exactly?

Basically, it lets you specify non US-ASCII characters inside of email bodies and header fields. Per RFC2822, all emails had to be encoded in 7bit US-ASCII, so if you were planing on sending ShiftJIS or UTF-8 characters that are not part of US-ASCII, you have to pre-encode them. With the old version of ActionMailer, you could assign certain multibyte characters into various header fields, such as address fields, subjects et al. However support was limited, and while it worked fairly often, it often felt like playing roulette with a loaded gun.

Mail, however, takes some good guesses with some sensible defaults to allow you to send a multibyte email without fuss. Here we will just send an email with the Japanese for “Hello”:

class Notifier
  default :from => "mikel@example.org"
  def welcome_email
    @name = user.name
    mail(:to => user.email, :subject => "今日は Mate!")
  end
end

And then in the body we could just ask “How are you?”:

お元気ですか。

The resulting email would look like this:

Date: Wed, 21 Apr 2010 12:30:57 +1000
From: mikel@example.org
To: mikel@example.com
Message-ID: <4bce6361d38c1_c2b3844c84c8904a@baci.lindsaar.net.mail>
Subject: =?UTF-8?Q?=E4=BB=8A=E6=97=A5=E3=81=AF=?= Mate!
Mime-Version: 1.0
Content-Type: text/plain;
        charset=UTF-8
Content-Transfer-Encoding: base64
charset: UTF-8

44GK5YWD5rCX44Gn44GZ44GL44CCCg==

See how the Mail object has auto encoded our body and subject to avoid injecting 8-bit text into our email message? You will also notice that Mail only encoded the non US-ASCII words in the Subject header field and left the “Mate!” in plain text.

Viewing that inside an email end user agent will correctly decode the text and display it in the original Japanese. Of course, you can insert any language. Mail will check and make sure the text is OK to send raw, otherwise will encode what it needs to so as to make the email RFC 2822 compliant.

You can also override the charset, the above uses UTF-8 by default, but just passing :charset => ‘ISO-8859-1’ into the defaults hash at the top of the Notifier model, or passing it in to the options hash into the individual calls to the mail method will alter this.

Multipart Emails

This is something that the new API just shines at. The cool thing about it is that if you choose the implicit rendering there is basically nothing you need to do beyond make the template. Seriously.

As ActionMailer now is part of the whole AbstractController thing, it automatically detects its “view” or email templates based on the name of the method executing, just like ActionController classes do. So if you have a Welcome email like we did above, and we want to also send a HTML markup version, we simply make another file called welcome.html.erb and style it up!

<h1 style="text-decoration:underline">Welcome to our new awesome system.</h1>

<p>Hi <%= @name %>,</p>

<p>
  This system is going to <strong>change your life</strong> and so you
  <em>will</em> tell all your friends about us!
</p>

<p>Regards,</p>

<p>MegloSys</p>

ActionMailer will detect both templates, and when you send the email, you will get the following back:

Date: Wed, 21 Apr 2010 15:47:55 +1000
From: mikel@example.org
To: mikel@example.com
Message-ID: <4bce918b5bfab_109038495bc6025064@baci.lindsaar.net.mail>
Subject: G'Day Mate!
Mime-Version: 1.0
Content-Type: multipart/alternative;
        boundary="--==_mimepart_4bce918b560b5_109038495bc602479b";
        charset=UTF-8
Content-Transfer-Encoding: 7bit
charset: UTF-8



----==_mimepart_4bce918b560b5_109038495bc602479b
Date: Wed, 21 Apr 2010 15:47:55 +1000
Mime-Version: 1.0
Content-Type: text/plain;
        charset=UTF-8
Content-Transfer-Encoding: 7bit
charset: UTF-8
Content-ID: <4bce918b59268_109038495bc60248bb@baci.lindsaar.net.mail>

Hi Mikel,

Welcome to our new awesome system.

This system is going to change your life and so you will tell all your friends about us!

Regards,

MegloSys


----==_mimepart_4bce918b560b5_109038495bc602479b
Date: Wed, 21 Apr 2010 15:47:55 +1000
Mime-Version: 1.0
Content-Type: text/html;
        charset=UTF-8
Content-Transfer-Encoding: 7bit
charset: UTF-8
Content-ID: <4bce918b5ab68_109038495bc60249a6@baci.lindsaar.net.mail>

<h1 style="text-decoration:underline">Welcome to our new awesome system.</h1>

<p>Hi Mikel,</p>

<p>
  This system is going to <strong>change your life</strong> and so you
  <em>will</em> tell all your friends about us!
</p>

<p>Regards,</p>

<p>MegloSys</p>

----==_mimepart_4bce918b560b5_109038495bc602479b--

See how ActionMailer has wrapped up your email into a multipart/alternative type and then added the plain text as well as HTML versions? Schweeeeet!

You can override the default template to render for each type in the same way that you would pick a format in ActionController:

class Notifier
  default :from => "mikel@example.org"
  def welcome_email
    @name = user.name
    mail(:to => user.email, :subject => "今日は Mate!") do |format|
      format.text { render :text => "Hello from Mikel!" }
      format.html # Render the normal template, welcome_email.html.erb
    end
  end
end

As you can see, the syntax borrows a lot from the ActionController render method, making it easier than ever to work with ActionMailer.

Attachments

Another very common task is sending an attachment, again ActionMailer with Mail makes some really useful assumptions that makes adding an attachment about as painless as it can be.

Say with every new user you want to send out our terms and conditions to try and scare them off before they get too involved with your business. How do you do this? Like this:

class Notifier
  default :from => "mikel@example.org"

  def welcome_email
    @name = user.name
    attachments['terms.pdf'] = File.read(Rails.root.join('docs/terms.pdf'))
    mail(:to => user.email, :subject => "今日は Mate!")
  end
end

And that is it.

ActionMailer will slurp up the terms.pdf file from the docs directory and pass it into the Mail::Message with the file name test.pdf. Mail will then guess (correctly) that this is probably a PDF file and set the mime type correctly. It will then change the email to a multipart/mixed, set the first part to the attachment, and the second part make a multipart/alternate inserting your HTML and Plain text parts in their correct order. The result is your user gets the email with the attachment.

But what if you need to over ride the mimetype to something else? Easy, just pass it in with a hash:

class Notifier
  default :from => "mikel@example.org"
  def welcome_email
    @name = user.name
    attachments['terms.pdf'] = {:data => File.read(Rails.root.join('docs/terms.adobe_pdf')),
                                :mime_type => 'application/pdf' }
    mail(:to => user.email, :subject => "今日は Mate!")
  end
end

This will stop Mail from trying to guess.

Mail by default sends all attachments Base64 encoded, and yes, you can change this too!

Wrapping Up

So that’s the new ActionMailer API in all its glory.

Are we done yet with ActionMailer? Absolutely not! But I think you can agree with me that we have come an awful long way.

The next things you all can look forward to are (1) smart inline attachments that let you add images to your HTML emails painlessly and (2) some work on the internals of Mail itself to make it trim, taut and terrific.

Further development is happening all the time, and as ActionMailer is now a wrapper of Mail, most of this development is happening in the Mail tree. If you want to help out, just jump onto the Mail Mailing List and make yourself known, or send me an email. I always post new changes to mail on my blog or via twitter so feel free to check those out too.