This guide will show you how to access the Gmail API with Ruby, using the OAuth 2.0 flow for web applications. You can use the Gmail API for things like migrating email messages between accounts, performing detailed analysis of archived messages, automating sending of outgoing messages, and extraction of data from incoming messages.
Step 1: Setup your project
First you’ll need to sign in to the Google Developers Console and register your app:
- Start by creating a new project, and enabling the Gmail API on the “APIs” page.
- Set the name of your application and your support email address on the “OAuth consent screen” page. The consent screen is shown to users when they authenticate with Google and agree to their data being shared with you.
- Create a new client on the “Credentials” page. Choose “Web application” as the application type (it should be selected by default), and edit the “Authorized redirect URIs” list to make sure the callback URL you’ll be using in development is included. This is the URL in your application that Google will redirect back to, for example:
http://localhost:5000/oauth2callback
. - Copy & paste the “Client ID” and “Client secret” values associated with the new client–your application will need these to access the API.
You can find these pages under the “APIs & auth” menu item, and you can come back and tweak all these settings before you deploy to staging or production environments.
Step 2: Install the google-api-client gem
Add the google-api-client gem to your Gemfile:
gem 'google-api-client', '~> 0.9', require: 'google/apis/gmail_v1'
Then run bundle. The gem has a lot of dependencies, but will save you from having to write any low level HTTP client code to call the API.
Step 3: Obtain an authorization code
The first step in the OAuth 2.0 flow is to redirect the user to Google so they can sign in and consent to the access you are requesting.
If you’re using Rails your controller action might look something like this:
def redirect
client = Signet::OAuth2::Client.new({
client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
scope: Google::Apis::GmailV1::AUTH_GMAIL_READONLY,
redirect_uri: url_for(:action => :callback)
})
redirect_to client.authorization_uri.to_s
end
There’s a lot going on here, so let’s go through some of the details:
- Signet is the underlying implementation of OAuth 2.0 that Google uses.
- The
client_id
andclient_secret
parameters are the values you copied/pasted from earlier when setting up your project. As with any other secrets it’s important not to commit these values to version control, but for testing purposes you might find it easier just to hardcode them into the code above instead of using environment variables. - The
redirect_uri
value should match the URL in your application that you specified in the “Authorized redirect URIs” list earlier. - You can test this step by running your app and hitting the redirect action. If everything is configured correctly you should see the Google “consent screen” requesting access to view your email messages and settings. If you accept Google will redirect you back to the URL that you provided with a temporary authorization code.
Step 4: Obtain an access token
The next step in the OAuth 2.0 flow is to handle the callback and exchange the temporary authorization code for an access token. If you’re using Rails your controller action might look something like this:
def callback
client = Signet::OAuth2::Client.new({
client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
redirect_uri: url_for(:action => :callback),
code: params[:code]
})
response = client.fetch_access_token!
session[:access_token] = response['access_token']
redirect_to url_for(:action => :labels)
end
Initialization of the client object is similar to before, and includes the temporary authorization code from the request params.
The call to #fetch_access_token!
exchanges the authorization code for an access token, which is then put into the session to persist it between requests.
Step 5: Call the Gmail API
Now that you have an access token you can call the Gmail API itself–finally!
The callback action in the previous step finishes by redirecting to a labels action, which you can implement like this:
def labels
client = Signet::OAuth2::Client.new(access_token: session[:access_token])
service = Google::Apis::GmailV1::GmailService.new
service.authorization = client
@labels_list = service.list_user_labels('me')
end
This time the client object is initialized with the access token from the session. The service object provides methods for making calls to the Gmail API. This example calls the Users.labels: list API method, which returns a list of the user’s labels. The API response is assigned to an instance variable so it can be accessed from the view.
You can then add in a basic view to list the labels like this:
<ul>
<% @labels_list.labels.each do |label| %>
<li><%= label.name %></li>
<% end %>
</ul>
You should now be able to test the complete flow end-to-end by starting at the redirect action. If everything is configured correctly the callback action should fetch the access token and redirect to the labels action to display a list of your labels.
Things to consider
The examples above implement the “happy path”, and don’t handle errors or exceptions which you should code for in a production ready application. Some common failure scenarios you might want to implement:
- If the user cancels at the consent screen Google will redirect back to the callback action with an
error
parameter instead of thecode
parameter. - Calling the API will fail if the access token has expired (access tokens are only valid for an hour). When obtaining the access token Google will also return a
refresh_token
value which you can use to re-request access tokens directly without having to re-authorize. - Calling the API might fail for other reasons, so you might also want to handle other response errors (
Google::Apis::ClientError
exceptions for example).
The code examples above contain some duplication in setting up the signet client. To reduce this you can either use some of the classes included in the google-api-client gem (Google::APIClient::ClientSecrets
and Google::APIClient::InstalledAppFlow
), or you can refactor and reorganize it as you would any other code.
You might want to consider storing the tokens in some kind of datastore instead of the session, depending on the needs of your application.