Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 185 lines (136 sloc) 9.813 kB
335cdaa @ConradIrwin +README and LICENSE
authored
1 An [EventMachine](http://eventmachine.org/) based [IMAP](http://tools.ietf.org/html/rfc3501) client.
2
3 ## Installation
4
5 gem install em-imap
6
7 ## Usage
8
9 This document tries to introduce concepts of IMAP alongside the facilities of the library that handle them, to give you an idea of how to perform basic IMAP operations. IMAP is more fully explained in [RFC3501](http://tools.ietf.org/html/rfc3501), and the details of the library are of course in the source code.
10
11 ### Connecting
12
434fb6f @ConradIrwin Change the API of connection setup.
authored
13 Before you can communicate with an IMAP server, you must first connect to it. There are three connection parameters, the hostname, the port number, and whether to use SSL/TLS. As with every method in EM::IMAP, `EM::IMAP::Client#connect` returns a [deferrable](http://eventmachine.rubyforge.org/docs/DEFERRABLES.html) enhanced by the [deferrable\_gratification](https://github.com/samstokes/deferrable_gratification) library.
335cdaa @ConradIrwin +README and LICENSE
authored
14
15 For example, to connect to Gmail's IMAP server, you can use the following snippet:
16
17 require 'rubygems'
18 require 'em-imap'
19
20 EM::run do
434fb6f @ConradIrwin Change the API of connection setup.
authored
21 client = EM::IMAP.new('imap.gmail.com', 993, true)
22 client.connect.errback do |error|
335cdaa @ConradIrwin +README and LICENSE
authored
23 puts "Connecting failed: #{error}"
24 end.callback do |hello_response|
25 puts "Connecting succeeded!"
26 end.bothback do
27 EM::stop
28 end
29 end
30
31 ### Authenticating
32
33 There are two authentication mechanisms in IMAP, `LOGIN` and `AUTHENTICATE`, exposed as two methods on the EM::IMAP client, `.login(username, password)` and `.authenticate(mechanism, *args)`. Again these methods both return deferrables, and the cleanest way to tie deferrables together is to use the [`.bind!`](http://samstokes.github.com/deferrable_gratification/doc/DeferrableGratification/Combinators.html#bind!-instance_method) method from deferrable\_gratification.
34
35 Extending our previous example to also log in to Gmail:
36
434fb6f @ConradIrwin Change the API of connection setup.
authored
37 client = EM::IMAP.new('imap.gmail.com', 993, true)
38 client.connect.bind! do
335cdaa @ConradIrwin +README and LICENSE
authored
39 client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
40 end.callback do
41 puts "Connected and logged in!"
42 end.errback do |error|
43 puts "Connecting or logging in failed: #{error}"
44 end
45
46 The `.authenticate` method is more advanced and uses the same extensible mechanism as [Net::IMAP](http://www.ruby-doc.org/stdlib/libdoc/net/imap/rdoc/classes/Net/IMAP.html). The two mechanisms supported by default are `'LOGIN'` and [`'CRAM-MD5'`](http://www.ietf.org/rfc/rfc2195.txt), other mechanisms are provided by gems like [gmail\_xoauth](https://github.com/nfo/gmail_xoauth).
47
48 ### Mailbox-level IMAP
49
50 Once the authentication has completed successfully, you can perform IMAP commands that don't require a currently selected mailbox. For example to get a list of the names of all Gmail mailboxes (including labels):
51
434fb6f @ConradIrwin Change the API of connection setup.
authored
52 client = EM::IMAP.new('imap.gmail.com', 993, true)
53 client.connect.bind! do
335cdaa @ConradIrwin +README and LICENSE
authored
54 client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
55 end.bind! do
56 client.list
57 end.callback do |list|
58 puts list.map(&:name)
59 end.errback do |error|
60 puts "Connecting, logging in or listing failed: #{error}"
61 end
62
63 The useful commands available to you at this point are `.list`, `.create(mailbox)`, `.delete(mailbox)`, `.rename(old_mailbox, new_mailbox)`, `.status(mailbox)`. `.select(mailbox)` and `.examine(mailbox)` are discussed in the next section, and `.subscribe(mailbox)`, `.unsubscribe(mailbox)`, `.lsub` and `.append(mailbox, message, flags?, date_time)` are unlikely to be useful to you immediately. For a full list of IMAP commands, and detailed considerations, please refer to [RFC3501](http://tools.ietf.org/html/rfc3501).
64
65 ### Message-level IMAP
66
67 In order to do useful things which actual messages, you need to first select a mailbox to interact with. There are two commands for doing this, `.select(mailbox)`, and `.examine(mailbox)`. They are the same except that `.examine` opens a mailbox in read-only mode; so that no changes are made (i.e. performing commands doesn't mark emails as read).
68
69 For example to search for all emails relevant to em-imap in Gmail:
70
434fb6f @ConradIrwin Change the API of connection setup.
authored
71 client = EM::IMAP.new('imap.gmail.com', 993, true)
72 client.connect.bind! do
335cdaa @ConradIrwin +README and LICENSE
authored
73 client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
74 end.bind! do
75 client.select('[Google Mail]/All Mail')
76 end.bind! do
77 client.search('ALL', 'SUBJECT', 'em-imap')
78 end.callback do |results|
79 puts results
80 end.errback do |error|
81 puts "Something failed: #{error}"
82 end
83
84 Once you have a list of message sequence numbers, as returned by search, you can actually read the emails with `.fetch`:
85
434fb6f @ConradIrwin Change the API of connection setup.
authored
86 client = EM::IMAP.new('imap.gmail.com', 993, true)
87 client.connect.bind! do
335cdaa @ConradIrwin +README and LICENSE
authored
88 client.login("conrad.irwin@gmail.com", ENV["GMAIL_PASSWORD"])
89 end.bind! do
90 client.select('[Google Mail]/All Mail')
91 end.bind! do
92 client.search('ALL', 'SUBJECT', 'em-imap')
93 end.bind! do |results|
94 client.fetch(results, 'BODY[TEXT]')
95 end.callback do |emails|
96 puts emails.map{|email| email.attr['BODY[TEXT]'] }
97 end.errback do |error|
98 puts "Something failed: #{error}"
99 end
100
101 The useful commands available to you at this point are `.search(*args)`, `.expunge`, `.fetch(messages, attributes)`, `.store(messages, name, values)` and `.copy(messages, mailbox)`. If you'd like to work with UIDs instead of sequence numbers, there are UID based alternatives: `.uid_search`, `.uid_fetch`, `.uid_store` and `.uid_copy`. The `.close` command and `.check` command are unlikely to be useful to you immediately.
102
103 ### Untagged responses
104
105 IMAP has the notion of untagged responses (aka. unsolicited responses). The idea is that sometimes when you run a command you'd like to be updated on the state of the mailbox with which you are interacting, even though notification isn't always required. To listen for these responses, the deferrables returned by each client method have a `.listen(&block)` method. All responses received by the server, up to and including the response that completes the current command will be passed to your block.
106
107 For example, we could insert a listener into the above example to find out some interesting numbers:
108
109 end.bind! do
110 client.select('[Google Mail]/All Mail').listen do |response|
111 case response.name
112 when "EXISTS"
113 puts "There are #{response.data} total emails in All Mail"
114 when "RECENT"
115 puts "There are #{response.data} new emails in All Mail"
116 end
117 end
118 end.bind! do
119
120 One IMAP command that exists solely to receive such unsolicited responses is IDLE. The IDLE command blocks the connection so that no other commands can use it, so before you can send further commands you must `stop` the IDLE command:
121
122 idler = client.idle
123
124 idler.listen do |response|
125 if (response.name == "EXISTS" rescue nil)
126 puts "Ooh, new emails!"
127 idler.stop
128 idler.callback do
129 # ... process new emails
130 end
131 end
132 end.errback do |e|
133 puts "Idler recieved an error: #{e}"
134 end
135
136 ### Concurrency
137
138 IMAP is an explicitly concurrent protocol: clients MAY send commands without waiting for the previous command to complete, and servers MAY send any untagged response at any time.
139
140 If you want to receive server responses at any time, you can call `.add_response_handler(&block)` on the client. This returns a deferrable like the IDLE command, on which you can call `stop` to stop receiving responses (which will cause the deferrable to succeed). You should also listen on the `errback` of this deferrable so that you know when the connection is closed:
141
142 handler = client.add_response_handler do |response|
143 puts "Server says: #{response}"
144 end.errback do |e|
145 puts "Connection closed?: #{e}"
146 end
147 EM::Timer.new(600){ handler.stop }
148
149 If you want to send commands without waiting for previous replies, you can also do so. em-imap handles the few cases where this is not permitted (for example, during an IDLE command) by queueing the command until the connection becomes available again. If you do this, bear in mind that any blocks that are listening on the connection may receive responses from multiple commands interleaved.
150
434fb6f @ConradIrwin Change the API of connection setup.
authored
151 client = EM::Imap.new('imap.gmail.com', 993, true)
152 client.connect.callback do
335cdaa @ConradIrwin +README and LICENSE
authored
153 logger_in = client.login('conrad.irwin@gmail.com', ENV["GMAIL_PASSWORD"])
154 selecter = client.select('[Google Mail]/All Mail')
155 searcher = client.search('from:conrad@rapportive.com').callback do |results|
156 puts results
157 end
158
159 logger_in.errback{ |e| selecter.fail e }
160 selecter.errback{ |e| searcher.fail e }
161 searcher.errback{ |e| "Something failed: #{e}" }
162 end
163
164 ## TODO
165
166 em-imap is still very much a work-in-progress, and the API will change as time goes by.
167
168 Before version 1, at least the following changes should be made:
169
170 1. Stop using Net::IMAP in quite so many bizarre ways, probably clearer to copy-paste the code and rename relevant classes (particular NoResponseError..)
171 2. Find a nicer API for some commands (maybe some objects to represent mailboxes, and/or messages?)
172 3. Document argument serialization.
173 4. Support SORT and THREAD.
174 5. Put the in-line documentation into a real format.
175
8c0f454 @ConradIrwin Up to version 0.2
authored
176 ### Breaking Changes
177
178 Between Version 0.1(.x) and 0.2, the connection setup API changed. Previously you would call `EM::IMAP.connect`, now that is broken into two steps: `EM::IMAP.new` and `EM::IMAP::Client#connect` as documented above. This makes it less likely people will write `client = connect.bind!` by accident, and allows you to bind to the `errback` of the connection as a whole should you wish to.
179
335cdaa @ConradIrwin +README and LICENSE
authored
180 ## Meta-foo
181
182 Em-imap is made available under the MIT license, see LICENSE.MIT for details
183
184 Patches and pull-requests are welcome.
Something went wrong with that request. Please try again.