July 6, 2017
Monkey Patching Griddler to Add Mailgun Properties
Griddler is a great gem to consider if your Rails application needs to receive email messages. Griddler works by creating an endpoint to receive webhooks from email services like SendGrid and Postmark. Since each email service formats their webhook payloads differently, Griddler adapts each incoming message into a generic Email
class instance.
While this generic class makes it really easy to switch email services without rewriting your code, it does require you to discard any data from incoming messages that Griddler doesn’t include in its Email
class. For example, I prefer to use Mailgun due to its addition a stripped-text
parameter, which automatically removes quoted text and signature blocks from emails.
When using Griddler out of the box, messages received from Mailgun don’t include the stripped-text
parameter since it’s not a common email field. Luckily, we can use monkey patching to cleanly extend Griddler and add this functionality just for our app.
Install Griddler
Let’s first install Griddler in our Rails app by adding the griddler-mailgun
gem to the end of the Gemfile
:
gem 'griddler-mailgun'
Run bundle install
to install the Griddler gem with the Mailgun adapter. Next, add the default Griddler endpoint to app/config/routes.rb
:
Rails.application.routes.draw do
mount_griddler
end
This will create an endpoint at /email_processor
- Mailgun will send emails to this route, and they’ll be processed by Griddler.
We also need to add an initializer file to configure Griddler, so create a new file named app/config/initializers/griddler.rb
with the following code:
Griddler.configure do |config|
config.email_service = :mailgun
end
Finally, we’ll need to create a class named EmailProcessor
- Griddler needs this to be defined in our app, and it’s where you’ll eventually process email messages after they arrive. You can place this file wherever you prefer in your Rails project, but let’s create it at lib/email_processor.rb
:
class EmailProcessor
def initialize(email)
@email = email
end
def process
# do something with @email here
end
end
In the process
method above, the @email
object will contain an email message that was sent from Mailgun, arrived at your app’s /email_processor
endpoint, and was processed by Griddler. You can easily access the contents of the message as attributes, such as @email.from
and @email.body
.
Now let’s extend Griddler to handle Mailgun’s stripped-text
attribute, which is currently simply dropped when an email is processed.
Extending the Mailgun Adapter
Let’s first take a look at the source code of the Griddler Mailgun adapter class, which processes webhook payloads as they arrive from Mailgun:
module Griddler
module Mailgun
class Adapter
attr_reader :params
def initialize(params)
@params = params
end
def self.normalize_params(params)
adapter = new(params)
adapter.normalize_params
end
def normalize_params
{
to: to_recipients,
cc: cc_recipients,
bcc: Array.wrap(param_or_header(:Bcc)),
from: determine_sender,
subject: params[:subject],
text: params['body-plain'],
html: params['body-html'],
attachments: attachment_files,
headers: serialized_headers
}
end
# ...
end
end
end
We’re mainly interested in normalize_params
- this method maps the params
values to a new hash that can be parsed into a Griddler::Email instance. Since we know stripped-text
is included in the params sent from Mailgun, we simply need to add a new key/value pair to the hash returned from normalize_params
.
Let’s create a new module in our app to extend Griddler::Mailgun::Adapter
- add the following code to app/config/initializers/griddler.rb
:
# Add Mailgun stripped-text to normalized params
module GriddlerMailgunAdapterExtensions
def normalize_params
normalized_params = super
normalized_params[:stripped_text] = params['stripped-text']
normalized_params
end
end
# Prepend custom extensions
Griddler::Mailgun::Adapter.class_eval do
prepend GriddlerMailgunAdapterExtensions
end
Our module contains its own normalize_params
method, which will be used to override the normalize_params
method in Griddler::Mailgun::Adapter
. The super
method calls the original normalize_params
method, which returns a hash object. A new :stripped_text
key is added to the hash, with the value of params['stripped-text']
from the Mailgun payload. The modified hash is then returned.
Our extension module is then applied to Griddler::Mailgun::Adapter
by using class_eval
to “open” the original class. The prepend
method is then used to “mix in” our extension module method.
Now our new normalize_params
code will execute in place of the original method in Griddler::Mailgun::Adapter
, and the resulting hash output will include our new key/value pair.
Extending Griddler’s Email Class
We also need to monkey patch Griddler::Email
to account for the updates we made to the Adapter. Here’s the relevant source code:
require 'htmlentities'
module Griddler
class Email
include ActionView::Helpers::SanitizeHelper
attr_reader :to,
:from,
:cc,
:bcc,
:original_recipient,
:reply_to,
:subject,
:body,
:raw_body,
:raw_text,
:raw_html,
:headers,
:raw_headers,
:attachments,
:vendor_specific,
:spam_report
def initialize(params)
@params = params
@to = recipients(:to)
@from = extract_address(params[:from])
@subject = extract_subject
@body = extract_body
@raw_text = params[:text]
@raw_html = params[:html]
@raw_body = @raw_text.presence || @raw_html
@headers = extract_headers
@cc = recipients(:cc)
@bcc = recipients(:bcc)
@original_recipient = extract_address(params[:original_recipient])
@reply_to = extract_address(params[:reply_to])
@raw_headers = params[:headers]
@attachments = params[:attachments]
@vendor_specific = params.fetch(:vendor_specific, {})
@spam_report = params[:spam_report]
end
# ...
end
end
We need to add :stripped_text
to the attr_reader
declaration, and make sure it’s parsed in the initialize
method.
Add this code to app/config/initializers/griddler.rb
:
# Assign Mailgun stripped-text to attribute
module GriddlerEmailExtensions
def initialize(params)
super
@stripped_text = params[:stripped_text]
end
end
# Add attribute for Mailgun stripped-text and prepend custom extensions
Griddler::Email.class_eval do
attr_reader :stripped_text
prepend GriddlerEmailExtensions
end
We’ve now defined a module with an initialize
method, which uses super
to call the original overridden method. An instance variable named @stripped_text
is declared to store the value of params[:stripped_text]
from our modified adapter (see previous section).
Finally, class_eval
is used to modify the Griddler::Email
class, where we add :stripped_text
to the attr_reader
declarations, then prepend our extension module method.
Wrapping it Up
Here’s the final version of app/config/initializers/griddler.rb
:
Griddler.configure do |config|
config.email_service = :mailgun
end
# Add Mailgun stripped-text to normalized params
module GriddlerMailgunAdapterExtensions
def normalize_params
normalized_params = super
normalized_params[:stripped_text] = params['stripped-text']
normalized_params
end
end
# Prepend custom extensions
Griddler::Mailgun::Adapter.class_eval do
prepend GriddlerMailgunAdapterExtensions
end
# Assign Mailgun stripped-text to attribute
module GriddlerEmailExtensions
def initialize(params)
super
@stripped_text = params[:stripped_text]
end
end
# Add attribute for Mailgun stripped-text and prepend custom extensions
Griddler::Email.class_eval do
attr_reader :stripped_text
prepend GriddlerEmailExtensions
end
To recap, we’ve added a small amount of functionality to Griddler. When an email arrives from Mailgun, the stripped-text
parameter is now processed, and is included as an attribute of the resulting Griddler::Email
object. We’re now free to use stripped-text
anywhere in our application!
Source Code Available on GitHub
You can find the full source code for this example here: https://github.com/jwkratz/griddler-stripped-text