Provides Ruby interface to Context.IO
Latest commit 799eb75 Dec 1, 2015 @cecyc cecyc Merge pull request #76 from contextio/update-readme
added to readme: how to retrieve messages from multiple email addresses
Failed to load latest commit information.
.rspec Clean up .rspec file Apr 22, 2014
Gemfile Update Oct 20, 2015
contextio.gemspec merge with master Aug 28, 2015



Provides a Ruby interface to Context.IO. The general design was inspired by the wonderful aws-sdk gem. You start with an object that represents your account with Context.IO and then you deal with collections within that going forward (see the Quick Start section).


$ gem install contextio

Or, of course, put this in your Gemfile:

gem 'contextio'

A Note On Method Names

If you're looking at the Context.IO docs, it is important to note that there are some attributes that've been renamed to be a bit more Ruby-friendly. In general, if the API returns a number meant to be seconds-from-epoch, then it's been converted to return a Time (e.g. updated has changed to updated_at) and a boolean has converted to something with a ? at the end (e.g. HasChildren and initial_import_finished are has_children? and initial_import_finished?, respectively). See the gem docs for a specific class when in doubt.

Version Numbers

This gem adheres to SemVer. So you should be pretty safe upgrading from 1.0.0 to 1.9.9. Whatever as long as the major version doesn't bump. When the major version bumps, be warned; upgrading will take some kind of effort.


Quick Start

Print the subject of the first five messages in the account.

require 'contextio'

contextio ='your_api_key', 'your_api_secret')

account = contextio.accounts.where(email: '').first

account.messages.where(limit: 5).each do |message|
  puts message.subject

Primary Key Queries

To grab some object you already know the primary key for, you'll use the [] method like you would for a Hash or Array.

This is most helpful for accounts, but works for any resource collection.

require 'contextio'

contextio ='your_api_key', 'your_api_secret')

some_account_id = "exampleaccountid12345678"

account = contextio.accounts[some_account_id]
message = account.messages[some_message_id]

Message By Key

message = account.messages[some_message_id]

message.message_id    #=> "examplemessageid12345678"
message.subject       #=> "My Email's Subject"
message.from.class    #=> Hash
message.from['email'] #=> ""
message.from['name']  #=> "John Doe"

message.delete #=> true

Body content must be accessed through each body part (eg: html, text, etc.). does not store body content and so each call will source it directly from the mail box associated with the account. This will be slow relative to the API.

You can specify and receive a single body content type.

message = account.messages[some_message_id]

message_part = message.body_parts.where(type: 'text/plain').first

message_part.class    #=> ContextIO::BodyPart
message_part.type     #=> "text/plain"
message_part.content  #=> "body content of text/plain body_part"

You can determine how many body parts are available and iterate over each one.

message = account.messages[some_message_id]

message.body_parts.class #=> ContextIO::BodyPartCollection
message.body_parts.count #=> 2

message.body_parts.each do |part|
  puts part.class   #=> ContextIO::BodyPart
  puts part.type    #=> "text/html"
  puts part.content #=> "body content of text/html body_part"

Account By Key

You can specify an account id and get back information about that account.

account = contextio.accounts[some_account_id]

account.email_addresses #=> ['', '']              #=> "exampleaccountid12345678"
account.first_name      #=> "Bruno"
account.suspended?      #=> False

Message Collections

Query Basics

Queries to Messages return ContextIO::MessageCollection objects which can be iterated on.

The where method allows you to refine search results based on available filters.

#the 25 most recent messages by default, you can specify a higher limit

#the 50 most recent messages
account.messages.where(limit: 50)

#recent messages sent to the account by
account.messages.where(from: '')

#recent messages sent to the account by multiple email accounts takes in comma separated values
account.messages.where(from: ',')

#multiple parameters accepted in hash format
account.messages.where(from: '', subject: 'hello')

#regexp accepted as a string like '/regexp/'
account.messages.where(from: '', subject: '/h.llo/')

#regexp options are supported, the /i case insensitive is often useful
account.messages.where(from: '', subject: '/h.llo/i')

Querying Dates

Pass dates in to message queries as Unix Epoch integers.

