Python でメールを取得 (IMAP)

 2023/08/31, last updated 2023/09/01 -  ~2 Minutes

Python を使って IMAP サーバからメールを取得する方法。

サンプルコード

以下のリンクのとおり。

IMAP4 の使用例   から転載

import getpass, imaplib

M = imaplib.IMAP4()
M.login(getpass.getuser(), getpass.getpass())
M.select()
typ, data = M.search(None, 'ALL')
for num in data[0].split():
    typ, data = M.fetch(num, '(RFC822)')
    print('Message %s\n%s\n' % (num, data[0][1]))
M.close()
M.logout()

imaplib search の補足

IMAP4.search(charset, criterion[, ...])

criterion にセットするのは、 RFC3501 section 6.4.4   に記載の search keys と思われる。

search が返すのは、(‘OK’, [b'1303 1302 1301 1300 1298 1297 1296’]) のようなデータ。そのため、data[0].split() のようにしてメッセージ番号をリストにしている。

imaplib sort

sort というメソッドもあり、search 結果を並べかえてくれる。

IMAP4.sort(sort_criteria, charset, search_criterion[, ...])

となっているが、「sort_criteria のリストを丸括弧で囲ったもの」である。sort_criterial は、 RFC5256 3.Additional Commands   に記載のものと思われる。

例えば、’(REVERSE DATE)’ などを指定。

imaplib fetch の補足

IMAP4.fetch(message_set, message_parts)

となっている。第2引数は FETCH Command   に記載されている。

fetch すると、既読に変わる。

未読のままにしたければ、上の例だと、M.fetch の後ろに

M.store (num, '-FLAGS', '\\Seen')

とすると、未読に戻る。

M.store の引数も RFC を見ないとわからない。

imaplib fetch で取れるメッセージ

('OK', [(b'1303 (FLAGS (\\Seen NonJunk) RFC822 {3714}', b'メールの中身', b')'])

のような感じで返ってくる。

そのため、data[0][1]のようにして本文にアクセスしている。

from を取りたければ、

import email
:
中略
:
msg = email.message_from_bytes(data[0][1])

として変数に代入したなら、

msg['From']

でアクセスできる。

From や Subject など、=?UTF-8?B?〜?= のような形式なっていることがあり、以下のような関数を作って元に戻した。

def decode_header(raw_header):
        ret = []
        for h, enc in email.header.decode_header(raw_header):
                if enc is None:
                        if isinstance(h, str): # str で返ってくることもある?よくわからん。
                                ret.append(h)
                        else:
                                ret.append(h.decode('ascii'))
                else:
                        ret.append(h.decode('utf-8'))
        return ' '.join(ret)

日付は、

import email.utils
:
中略
:
dt = email.utils.parsedate_to_datetime(msg['Date'])

のようにしてやれば、datetime オブジェクトとして取得できる。

応用

定期的にループして、Unseen メールを取得し、前回チェックした時以降のものであれば、slack の #general チャンネルに post する、などとすれば、新規メールを slack で通知できるだろう。(そういう slack アプリはあるので、自分でやるのは、単なる勉強の意味合いしかないが)