Notify and Rails sitting in a tree

Here at dxw, we love GOV.UK Notify

Here at dxw, we love GOV.UK Notify. It’s a web service provided by the Government Digital Service (GDS) allowing the whole public sector to send emails, SMS and even letters. It’s free for email, and incredibly low cost for SMS and letters (your first 250,000 texts are free- brilliant for cash strapped services, who would otherwise have to use something like Twilio at 4p per SMS).

It’s really easy to integrate with, and there are a whole bunch of well-supported libraries for pretty much every popular programming language. In this blog post I’m going to go through a technique we used to make working with Notify even easier with our current tech stack. If you’re not technically minded, feel free to read no further. If you’re a developer (Ruby or otherwise), then read on…

We’re currently using Notify on the Teaching Vacancies Public Beta to allow applicants to receive alerts on new jobs. Notify is a perfect service for us to use, but we found ourselves with a couple of problems.

Notify provides support for some rudimentary templates. We can enter the email’s text and add variables in double brackets for personalisation – for example, if we enter ((first_name)) in the template, ((first_name)) will be replaced with the user’s first name when we send the email.

However, Notify’s templating language does not provide functionality for loops, so we can’t send multiple vacancies to the service and tell it to loop through them, outputting, for example, a job title, school and a link.

This led us to handle the logic in the Rails app itself. We built a simple template in Notify, with just a ((body)) variable and could then build a class that would take in the vacancies, build the email body and then send it as a variable to Notify.

This works pretty well, but as Teaching Vacancies is built in Rails, we lost some of the advantages of working with emails in Rails. Out of the box, Rails provides ‘mailers’, which are classes with methods that take in variables, build an email body using a templating language such as  ERB  or Haml (much like how Rails handles webpage views), and then send an email.

Rails mailers can also be hooked into ActiveJob (which is Rails’ framework for queuing and running background jobs). This means when a user does a particular action, we can tell a background job to send an email, rather than the web application itself. This speeds up the user experience, and also allow us to retry if the initial action fails for some reason.

With this in mind, I decided to have a look and see if we could hook Notify into Rails’s mailers. The framework that provides support for Mailers in Rails, called ActionMailer, gives us the ability to specify a delivery method for your emails.

Out of the box, Action Mailer provides support for four delivery methods: SMTP, Sendmail, File (which saves emails to a file), and Test (which pushes emails to an array, which we can inspect when writing tests). Moreover, you can also write your own delivery method. This is where I decided to make a start.

A delivery method only needs two things:

  1. An initializer, which initialises the class and takes some settings
  2. A method called ‘deliver!’ which actually sends the email.

A very simple mailer could look like this:

module Mail
  class MySimpleDeliveryMethod

    def initialize(settings)
      @settings = settings
    end

    def deliver(mail)
      # do something with email

      puts "from: #{mail.from}"
      puts "to: #{mail.to}"

      # access mail content
      mail.parts.each do |m|
        puts "content: #{m.body.to_s}"
      end
    end
  end
end

We can then configure our mailers in Rails to use our delivery method, like so:

# config/environments/development.rb
Rails.application.configure do
  ActionMailer::Base.add_delivery_method :my_simple_delivery_method, Mail::MyTestMySimpleDeliveryMethodDelivery
  config.action_mailer.delivery_method = :my_simple_delivery_method
end

This then makes it very easy to, rather than printing the mail in the case above, call an API. In our case, we could grab the details from the mail and call the Notify API like so:

module Mail
  module Notify
    class DeliveryMethod

      def initialize(settings)
        @settings = settings # This will contain our API key
      end

      def deliver!(mail)
        # Call the Notify API
        Notifications::Client.new(@settings[:api_key]).send_email(
          to: mail.to, # The address our email is going to
          template_id: mail.template_id, # The ID of the template we’ve created in Notify
          personalisation: {
            subject: mail.subject,
            body: mail.body.raw_source # The body of the email generated by the template
          }
        )
      end
    end
  end
end

This would pretty much work without any customisation to the ActionMailer mailers, but the one thing missing is how we tell Notify what template to use. When using ActionMailer, a mailer usually looks something like this:

class MyCoolMailer < ActionMailer::Base
  def send_an_email(params)
    # Do some stuff with params here

    # This is where the magic happens
    mail(to: 'cooldude@wow.com', subject: 'You are cool')
  end
end

ActionMailer is very permissive, in that we can add any parameter to our call to mail, and the resulting mail object in our delivery_method has access to it. For example, if I called:

mail(to: 'cooldude@wow.com', subject: 'You are cool', foo: 'bar')

When I called deliver! in my delivery method, I would have access to foo like so:

def deliver(mail)
  mail.foo #=> 'bar'
end

If I wanted to then I could just add a template_id to my call to mail, and that would be that, but as things would break if we didn’t specify a template ID, I wanted to have template_id as a required parameter, so I built my own mailer, which inherits from ActionMailer::Base:

module Mail
  module Notify
    class Mailer < ActionMailer::Base
      def notify_mailer(template_id, headers)
        mail(headers.merge(template_id: template_id))
      end
    end
  end
end

This accepts two arguments, template_id and headers, and then merges the arguments into a single parameter and calls mail. This may seem like overkill, but at least gives us the confidence that we’re going to have the template ID when we come to call the delivery method, and also gives us more scope for customisation later.

We can then use the mailers in our Rails application like so:

class MyCoolMailer < Mail::Notify::Mailer
  def send_an_email(params)
    # Do some stuff with params here
    notify_mailer('template_id_goes_here', to: 'cooldude@wow.com', subject: 'You are cool')
  end
end

You can check out the readme on Github here (some of the code in this blog post is simplified for brevity), and it’s published to RubyGems, so you can use it yourself today. Feel free to submit bugs, PRs etc, and let me know how you get on with it if you use it anywhere!