Pythonでメールを受信する

プログラムで自動でメールを処理したい時があったので、その時のメモ。
以下では全て、動作環境はWindows10とPython3.9、メールはGmailで行っています。

# 準備（Gmailのみ）

以下はGmailに限った話であって、デフォルトでIMAPを使う設定のメールを使う場合は関係ありません。

現在のGmailでは、デフォルトではIMAPでメール受信は出来なくなっています。
またアカウントのパスワードではメールを使えないので、アプリパスワードを発行して利用します。

- [他のメール プラットフォームで Gmail のメールをチェックする](https://support.google.com/mail/answer/7126229)
- [アプリ パスワードでログインする](https://support.google.com/accounts/answer/185833)

# プログラム

以下ではメールサーバへ接続、メッセージを検索、取得、それをprintするプログラムです。

## メールサーバへ接接続

メールサーバにログインした後、INBOX（受信トレイ）を選択。

```python
import email
import imaplib
from email.header import decode_header, make_header

# メールサーバにログイン
imap = imaplib.IMAP4_SSL("imap.gmail.com", "993")
imap.login("YourMailAddress","ApplicationPassword")
imap.select("INBOX")
```

さらにメールの送信者が`foo@example.com`のものを選びます。

```python
# 受信トレイからメールアドレスで絞り込み
search_str = 'FROM "foo@example.com"'
head, data = imap.search(None, search_str)
```

上で選んだものをMIMEメッセージのリストに収めます。

```python
datas = data[0].split()
msg_list = []  # 取得したMIMEメッセージを格納するリスト
for num in datas:
    head, data = self.imap.fetch(num, '(RFC822)')
    msg = email.message_from_bytes(data[0][1])
    msg_list.append(msg)
```

取り出したMIMEメッセージをデコードして読める形にして、辞書リストに収めます。

```python
decode_msg_list = [] # デコードしたメッセージのリスト
for msg in msg_list:
    subject = str(make_header(decode_header(msg["Subject"]))) # タイトル
    payload = ""
    # シングルパートとマルチパートの振り分け
    if msg.is_multipart():
        # マルチパート
        for part in msg.walk():
            payload = part.get_payload(decode=True)
            if payload is None:
                continue
            charset = part.get_content_charset()
            if charset is not None:
                payload = payload.decode(charset, "ignore")
    else:
        # シングルパート
        payload = msg.get_payload(decode=True)
        charset = msg.get_content_charset()
        if charset is not None:
            payload = payload.decode(charset, "ignore")

    decode_msg = {'subject': subject, 'body': payload}
    decode_msg_list.append(decode_msg)

for msg in decode_msg_list:
    print(msg['subject'])
    print(msg['body'])
```

print文を変えたり、以下の複数検索条件を組み合わせたり、decode_header部分を変えたりすると、検索や他の項目が出力できます。

## 複数検索条件

AND検索にはなりますが、以下のように複数の条件も可能です。
以下は`foo@example.com`から2021年1月16日に送られたメールを検索する例です。

```python
search_str = 'FROM "foo@example.com" SENTON "16-Jan-2021"'
```

## 表示項目を増やす

上ではメールはタイトルと本文しかメールから取得していませんが、それ以外にもメールヘッダの項目は取得できます。
例えば作成日時は以下のように取得できます。

```python
date = str(make_header(decode_header(msg["Date"]))) # メール作成日時
```

# 課題

以下のことをやりたいと思っても出来ていないので、現在の課題です。

## 日本語タイトルでの検索

タイトルで日本語で検索したいけどエラーになります。

```python
search_str = 'HEADER Subject "日本語" FROM "foo@example.com"'
```

以下のように英語で検索はできます。

```python
search_str = 'HEADER Subject "japanese" FROM "foo@example.com"'
```

## 時間指定での検索

以下のようにやるとエラーになります。

```python
search_str = 'FROM "foo@example.com" SINCE "16-Jan-2021 15:00:00"'
```

時間まで含めた検索が出来ないのは、IMAPの仕様らしいです（RFC幾つだか忘れましたが）。
もしやる時はメールヘッダのDateからfor文内で判別させるしかなさそうです。

# 参考

- [【python】imaplibでGmailを読み取る](https://qiita.com/hujuu/items/b75f8492000483bc66aa)
- [Pythonでメール受信; imaplibのサンプルコード](https://qiita.com/takey/items/1498af9e1113eeb7bb21)
- [メールヘッダの一覧【@IT】](https://www.atmarkit.co.jp/fnetwork/rensai/netpro03/mail-header.html)