Written August 7, 2012. Tagged SendGrid, Ruby on Rails, Action Mailer.
We have a Rails app that sends its mail through SendGrid.
SendGrid lets you specify metadata in your mail headers, which you can put to excellent use.
SendGrid's highly useful activity log (docs) lists the last week of sent mail. Not the full mail, but the recipient e-mail address, the time it was sent, and its state (delivered, link in mail was clicked etc) as far as SendGrid can tell.
You can filter the list by e-mail address, which is really handy for debugging and customer support.
But the list doesn't contain the mail body, or subject, or anything to help you tell which mail is which.
This can be solved with SendGrid metadata and some Action Mailer trickery.
You can set up to 10 categories as SendGrid metadata on each mail:
class MyMailer < ActionMailer::Base
def hello
headers "X-SMTPAPI" => {
category: ["Unsolicited", "Greetings"],
}.to_json
mail(to: "someone@example.com") do |format|
format.text { render(text: "Hello!") }
end
end
end
They're not predefined, so you can use an unlimited number of categories in total.
You can use categories to filter your statistics. Categories are also displayed in the activity log, which may give you an idea of what we'll do in a bit.
You can also add any keys and values you like as so-called unique arguments:
headers "X-SMTPAPI" => {
unique_args: {
locale: I18n.locale,
environment: Rails.env
}
}.to_json
They're also shown in the activity log.
Note that the values will be turned into strings. If a value is a Ruby array, you'll just see "Array" in SendGrid, so if you want them, do:
headers "X-SMTPAPI" => {
unique_args: {
my_array: ["one", "two"].to_json
}
}.to_json
So how do we automatically store the mailer and action (and some additional goodies) in the SendGrid metadata?
class ApplicationMailer < ActionMailer::Base
# Call add_sendgrid_headers after generating each mail.
def initialize(method_name=nil, *args)
super.tap do
add_sendgrid_headers(method_name, args) if method_name
end
end
private
# Set headers for SendGrid.
def add_sendgrid_headers(action, args)
mailer = self.class.name
args = Hash[ method(action).parameters.map(&:last).zip(args) ]
headers "X-SMTPAPI" => {
category: [ mailer, "#{mailer}##{action}" ],
unique_args: { environment: Rails.env, arguments: args.inspect }
}.to_json
end
end
Any mailer that inherits from ApplicationMailer
will now get the following metadata:
The mailer arguments will be rather a lot of text if you pass in full Active Record instances. If you use resque_mailer like we do, you will usually be passing only record ids, so it will be more compact.
Instead of this:
The log will show this:
Having this data is really handy, and I was pretty happy with this implementation. Sure beats looking at the call chain :)