Sending Liquid-based Email with Rails

7:07 AM EDT Saturday, April 19 2008

Many applications send emails out to users when certain actions happen, such as placing an order. Sometimes the contents of those emails can change frequently, so it is a nice feature is to be able to give the administrative users of your system a way to change the contents of those emails.

One way to do it is to use the Liquid templating language. The reason Liquid is a good choice is that Liquid is "non-evaling", which means you can just execute arbitrary Ruby code from within a Liquid template. This isn't true of ERB, for example, where you could just put <% something_evil %> in the middle of a template and it would get executed. So if you were to give your users a way to edit the the template and it was using ERB, you would have a potential security problem.

So create a rails app:

$ rails liquid

Then make sure you have the liquid gem installed:

$ sudo gem install liquid

Then require liquid in your config/environment.rb (sidenote: edge rails has a better way of doing this)

require 'liquid'

Next we'll create the model and interface for letting users edit the mail templates:

$ script/generate scaffold mail_template name:string body:text
$ rake db:migrate

Now you can create a new mail template through the browser or just using IRB:

$ script/console
>> MailTemplate.create!(:name => "order_confirmation", 
  :body => "Dear {{name}},\n\nThank you for your order!")

There you can see the very basic syntax of a Liquid template. The {{name}} is a variable that will get replace with content when we render the template. Now we'll generate a mailer:

$ script/generate mailer OrderMailer order_confirmation

Now we have to modify the OrderMailer to use our Liquid template stored in the database. Edit app/models/order_mailer.rb so that it has the following contents:

class OrderMailer < ActionMailer::Base

  def order_confirmation(sent_at = Time.now)
    @from       = 'orders@yoursite.com'
    @recipients = 'some_customer@whatever.com'
    @subject    = 'Order Confirmation'
    @body       = {"name" => "Paul"}
    @sent_on    = sent_at
    @headers    = {}
  end

  def render_message(method_name, body)
    mail_template = MailTemplate.find_by_name(method_name)
    template = Liquid::Template.parse(mail_template.body)
    template.render body
  end

end

The order_confirmation method should look familiar, it is the almost the same as it would be for any regular mailer. One caveat is that Liquid expects the keys in the context hash that you pass to it to be strings, not symbols. The @body instance variable is the context hash that will be passed to Liquid. Those are the variables that you will have access to when the template renders.

The real work is happening in the render_message method. This is the method that the mailer calls when delivering your message. Normally you don't need to override this method, it takes care of finding the ERB template and rendering it for you. We can override it here to look for our Liquid template from the database. The first argument to the method will be set to the method name of the mailer, which in this case is order_confirmation. The second argument will be the value that we set @body to in the order_confirmation method. This makes for a pretty clean way of doing this, hooking in our Liquid functionality using the standard object-oriented technique of overriding a method, no monkey patching required, which should make Avdi proud!

Make sure you've started the app with script/server and then go into script/console and enter this:

>> OrderMailer.deliver_order_confirmation

Now in the output from script/server, you should see something like this:

Sent mail:
 Date: Sat, 19 Apr 2008 06:41:53 -0400
From: orders@yoursite.com
To: some_customer@whatever.com
Subject: Order Confirmation
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8

Dear Paul,

Thank you for your order!

Editorial: 4 years ago, Matt Raible posted an article to the The Server Side titled Sending Velocity-based Email with Spring. Consider this the Ruby equivalent to that article. Compare the amount of code/configuration required to accompish this task in Java/J2EE vs. Ruby on Rails. Also, the Ruby version is more complicated because it involves storing/retrieving the templates from the database rather than the file system. If we were just retrieving the templates from the file system, this probably would have been a 2 line article. This is just another example of how much simpler Rails makes things when compared to Java/J2EE.

Posted in  | Tags Rails, Liquid, Ruby | 1 Comments