Gmailをはじめとする、IMAPのメールサーバ*1から、プッシュ配信されるメールを取得する方法を調べたのでメモ。新規メールを監視してリアルタイムに何らかの処理を実行するといったことが可能になります。
使うライブラリは
require 'net/imap'
まず接続と認証、それから監視したいメールボックス(Gmailなら"INBOX"や特定のラベル*2など)をselectしたのち、IDLEコマンドを発行してイベントが起こるのを待機します。新たなメールが追加されるとEXISTSイベントで新しいメールのIDが通知されるので、imap.idleの中でそれを検知し、idleを抜けます(そうしないとメール取得コマンドなどがデッドロックする)。また、しばらくイベントがないとサーバから切断されることがあるので、その場合は再接続します。
last_id = -1 while true begin unless $imap $imap = Net::$imap.new('$imap.gmail.com', 993, true) $imap.login(mail_user, mail_password) $imap.select(mail_label) puts "connected to $imap server" end $imap.idle do |resp| if resp.name == "EXISTS" last_id = resp.data $imap.idle_done else p resp end end rescue Net::$imap::Error => e if e.inspect.include? "connection closed" puts "connection closed: reconecting..." $imap = nil else raise end end next unless $imap fetch_mail last_id..-1 end
この実装だとIDLEコマンド実行中以外に新規メールが届いたらイベントを取り逃すような気がするので、イベントハンドラを別途登録してその中で処理すべきかも。
メールの取得はfetch_mailという関数で。HTMLメールの場合、マルチパートになっているので、下記ではプレインテキストの部分をとるようにしています。part.subtype == "HTML"とすれば、HTML部分がとれます。BODYというattributeをfetchすると、そこにマルチパートの情報が入っているので、それを解析して"BODY[n]"を改めてfetchしています。QUOTED-PRINTABLEでエンコードされている場合はそのデコードも行っています。
def fetch_mail(range) mails = $imap.fetch(range, ["UID","BODY","FLAGS"]) mails.each do |mail| uid = mail.attr["UID"] seen = mail.attr["FLAGS"].include? :Seen body = mail.attr["BODY[1]"] if mail.attr["BODY"].multipart? n = mail.attr["BODY"].parts.map.with_index{|part,idx| idx if part.media_type == "TEXT" and part.subtype == "PLAIN" }.select{|x| x}[0] if not n raise "NO PLAINTEXT PART" end body = $imap.uid_fetch(uid, "BODY[#{n+1}]")[0].attr["BODY[#{n+1}]"] if mail.attr["BODY"].parts[n].encoding == "QUOTED-PRINTABLE" body = body.unpack('M')[0] end end end process_body body end
あとは、process_bodyに本文が渡されるので、適当な処理をすればOK。seenは既読かどうかのフラグです。ちなみに、BODY[n]をとると既読になります。そういえばタイトルを取り忘れましたが、fetchするattributeに"ENVELOPE"を追加し、mail.attr["ENVELOPE"].subjectをとってくれば良いでしょう。
ちなみにこれを書いた目的は、Ingressのポータル破壊通知メールが飛んできたときに、それをGmailの振り分けでラベルをつけた後アーカイブすることで新着メール通知が飛んでこないようにし、別途このスクリプトでポータルの位置と画像付きでリアルタイムにTwitterに通知する、というものです。まとまってたくさん来たりするから他のメール通知に混ざってほしくない、という。。
HTMLメールなので、nokogiriを使ってHTMLをパースして、画像ファイルやIntel MapのURLなどを抽出しています。