Part 2: Importing all templates from Postmark

Part 2: Importing all templates from Postmark

[cover image: Photo by Maksim Goncharenok from Pexels]

Goal

As explained in part one, we want to import all Postmark templates into our local system to be able to change them in bulk and export them to Postmark later.

In this section, we will look at importing e-mails from Postmark.

Data Structure

E-mails in Postmark are not only composed of HTML. They also have:

  • A text body
  • A layout id
  • A name
  • A subject
  • An alias
  • An associated server-id
  • An active attribute
  • A layout template

To both allow changing easily the HTML of the e-mails and the data associated with our e-mail, we decided to structure the data in two ways:

  • an HTML file per template named with the following pattern: #{templatename}#{template_id}.
  • a global metadata.yml file with the rest of the templates information. Each e-mail has one entry under its id and then an entry per attribute + the DateTime of the import for that template.
1234:
  :name: template_name
  :subject: Some Subject
  :associated_server_id: 1564321
  :active: true
  :text_body: The text body
  :alias: some-alias
  :layout_template: yago_layout_nl
  :imported_at: "2022-02-09T15:53"

Code

Code Structure

We decided to implement an ExternalTemplate model that represents the data on Postmark. We felt that it would have different responsibilities than the InternalTemplate model, which would focus on representing the mail on our DB.

To bulk import the e-mails, we also decided to implement a TemplateGetter service. The TemplateGetter service will make use of the ExternalTemplate.

Untitled Diagram.drawio(6).png

ExternalTemplate

Its responsibilities are to get the data from Postmark and instantiate itself as an object (initialize), and save itself on the DB. For that, it will user write_html_body and write_metadata.

class ExternalTemplate
  def initialize(id, server_key)
    postmark = Postmark::ApiClient.new(server_key)
    postmark.get_template(id).each do |key, value|
      instance_variable_set("@#{key}", value)
      self.class.send(:attr_reader, key)
    end
  end

  def save
    print '.'
    write_html_body
    write_metadata
  end

  private

  def write_html_body
    File.open("imported_templates/#{name}_#{template_id}.html", 'w+') do |file|
      file << html_body
    end
  end

  def write_metadata
    require 'yaml'
    data = YAML.load_file('imported_templates/metadata.yml') || {}

    data[template_id] = yaml_attributes.merge(imported_at: Time.now.strftime('%Y-%m-%dT%H:%M'))
    File.open('imported_templates/metadata.yml', 'w+') do |file|
      YAML.dump(data, file)
    end
  end

  def yaml_attributes
    {
      name: name,
      subject: subject,
      associated_server_id: associated_server_id,
      active: active,
      text_body: text_body,
      alias: self.alias,
      layout_template: layout_template
    }
  end
end

TemplateGetter

Its responsibilities are:

  • Iterate through the postmark servers (limited to 100 templates, so we currently have two servers)
  • Get the ids of the server's templates
  • Initialize an InternalTemplate for each id
  • Tell the InternalTemplate to save
require 'rubygems'
require 'bundler/setup'
require 'yaml'
require 'postmark'
require_relative '../models/external_template'

class TemplateGetter
  def self.import_templates
    postmark_api_servers.each do |server_key|
      templates_ids(server_key).each { |id| download_template(id, server_key) }
    end
  end

  def self.postmark_api_servers
    secrets = YAML.load_file('secrets.yml')
    [secrets['postmark_api_server_1'], secrets['postmark_api_server_2']]
  end

  def self.templates_ids(server_key)
    postmark = Postmark::ApiClient.new(server_key)
    postmark.get_templates(count: 100)[1].map { |template| template[:template_id] }
  end

  def self.download_template(id, server_key)
    template = ExternalTemplate.new(id, server_key)
    template.save
  end
end

Conclusion

Building a tool to import templates from Postmark is relatively easy (thanks to their gem. If you find yourself having to make changes in bulk or even just wanting to have a copy of all your e-mail data on Postmark, this system is relatively easy to implement.

In our case, we added a CLI tool to be able to interact easily with the code:

require_relative 'services/template_getter'

puts 'welcome to the Postmark cli tool'
puts 'would you like to: ?'
puts '1. import all e-mails'

input = gets

case input.strip
when '1'
  TemplateGetter.new.get_all_templates
else
  puts 'invalid input'
end

In Part 3 we will discuss the strategies we implemented to test our changes.