require 'active_support/all'

account.messages.where(date_before: 3.hours.ago.to_i, date_after: 5.hours.ago.to_i).each do |message|
  puts message.subject      #=> "The subject of my email"
  puts message.received_at  #=> 2013-07-31 20:33:56 -0500  

You can mix date and non-date parameters.

  date_before: 3.days.ago.to_i,
  date_after: 4.weeks.ago.to_i,
  subject: 'subject of email',
  from: ''

Individual Messages

Message Basics

account.messages.where(limit: 1).class # ContextIO::MessageCollection

message = account.messages.where(limit: 1).first

message.class   #=> ContextIO::Message
message.subject #=> "subject of message"
message.from    #=> {"email"=>"", "name"=>"John Doe"}      #=> [{"email"=>"", "name"=>"'John Doe'"}, {"email"=>"", "name"=>"Jeff Mangum"}]

Message Dates


received_at is the time when your IMAP account records the message having arrived. It is returned as a Time object.

m.received_at       #=> 2013-04-19 08:12:04 -0500
m.received_at.class #=> Time

indexed_at is the time when the message was processed and indexed by the sync process.

m.indexed_at        #=> 2013-04-29 01:14:39 -0500
m.received_at.class #=> Time

A message's date is set by the sender, extracted from the message's Date header and is returned as a FixNum which can be converted into a Time object.                #=> 1361828599       #=> 2013-04-19 08:11:33 -0500 #=> Time is not reliable as it is easily spoofed. While it is made available, received_at and indexed_at are better choices.

Messages with Body Data

By default,'s API does not return message queries with body data.

You can include the body attribute in each individual message returned by adding include_body: 1 to your messages.where query options.

account.messages.where(include_body: 1, limit: 1).each do |message|
  puts "#{message.subject} #{} #{message.body[0]['content']}"

Emails that contain two or more body parts are called multipart messages.

'text/plain' and 'text/html' are common body part types for multipart messages.

In the case a user is viewing email in a client that does not support HTML markup, the 'text/plain' body part type will render instead.

If you are working with multipart messages, you may want to check each body part's content in turn.

account.messages.where(include_body: 1, limit: 1).each do |message|
  message.body_parts.each do |body_part|
    puts body_part.content

The include_body method queries the source IMAP box directly, which results in slower return times.


Files Per Message ID

message = account.messages[message_id]

message.files.class                   #=> ContextIO::FileCollection
message.files.count                   #=> 2 { |f| f.file_name } #=> ["at_icon.png", "argyle_slides.png"]
message.files.first.content_link      #=> https://contextio_to_s3_redirect_url

The file['content_link'] url is a S3 backed temporary link. It is intended to be used promptly after being called. Do not store off this link. Instead, store off the message_id and request on demand.

On Laziness

This gem is architected to be as lazy as possible. It won't make an HTTP request until you ask it for some data that it knows it needs to fetch. An example might be illustrative:

require 'contextio'

contextio ='your_api_key', 'your_api_secret')

account = contextio.accounts['exampleaccountid12345678'] # No request made here.
account.last_name # Request made here.
account.first_name # No request made here.

Note that when it made the request, it stored the data it got back, which included the first name, so it didn't need to make a second request. Asking for the value of any attribute listed in the gem docs will trigger the request.

On Requests and Methods

There are some consistent mappings between the requests documented in the API docs and the methods implemented in the gem.

For collections of resources:

  • the object its self sort of represents the collection-level GET (treat it like any other Enumerable).
  • #where is how you set up the filters on the collection-level GET.
  • #create maps to the collection-level POST or PUT, as appropriate.
  • #[] maps to the individual-level GET, but (as mentioned above) is lazy.

For individual resources

  • the object its self sort of represents the individual-level GET (but see #[] above).
  • #delete maps to the individual-level DELETE.
  • #update maps to the individual-level POST (except in a few cases like Message#copy_to and Message#move_to).


If you'd like to contribute, please see the contribution guidelines.


Maintainers: Please make sure to follow the release steps when it's time to cut a new release.


Copyright (c) 2012-2014 Context.IO

This gem is distributed under the MIT License. See for details.