Using Clojure to Connect to GMail via IMAP

I had to dig through quite a few docs to get a couple of raw emails from my IMAP account so I am posting the snippet for future reference. I have uploaded necessary jars to clojars under the group org.clojars.nakkaya.javax.mail, in case anyone wants to play with it.

( ns gmail.core ( :use clojure.contrib.java-utils) ( :import (javax.mail Session Folder Flags) (javax.mail.search FlagTerm) (javax.mail Flags$Flag))) ( defn store [protocol server user pass] ( let [p (as-properties [[ "mail.store.protocol" protocol]])] ( doto (.getStore ( Session /getDefaultInstance p) protocol) (.connect server user pass)))) ( def gmail (store "imaps" "imap.gmail.com" "nurullah@nakkaya.com" "super_secret_pass" ))

Opening a connection is quite simple, we ask for a session instance and connect to the store (IMAP server in JavaMail lingo).

( defn folders ([s] (folders s (.getDefaultFolder s))) ([s f] ( let [sub? #( if (= 0 (bit-and (.getType % ) Folder /HOLDS_FOLDERS)) false true )] (map #(cons (.getName % ) ( if (sub? % ) (folders s % ))) (.list f)))))

Now that the connection to the store is ready, we need to get a list of folders (Labels in GMail), to get a list of folders, first get the users default folder, that will give us the top level folders we recursively check each folder for sub folders returning a list of all folders and subfolders.

gmail.core=> (folders gmail) (("Fatura") ("INBOX") ("[Gmail]" ("Starred") ("Trash")) ("clojure") ("compojure") ("firmata-devel") ("help-gnu-emacs") ("ikiteker") ("incanter") ("java-dev") ("jna-users") ("leningen") ("metasploit") ("neurobotics") ("org-mode") ("pen-test") ("ring-clojure"))

A folder name and a connection to the store is all that is needed to interact with messages.

( defn messages [s fd & opt] ( let [fd ( doto (.getFolder s fd) (.open Folder /READ_ONLY)) [flags set] opt msgs ( if opt (.search fd (FlagTerm. (Flags. flags) set)) (.getMessages fd))] (map #(vector (.getUID fd % ) % ) msgs)))

Before interacting with a folder we need to open it, either in read only mode or read/write mode. Without the optional parameters, messages just returns a sequence of messages, optional parameters allows us to search for messages using flags such as seen, deleted etc.

gmail.core=> (take 3 (messages gmail "INBOX")) ([8709 #<IMAPMessage com.sun.mail.imap.IMAPMessage@1320a41>] [8712 #<IMAPMessage com.sun.mail.imap.IMAPMessage@3f4ebd>] [8713 #<IMAPMessage com.sun.mail.imap.IMAPMessage@4a5c78>]) gmail.core=> (messages gmail "clojure" Flags$Flag/SEEN false) ([11401 #<IMAPMessage com.sun.mail.imap.IMAPMessage@13b5a3a>] [11402 #<IMAPMessage com.sun.mail.imap.IMAPMessage@1a0b53e>])

The whole reason, I came up with this snippet was to get a copy of raw messages, saved using their IMAP UID,