PythonでIMAPメールサーバ上の古いメールを定期削除

投稿者: | 2022年6月23日

毎朝自動で メール を発するスクリプトが、その送信控えとして自身をcc宛先に入れている為、放っておくとストレージを喰い潰す一方です。そこで、 IMAP サーバ上の古い メール を定期的に 削除 する仕組みを Python で組んでみました。

導入

Raspberry PiやOpenWRTルータのvnstatによる日ごとのトラフィックレポートを機器の死活監視を兼ねて、毎朝発するようスケジューリングしています。

G suite無料版終了に伴い、メールサーバをGMailからロリポップへ変えて以来、時折メールが届かない事象に遭遇したことから、送信元メールアドレスをccに加えて送信控えとしていました。

メール送信に使うアドレスは普段は送信専用にしているので、メールクライアントで日常的にチェックすることも無く、放っておくと受信箱へccメールが溜まる一方なことから、今回、一定日数以前の古いメールを定期的に削除する仕組みを、Pythonの2つのライブラリ(imaplib, imapclient)を比較しながら構築してみます。

 

imaplibの基本

Pythonに標準で用意されているIMAP用ライブラリがimaplibです。

早速、対話コンソールでロリポップのメールサーバへIMAP接続、フォルダリストを取得してログアウトしてみます。

図1.ウェブメールから見たフォルダ一覧

図1.ウェブメールから見たフォルダ一覧

ウェブメールでは4つのフォルダが並列に存在しているように見えますが、IMAP上では imap.list() の戻り値が示す通り、 INBOX の下にその他のフォルダがツリー状にぶら下がっています。

このライブラリでは、フォルダ名やメールにマルチバイト文字が使われていると、そのままでは正しく表示されないとの不便さがあるようなのですが、別途ライブラリをインストールしなくとも即使えるのは利点です。

 

imapclientの基本

対して、マルチバイト文字を手間なく正しく表示してくれると評判なのがこちらのimapclient、使う前にインストールが必要です。

基本的な使い方はimaplibと同じで、サーバへ接続してフォルダリストを取得する程度では差異は感じません。

 

フォルダの選択

ここからは、個々の命題に対して、2つのライブラリそれぞれのアプローチを比較します。

まずは削除したいメールが収納されているフォルダを選択する動作を、imaplibで実現します。

フォルダを選択するとその戻り値として、中に収納されているメール数を得ることが出来ます。

続いて同じ動作をimapclientで実行してみると、

こちらの場合は戻り値の情報が少し多くて複雑なのですが、フォルダ内のメール数は mbox[b'EXISTS'] と要素名を名指しすることで取得可能でした。

 

検索クエリ

次にある日付より古いメールを抽出するような、検索クエリの投げ方を確認してみます。まずはimaplibで試してみると、戻り値として返って来るメッセージ番号がスペース区切りで連結されているだけなので、「何件ヒットしたのか」を数えるには、一度それを split() してから len() に入れる手間が必要になります。

同じことをimapclientで試してみると、戻り値はリスト型で得られるので、件数も素直に len() に入れて評価するだけ。

 

メッセージの取得

検索クエリで得られたのは条件に合致したメールの番号のみに過ぎません。メールを削除する前に日付や件名と言った最小限の情報は確認したいので、メッセージ番号を元に必要なヘッダ情報を fetch() を使って取得してみます。

まずimaplibでは上述の通り、メッセージ番号はスペース区切りの数字の羅列に過ぎないので、一度 split() してから for ループで個々のメッセージ番号を fetch() 要求するのが常套手段なようです。

必要な情報に絞って成形してみます。

 

一方、imapclientではリスト型のメッセージ番号のかたまりをそのまま fetch() へ渡し、結果を for ループで順に処理する手法になります。

読みやすく成形出力するとこのように。

 

メールの削除

メールの削除とは、実際には \\Deleted というフラグをメールに付与するものです。その後、 expunge() するようにドキュメントに記述がありますが、実際には expunge() せずとも、フォルダを close() したり、 logout() してしまえばそれだけで削除が確定します。

削除が確定するともう元には戻せないのでゴミ箱へ移動したい場合は、フラグを付与する前にINBOX.Trashへメールをコピーしておきましょう。

imaplibでは、先ほどのメッセージヘッダ取得ループの中へ組み込んでしまいます。

imapclientでは、リスト型の複数のメッセージ番号を扱えるので、ループの外でフラグを付与することができます。

 

なお、imapclientでは close_folder() の他に unselect_folder() というメソッドがあり、 expunge() せずにフォルダをクローズする、とドキュメントに記されていました。

unselect_folder()
Unselect the current folder and release associated resources.
Unlike close_folder, the UNSELECT command does not expunge the mailbox,
keeping messages with Deleted flag set for example.

実際に試してみると、次のようなエラーで異常終了するので、これはサーバサイドでサポートされている必要がありそうです。

 

フォルダステータスの取得

メール削除後のフォルダ内のメール数を再び取得するのに、imaplibでは status() を使うのですが、この戻り値にもまた悩まされます。

imapclientの folder_status() なら、辞書型(連想配列)で得られるので、戻り値をそのまま利用できます。

ちなみに上の例では”Messages”に限って取得していますが、ステータスとして得られる情報の全体像は次のようになっているようです。

 

n日前の日付文字列

実際のバッチスクリプトではメール保存日数を指定して運用するので、次のようなスクリプトで「今日からn日前の日付」を timedelta() で計算し、 strftime() で日付文字列をdd-MMM-yyyy形式で得てから、検索クエリに投げるようにします。

 

imaplib版:古いメール削除スクリプト

解説が少し長くなってしまいましたが、ロリポップのメールサーバ上にある、n日以前の古いメールを削除するスクリプトはこのようになりました。

その実行結果は次の通り。

 

imapclient版:古いメール削除スクリプト

一方、imapclient版のスクリプトはこちらになります。

imaplib版と同じですが、実行結果は次の通りです。

 

結果的に同じ動作をする2つのスクリプトが出来上がりましたが、imaplibのラッパーライブラリとも取れるimapclientを使った方が、今後のデバッグや改良に際しても良さそうです。

 

 

 

コメントを残す

メールアドレスが公開されることはありません。

CAPTCHA