Using the Postmark API and custom metatags with Ruby on Rails

Why Postmark?

Almost any web application needs to send transactional emails to users of the application at some point. Instead of setting up your own mail server, many applications use a dedicated email service for this, like Sendgrid, Mailgun or Postmark. The main benefit is that the application can just send emails and the service takes care of the delivery. This sounds easy, but these days having your emails delivered reliable and quickly is surprisingly complicated due to all measures taken by email providers to fight spam and phishing.

I always use the excellent Postmark for sending transactional emails, because the experience for a developer is great, with good documentation and fast support. Another benefit is that Postmark has an API you can use to send or read emails. But most importantly: they deliver the emails reliable and quick.

In this article I explain how to setup Postmark, add custom metatags to emails and use the Postmark API to show tagged emails in a Rails application.

Postmark setup

The first thing to do is to create a Postmark account, so you can setup a mail server. This mail server will send the emails for your application and also provides the connection details for the SMTP service and the API keys that you need for your Rails application. You also need to make sure to setup DKIM for your domain, otherwise email providers will mark the email as suspicious or even block it.

There are two ways to send email, with the API or with the SMTP service. The SMTP service is very simple, just change the mail settings in production.rb and you’re good to go. Sending email with the API needs a bit more setup, but then you are also more flexible. Postmark provides a good guide about choosing between these two.

Custom metatags

Another nice feature is that it is possible to add custom metatags to the emails. It is really useful to tag emails so they can be easily retrieved with the API. In our Rails application we have offers and these are emailed to customers and also later more emails are sent for a specific offer, like a confirmation email. It is great to have an overview of all emails sent for a specific offer in our application and with the Postmark API this is really easy to do.

So I want to add a custom metatag called offer-id to these emails, so I can easily find them later with the API. To do this I need to add a header to the mailer:

class OfferMailer < Devise::Mailer
  default from: "No reply <no-reply@myapp.com>"
  def offer_completed_email(offer)
    @offer = offer
    headers['X-PM-Metadata-offer-id'] = @offer.id
    mail(to: "test@test.nl", subject: "Offer is complete")
  end
end

So this is a regular email and I added a headers['X-PM-Metadata-offer-id']. The X-PM-Metadata- part is mandatory and you can add any value you want after that. In our case it is the offer-id. When this email gets sent by Postmark, the custom metatag is added and can be found back in the email logs:

image

Show tagged emails in Rails

Now we ensured the right meta data is added to the emails with a custom metatag, let’s setup the Postmark API to retrieve the email data an show it in our application. The first step is to add a gem so we can send HTTP requests to the Postmark API. There are several good gems for this, I use the httparty gem. So add this to the Gemfile:

gem 'httparty'

Then run bundle install to install the gem. I am a huge fan of service objects, because I don’t want to scatter code for this API implementation throughout my application code, so I create a new service object /app/services/postmarker.rb and setup the class:

class Postmarker
  def initialize
    @url = "https://api.postmarkapp.com"
  end
end

I created a class Postmarker with an initialize method and this method contains the base URL to the Postmark API. Now I can add a method to retrieve all emails sent for a certain offer:

def get_offer_emails(offer_id)
  response = HTTParty.get("#{@url}/messages/outbound?count=100&offset=0&metadata_offer-id=#{offer_id}", {
    headers: {"Accept" => "application/json", "X-Postmark-Server-Token" => ENV["POSTMARK_API_TOKEN"]}
  })
  emails = JSON.parse(response.body)
end

A method get_offer_emails is added with the offer_id as parameter. In this method an API request /messages/outbound is sent to Postmark with the mandatory parameters count and offset. And additionally I only want the emails for this specific offer_id, so I added the parameter metadata_offer-id, which requests only emails containing the custom metadata X-PM-Metadata-offer-id.

The Postmark API needs two mandatory headers:

"Accept" => "application/json"
"X-Postmark-Server-Token" => ENV["POSTMARK_API_TOKEN"]

The last one is the Postmark API token, which you can find in Postmark under [API Tokens] of the specific mail server. It is good practice to not use this token directly in the code, so I use an environment variable. The JSON response coming back from the API request is then parsed. This method can now be called from a controller:

@offer = Offer.find(params[:offer_id])
@emails = Postmarker.new.get_offer_emails(@offer.id)

And in the view @emails can be iterated to show the email details:

%table.table.table-striped
  %thead
    %tr
      %th= I18n.t('.offer.email.recipient')
      %th= I18n.t('.offer.email.subject')
      %th.center= I18n.t('.offer.email.date')
  %tbody
    - if @emails.blank?
      = render "shared/placeholder", colspan: "3"
    - else
      - @emails["Messages"].each do |message|
        %tr
          %td= message["To"][0]["Email"]
          %td= message["Subject"]
          %td= message["ReceivedAt"].to_time.strftime("%d-%m-%Y %H:%M")

And that is all that is needed to show the emails. It is even quite simple to make the subject a link, and when you click it to show the email details. The possibilities are endless once you got to know the API and its methods.