Previewing email in Rails with ActionMailer::Preview and Letter Opener

Developing email templates to be sent by our Rails application may sometimes be a long and tedious operation because under development you must continuously send to yourself the email in order to test the changes.

If you don’t want to waste your time, Rails provides a very helpful tool for those who need to build the layout for the emails of our application: ActionMailer::Preview. In fact, with this tool (available from Rails 4.1), you are able to generate the Rails mailer view without the need to actually send the email.
Continue reading “Previewing email in Rails with ActionMailer::Preview and Letter Opener”

Predictive algorithms are watching your shopping cart

Back in the times when it was an online bookseller and not today’s ecommerce giant, Amazon used to rely upon human-written book reviews for suggesting the next book purchase to its customers. However, when a predictive algorithm was experimentally introduced, Amazon quickly found out that the algorithm-generated recommendations led to more purchases than human-generated ones, at a fraction of the price.

Continue reading “Predictive algorithms are watching your shopping cart”

Preview emails for any user with Rails 4.1

Last night I was preparing a weekly email to remind my dear TimeSampler users about the work they have done in their previous week.

I started by adding a couple of previews one for the user that has actual stuff done (see an example below):

Weekly email for user with registered bursts

Setup a mailer (you can skip this)

The first thing you need to preview an email is to have a Mailer (yuk!), here’s a simple one:

# app/mailers/user_notifier

class UserNotifier < ActionMailer::Base
  default from: 'weekly-report@timesampler.com', reply_to: 'elia+timesampler-weekly-report@schito.me'
  helper DayHelper

  def weekly_report user
    @user         = user
    @project_days = project_days
    @days         = project_days.group_by(&:day).to_a.sort
    @end_time     = end_time
    @start_time   = start_time

    mail to: user.email, subject: "TimeSampler: Weekly activity report · week #{start_time.cweek}"
  end
end

And its view:

/ app/views/user_notifier
= render 'style'

%h1 TimeSampler: Weekly activity report · week #{@start_time.cweek}

%p
  Hi #{@user.some_name}!

- if @project_days.any?
  %p
    Here's what you've done during the past week (#{link_to "from #{@start_time} to #{@end_time}", journal_path}):
  .days
    - @days.sort.reverse.each do |(day, project_days)|
      = render 'day', day: day, project_days: project_days
- else
  = render 'blank_slate'

= render 'footer'

Setup a previewer

Then we need to add our previewer. Problem is that the default location for the previewers is test/mailers/previews
and as you may have guessed already we have no test dir in this app, instead we’ll put our
ActionMailer::Preview classes inside app/mailer_previews.

To do that let’s add the following line to config/environments/development:

Rails.application.configure do
  # …
  config.action_mailer.preview_path = "#{Rails.root}/app/mailer_previews"
end

Your previewer could be something like this:

class UserNotifierPreview < ActionMailer::Preview
  def weekly_report_empty
    UserNotifier.weekly_report(andrea)
  end

  def weekly_report_full
    UserNotifier.weekly_report(elia)
  end

  # …s
end

And have beautyful previews for your emails. For example, this is how the blankslate TimeSampler mail looks like:

blank slate mail preview

POWER PREVIEWING !!1!

Now what if you want to preview emails for arbitrary users without having to manually update the code?

No problem, luckly it’s Ruby! After a bit of inspection inside actionmailer and railties I found the right hook…

WARNING: rails hackery ahead

Rails asks the previewer if an email_exists? before trying to display it, so we’ll hook into that method and
auto-define a previewer instance method for each user in out system (please don’t try this with more than a hundred users).

Here teh codez:

class UserNotifierPreview < ActionMailer::Preview
  def self.email_exists?(*)
    User.all.each do |user|
      next if user.email.blank?
      email = user.email.gsub(/W/, '_')
      method_name = "weekly_report_#{email}"
      user_id = user.id

      define_method method_name do
        UserNotifier.weekly_report(User.find(user_id))
      end
    end

    super
  end

  # …
end

Of course it’s advisable to be careful and probably reduce the set of users you’re gonna prepare a preview method for.
In my case the development database is quite manageable and hence User.all is fine.

Final Pro-tip

You find cumbersome to dig the sources of the gems in your bundle even
if you setup the $EDITOR var in your bash and are a regular client of bundle open <my-gem>?

Then you can give a try to the Bundler bundle for TextMate2 which will give you an
incredible speed boost while perusing the gems in your current Gemfile.lock.

To get some more info on my TextMate2 setup you can read:

Read next: Pimp my TextMate2 — Ruby edition

opening gems from the current bundle