How to integrate drag and drop email builder to your Ruby on Rails project

This is a short story about integration of BeeFree email builder into Ruby on Rails application.

Currently I’m working as Project Manager in Nikon Russia, and my responsibility also includes sending email campaigns to Nikon School students.

Let me tell you the story about my pain

We have pretty rubbish CMS system called 1C-Bitrix, and to perform email campaign I need to segment students manually per every type of courses (export as CSV, remove unnecessary elements and columns, remove email duplicates, merge different files to one), then write html-partials for hardcoded templates, paste huge list of emails to form, send it and hope that everything is fine.

Because I’m really tired spending few hours on this basic operations, my emails lacked of good content and design. And I decided: no more. No more pain! That’s how I started my personal Ruby on Rails project “Nikon School Automation”.

And straight to the core

When you google for good email template builder, you wouldn’t find a lot. But there is one nice tool called BeeFree plugin, which you can integrate to your software, and it’s pricing plans are very loyal. So I started digging.

BeeFree basic interface

First thing to do is very simple: Sign up and obtain your app credentials. And if you like, you may look at their sample code on GitHub.

To make a working app you’ll need

  1. View to display your editor
  2. Javascript code to initialize editor workflow
  3. Controller to process token authentication, templates processing, saving email content, etc
  4. Models to store templates and generated emails (but it also can be done through files)

Ok, ok, let’s code a little bit!

The easiest part of all this work is to write a view. Just place somewhere in the view:

<h1>Editor</h1>

<div id="bee-plugin-container" data-template="#{@template_url}"></div>

Don’t forget about styling! You’ll also need to add styles for correct placement of plugin. In my case:

@import 'bootstrap/variables';

#bee-plugin-container {
  position: absolute;
  top: 60px;
  bottom:0;
}

.campaign-editor .content-area {
  position: absolute;
  top: 60px;
  bottom: 60px;

  @media (max-width: $screen-xs-max) {
    width: 100%;
  }
  @media (min-width: $screen-sm-min) {
    width: 750px;
  }
  @media (min-width: $screen-md-min) {
    width: 970px;
  }
  @media (min-width: $screen-lg-min) {
    width: 1170px;
  }
}

Next, we’ll write controller, which will authenticate and put @template_url into our view:

class EditorController < ApplicationController

  # Possible values for bee_template_url:
  #
  # one-column
  # promo
  # newsletter
  # m-bee
  # base-one-column
  # base-m-bee
  # base-newsletter
  # base-promo

  def index
    @template_url =  bee_templates_url('m-bee')
  end

  def token
    uri = URI('https://auth.getbee.io/apiauth')
    res = Net::HTTP.post_form(
      uri,
      grant_type: 'password',
      client_id: ENV['BEE_CLIENT_ID'],
      client_secret: ENV['BEE_CLIENT_SECRET']
    )

    render json: res.body
  end

  private

  def bee_templates_url(id)
    return "https://rsrc.getbee.io/api/templates/#{id}"
  end
end

Of course our app needs to know where to route users

Rails.application.routes.draw do
  get 'editor' => 'editor#index'
  get 'editor/token' => 'editor#token'
  get 'editor/template' => 'editor#template'
end

As you see, you may easily override default templates with your own and store them in database as JSON, but this part is for my next articles.

Last step is to include BeePlugin.js, but I didn’t want to have this script loaded on every page. I made Rails put controller name into body class, so I can limit script execution to specified page (I found this trick on some website I don’t remember). Also you need to include javascript inside javascript. Something like Google Analytics does.

// Extend $.ready() function
(function ($) {
  var ready = $.fn.ready;
  $.fn.ready = function (fn) {
    if (this.context === undefined) {
      // The $().ready(fn) case.
      ready(fn);
    } else if (this.selector) {
      ready($.proxy(function(){
        $(this.selector, this.context).each(fn);
      }, this));
    } else {
      ready($.proxy(function(){
        $(this).each(fn);
      }, this));
    }
  }
})(jQuery);

// Require external script
jQuery.externalScript = function(url, options) {
  // allow user to set any option except for dataType, cache, and url
  options = $.extend(options || {}, {
    dataType: "script",
    cache: true,
    url: url
  });
  // Use $.ajax() since it is more flexible than $.getScript
  // Return the jqXHR object so we can chain callbacks
  return jQuery.ajax(options);
};


$('.campaign-editor').ready(function() {

  // Load BeeFree plugin
  $.externalScript('https://app-rsrc.getbee.io/plugin/BeePlugin.js').done(function(script, textStatus){

    ( function($, Bee, undefined ) {

      Bee.init = function() {
        $.ajax({ url: '/editor/token', success: Bee.create});
      };

      Bee.create = function(token) {
        BeePlugin.create(token, Bee.config(), Bee.on_create);
      };

      Bee.on_save = function(jsonFile, htmlFile) {

      };

      Bee.on_save_as_template = function(jsonFile) {

      };

      Bee.on_auto_save = function(jsonFile) {

      };

      Bee.on_send = function(htmlFile) {

      };

      Bee.on_error = function(errorMessages) {
        console.log('onError ', errorMessages);
      };

      Bee.on_create = function(bee) {
        $.ajax({ url: $('#bee-plugin-container').data('template'), success: Bee.on_load_template});
        window.bee = bee;
      };

      Bee.on_load_template = function(template) {
        window.bee.start(JSON.parse(template));
      };

      Bee.config = function() {
        return  {
          uid: 'YourAppName',
          container: 'bee-plugin-container',
          autosave: 15,
          language: 'ru-RU',
          preventClose: false,
          specialLinks: [{
            type: 'unsubscribe',
            label: 'SpecialLink.Unsubscribe',
            link: 'http://[unsubscribe]/'
          }],
          onSave: Bee.on_save,
          onSaveAsTemplate: Bee.on_save_as_template,
          onAutoSave: Bee.on_auto_save,
          onSend: Bee.on_send,
          onError: Bee.on_error
        }
      };

    }(jQuery, window.Bee = window.Bee || {}));


    Bee.init();

  });
});

That’s it!

But this is not the end of the story. I’m currently working on implementation of custom templates function, saving and sending tons of email campaigns, and lot more. Stay tuned!

Dmitry Zuev

Dmitry has over two years of experience developing web applications on Ruby (mostly on Rails), and about five years of web development total. Now he is a part of development team at Rambler & Co, a Russian media company with an audience of around 40 million. Dmitry prefers back-end web development.