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,
(defn dump [msgs] (doseq [[uid msg] msgs] (.writeTo msg (java.io.FileOutputStream. (str uid))))) (dump (take 3 (messages gmail "INBOX")))