Gmail API and Rails: sending emails through ActionMailer
When I was working with my friends on Foxbound, one of the main features we needed for the sales automation tool was to send emails through a user's Gmail account. Getting this to work in Ruby is easy once you manage to find the documentation. However, I wanted to integrate it with ActionMailer, because it's a nice structured way of writing mailers and it lets us leverage built-in functionality like the ActiveJob integration to queue up mail for later.
So that's how I got around to writing a gem to add Gmail HTTP API support for ActionMailer, which led to the creation of google-http-actionmailer.
So how do you send mail through the Gmail HTTP API in Ruby?
gem 'google-api-client'
require 'google/apis/gmail_v1'
mail = Mail.new
# ...
service = Google::Apis::GmailV1::GmailService.new
service.authorization = access_token # You'll get this through your refresh token
message = Google::Apis::GmailV1::Message.new(
raw: mail.to_s
)
service.send_user_message(
user_id, # ID of the user or 'me' for current authenticated user
message
)
In the gem, this code is incorporated as a custom delivery method that gets included into ActionMailer.
What can google-http-actionmailer do?
The gem is a plug-and-play solution for sending mail through the Google HTTP API for Gmail. It's easy to configure.
While working on Foxbound, we also found that we wanted to track the emails we sent and the threads they were in. The message ID and thread ID come back in the response from the Gmail API.
So we added hooks that trigger on your mail object before and after the email is sent. This lets you write callbacks to store details, trigger actions conditionally, and so on.
How do I use google-http-actionmailer?
Set up your authorization as described in the Google API Client (the easiest method is to pass a valid access token, which I describe below), then edit config/application.rb or config/environments/<ENVIRONMENT>.rb and add or change the ActionMailer configuration:
config.action_mailer.delivery_method = :google_http_actionmailer
config.action_mailer.google_http_actionmailer_settings = {
authorization: ...,
client_options: {
application_name: ...,
application_version: ...,
},
request_options: {
retries: ...,
header: ...,
},
message_options: {
fields: ...,
content_type: ...,
},
delivery_options: {
before_send: ...,
after_send: ...,
}
}
For client and request options, see the options in the Google API client. For message options, see the send_user_message method in the Gmail service.
Normal ActionMailer usage will now be sent using Google's HTTPS API.
If you go with access tokens for authorization, you'll need to know how to refresh them and how to set the token dynamically at the time of sending, which I describe below.
Refreshing access tokens
This requires the omniauth-google-oauth2 gem. We use our existing access token and refresh token to obtain a new token.
strategy = OmniAuth::Strategies::GoogleOauth2.new(nil, <GOOGLE_CLIENT_ID>, <GOOGLE_CLIENT_SECRET>)
client = strategy.client
token = OAuth2::AccessToken.new client, access_token, { refresh_token: refresh_token }
new_token = token.refresh!
# Store the new token
Dynamically setting the delivery method
Access tokens usually expire, so you may need to set the delivery method dynamically. For that we use an interceptor. Interceptors are a concept in ActionMailer: they receive the message before it's sent, so you can use them to modify messages on the way out. In this case we use an interceptor to set the delivery method with a token we refreshed:
class DynamicSettingsInterceptor
def self.delivering_email(message)
token = user.new_token
message.delivery_method(
GoogleHttpActionmailer::DeliveryMethod,
{
authorization: token
}
)
message
end
end
register_interceptor ::DynamicSettingsInterceptor
Setting after-send and before-send hooks
Sometimes we want the object returned by the Gmail API, perhaps to store details of the email that was sent. For that we can use delivery options: pass a proc that takes two parameters, mail (the object created by ActionMailer) and message (the object created by the Gmail API), to the after_send and before_send hooks.
config.action_mailer.delivery_method = :google_http_actionmailer
config.action_mailer.google_http_actionmailer_settings = {
authorization: access_token,
...
delivery_options: {
after_send: ->(mail, message) {
...
},
before_send: ->(mail, message) {
...
}
}
}
And that covers using google-http-actionmailer. If you'd like to look at the code, contribute or make suggestions, head over to GitHub, or say hi on X.