GhostInThePost
Using Phantomjs to pre-run javascript in emails
GhostInThePost uses Phantomjs to run javascript in the html of your emails so that you can render mustache templates and run scripts that will change the DOM before sending. GhostInThePost will also remove script tags after everything is run so it wont be included in the final email.
Installation
Add this gem to your Gemfile and run bundle install
.
gem 'ghost_in_the_post'
Usage
ghost_in_the_post
have two primary means of usage. The first on is the "Automatic usage", which does almost everything automatically when you deliver the email. "Manual usage" means you will have to specifically call ghost on the email. For the most part you should only need automatic.
Automatic usage
Include the GhostInThePost::Automatic
module inside your mailer. GhostInThePost will do its magic when you try to deliver the message:
class NewsletterMailer < ActionMailer::Base
include GhostInThePost::Automatic
def user_newsletter(user)
mail to: user.email, subject: subject_for_user(user)
end
end
# email has the original body; GhostInThePost has not been invoked yet
email = NewsletterMailer.user_newsletter(User.first)
# This triggers GhostInThePost to process the javascript before it sends
email.deliver
If you dont need GhostInThePost for every email you can call it on only the emails you need.
Manual usage
Include the GhostInThePost::Mailer
module inside your ActionMailer
and call ghost
on the emails that you need. The ghost
email returns the processed email so you can call it on the mail method as follows
class NewsletterMailer < ActionMailer::Base
include GhostInThePost::Mailer
#call ghost of this email
def user_newsletter(user)
mail(to: user.email, subject: subject_for_user(user)).ghost
end
#notifications dont require js
def user_notification(user)
mail(to: user.email, subject: subject_for_user(user))
end
end
Usage Notes
Templating Warning
The html is processed with nokogiri and as such is validated html. However, this means that it will url-encode attributes. For instance <img src="{{source}}"/>
will be transformed into <img src=\"%7B%7Bsource%7D%7D\">
It is recomended that you precompile your js templates (using hogan-assets or something similar and include it as js) so that you are not depending on text that is url-encoded.
JSON from the DOM
If you are parsing JSON from a data attribute, you need to decode the string first:
Instead of this:
var el = document.getElementById('myDiv');
var data = JSON.parse(el.dataset.json);
do this:
var el = document.getElementById('myDiv');
var data = JSON.parse(decodeURIComponent(el.dataset.json));
This will make your code work both in the email and in a web browser
Configuration
GhostInThePost can be configured in an initializer
#config/initializers/ghost_in_the_post.rb
GhostInThePost.config = {
phantomjs_path: "/usr/local/bin/phantomjs", #[required] path of phantomjs, if this is not set there will be an error
includes: ["application.js"], #global include of a javascript file, this will be injected into every email
remove_js_tags: true, #remove script tags after javascript has been processed
raise_js_errors: true, #Raise a GhostInThePost::GhostJSError if there is an error in the js included in the email
raise_asset_errors: true, #Raise an GhostInThePost::AssetNotFoundError if an script provided for running cannot be found
timeout: 1000, #timeout after js has been inserted to make sure it is run
wait_event: 'ghost_in_the_post:done', #an event that can be fire on the document to trigger finish of the processing early
debug: false, #will give a link to the temp file of html for review if there was an error
}
You can also change the includes, timeout and wait event per method like the following
class NewsletterMailer < ActionMailer::Base
include GhostInThePost::Automatic
def user_newsletter(user)
#include extra emails for this email
include_script "email.js", "util.js"
set_ghost_timeout 43
set_ghost_wait_event "done"
mail to: user.email, subject: subject_for_user(user)
end
end
Why? why? why?
I had many, very interactive components that were rendered client side with hogan and I was faced with reproducing complicated javascript logic in ruby just to support an html template, only for emails. It seems silly to create that much redundant code in several places. So I did this instead.
As such this tool should probably be used in limited quantities.
Inspiration
This gem took a lot of design queues from roadie-rails and works along side of it so you can preprocess css and pre-run js all at once!
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Run the test suite and check the output (
rake
) - Add tests for your feature or fix (please)
- Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request