概要(IMAPは「受信箱から安全に読み取る」ための標準プロトコル)
IMAPは、メールサーバ上の受信箱(INBOX)からメールを検索・取得・既読化・移動・削除するためのしくみです。Python標準のimaplibとemailモジュールを組み合わせれば、未読だけ拾う、差出人や件名で絞る、本文と添付を取り出す、といった処理を短いコードで実現できます。重要なのは、暗号化接続(IMAP4_SSL)、正しいログイン情報(多くのサービスはアプリパスワードを推奨)、検索条件(UNSEEN/FROM/SINCEなど)の使い方、そしてMIME構造の理解による本文・添付の正しい取り出しです。
仕組みと準備(IMAPサーバ・暗号化・モジュール)
接続先と暗号化の基本
IMAPは通常、SSL/TLSで暗号化されたポート993へ接続します。ホスト名はサービスごとに異なり、Gmailなら imap.gmail.com、Microsoft 365なら outlook.office365.com などです。ユーザー名はメールアドレス、パスワードはサービス側の方針に従い、2段階認証とアプリパスワードの利用が推奨されることが多いです。
使うモジュール(imaplib+email)
受信・検索はimaplib、メッセージの解析(件名・本文・添付の取り出し)はemailモジュールを使います。email.message_from_bytesでパースし、multipartをwalkしてパートごとのMIMEタイプで本文・添付を区別します。
基本の読み取りフロー(接続→ログイン→検索→取得→解析)
最短の受信・本文表示(テキストメール想定)
import imaplib, email
IMAP_HOST = "imap.example.com"
USERNAME = "user@example.com"
PASSWORD = "app_password" # 実運用は環境変数などで安全に管理
# 1) 暗号化接続
mail = imaplib.IMAP4_SSL(IMAP_HOST, 993)
# 2) ログイン
mail.login(USERNAME, PASSWORD)
# 3) 受信箱を選択(既定はINBOX)
mail.select("INBOX") # readonlyにしたいなら mail.select("INBOX", readonly=True)
# 4) 検索(未読)
status, ids = mail.search(None, "UNSEEN")
msg_ids = ids[0].split() # b'1 2 3' → [b'1', b'2', b'3']
# 5) 取得&解析(先頭1通)
if msg_ids:
mid = msg_ids[0]
status, data = mail.fetch(mid, "(RFC822)") # メッセージ全体
raw = data[0][1] # bytes
msg = email.message_from_bytes(raw)
print("From:", msg.get("From"))
print("Subject:", msg.get("Subject"))
# 本文(プレーンテキストを優先)
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
print(part.get_payload(decode=True).decode(part.get_content_charset() or "utf-8"))
break
else:
print(msg.get_payload(decode=True).decode(msg.get_content_charset() or "utf-8"))
mail.logout()
Pythonこの型で「未読の中から1通を取り出して本文を表示」できます。multipartは本文が複数パート(text/plainとtext/html)になるため、優先順位を決めて取り出します。
ヘッダー・本文の解析(文字コード・multipart・HTML本文)
ヘッダーの文字化け対策(decode_header)
from email.header import decode_header, make_header
def decode_str(s):
return str(make_header(decode_header(s))) if s else ""
print("Subject:", decode_str(msg.get("Subject")))
print("From:", decode_str(msg.get("From")))
Python件名や差出人はエンコードされていることが多く、decode_headerで確実に可読化します。
本文の取り出し方(text/plain優先、次にtext/html)
def extract_body(msg):
if msg.is_multipart():
# text/plainを優先。なければtext/html
text_part = None
html_part = None
for part in msg.walk():
ctype = part.get_content_type()
if ctype == "text/plain" and not text_part:
text_part = part
elif ctype == "text/html" and not html_part:
html_part = part
target = text_part or html_part
if target:
charset = target.get_content_charset() or "utf-8"
return target.get_payload(decode=True).decode(charset, errors="replace")
return ""
else:
charset = msg.get_content_charset() or "utf-8"
return msg.get_payload(decode=True).decode(charset, errors="replace")
body = extract_body(msg)
print(body[:500])
Pythonメール本文はMIME構造次第。常にcharsetを取得してデコードし、欠けている場合はUTF-8に倒します。
検索条件の深掘り(UNSEEN・FROM・SUBJECT・SINCE)
代表的な検索キーと使い方
- 未読だけ拾う: UNSEEN
- 差出人を限定: FROM “sender@example.com”
- 件名の一部を検索: SUBJECT “キーワード”
- 日付で絞る: SINCE “1-Jan-2025″(RFC準拠の英語日付表現)
- 複合条件: UNSEEN FROM “a@example.com”
# 例:未読かつ差出人指定
status, ids = mail.search(None, 'UNSEEN', 'FROM', '"sender@example.com"')
# 例:日付以降(英語表記)
status, ids = mail.search(None, 'SINCE', '1-Dec-2025')
# 例:件名にキーワード(部分一致)
status, ids = mail.search(None, 'SUBJECT', '"注文確認"')
Python検索はIMAPサーバ側で実行されるため効率的です。日付はRFC準拠の英語表記が基本で、ローカル形式は使えません。
添付ファイルの保存とHTML整形(MIMEパートの扱い)
添付の取り出しと保存
from pathlib import Path
def save_attachments(msg, out_dir="attachments"):
Path(out_dir).mkdir(parents=True, exist_ok=True)
saved = []
for part in msg.walk():
if part.get_content_disposition() == "attachment":
fname = part.get_filename()
data = part.get_payload(decode=True)
if fname and data:
path = Path(out_dir) / fname
path.write_bytes(data)
saved.append(str(path))
return saved
files = save_attachments(msg)
print("Saved:", files)
Pythonattachment判定で安全に抽出。ファイル名はエンコードされていることがあるため、必要ならdecode_headerで可読化します。
HTML本文をテキスト化(必要に応じて)
# 最低限のタグ除去(簡易)
import re
html = extract_body(msg) # text/htmlを拾った場合
text = re.sub(r"<[^>]+>", "", html) # 本格対応はBeautifulSoup推奨
print(text[:300])
Python整形が必要ならBeautifulSoupでのパースを検討します(HTMLメールは装飾が多いため)。
既読・削除・移動・UID(状態操作の基礎)
既読フラグを付ける・外す
mid = msg_ids[0]
# 既読化(\Seen を追加)
mail.store(mid, "+FLAGS", r"(\Seen)")
# 未読へ戻す(\Seen を除去)
mail.store(mid, "-FLAGS", r"(\Seen)")
Pythonフラグ操作で既読・未読を制御できます。readonly=Trueでselectしている場合は変更不可です。
削除と実削除(expunge)
# 削除フラグ付与
mail.store(mid, "+FLAGS", r"(\Deleted)")
# 実削除
mail.expunge()
PythonIMAPは削除フラグ→expungeで物理削除。安全のため、まずゴミ箱(Trash)へ移動する運用もあります。
別フォルダへ移動(コピー→削除が一般的)
# 例:INBOXからArchiveへ移動
mail.copy(mid, "Archive")
mail.store(mid, "+FLAGS", r"(\Deleted)")
mail.expunge()
Python一部のサーバでは専用MOVEコマンドが使えることもありますが、コピー+削除が標準的です。
UIDで安定参照(message番号の変動対策)
# UIDベースの検索・取得(サーバ拡張によりサポート)
status, ids = mail.uid("search", None, "UNSEEN")
uid = ids[0].split()[0]
status, data = mail.uid("fetch", uid, "(RFC822)")
PythonUIDはメッセージの安定識別子。番号の変動に強く、繰り返し処理で安全です。
エラー対策と安全運用(認証・タイムアウト・再試行)
代表的な失敗への備え
- 認証失敗: 対策: メールサービスのアプリパスワード利用、2段階認証設定の確認。
- 接続失敗: 対策: ホスト名とポート(993)を確認、ネットワーク・ファイアウォールの例外設定。
- タイムアウト: 対策: socket.setdefaulttimeoutで全体のデフォルトタイムアウトを設定し、長すぎる待ちを避ける。
- 文字化け: 対策: ヘッダーはdecode_header、本文はget_content_charsetの利用とUTF-8へのフォールバック。
import socket
socket.setdefaulttimeout(20) # 全てのソケット操作にデフォルトタイムアウト
Python実務の安全設計
- 認証情報の保護: 対策: 環境変数や秘密管理を利用し、コードに平文で書かない。
- 過剰取得回避: 対策: 検索で絞り、取得は必要件数(先頭N件)に限定。
- 再試行の設計: 対策: 一時的な接続失敗は短いバックオフで再試行。ただし削除・移動など操作系は重複に注意。
例題で身につける(定番から実務まで)
例題1:未読の差出人フィルタ+本文取り出し
import imaplib, email
from email.header import decode_header, make_header
def decode_str(s): return str(make_header(decode_header(s))) if s else ""
mail = imaplib.IMAP4_SSL("imap.example.com", 993)
mail.login("user@example.com", "app_password")
mail.select("INBOX")
status, ids = mail.search(None, 'UNSEEN', 'FROM', '"sender@example.com"')
for mid in ids[0].split()[:5]:
status, data = mail.fetch(mid, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
print("Subject:", decode_str(msg.get("Subject")))
print("From:", decode_str(msg.get("From")))
# 本文(テキスト優先)
body = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/plain":
body = part.get_payload(decode=True).decode(part.get_content_charset() or "utf-8")
break
else:
body = msg.get_payload(decode=True).decode(msg.get_content_charset() or "utf-8")
print(body[:300], "\n---")
mail.logout()
Python例題2:SINCEで日付以降+添付を保存
import imaplib, email
from email.header import decode_header
from pathlib import Path
mail = imaplib.IMAP4_SSL("imap.example.com", 993)
mail.login("user@example.com", "app_password")
mail.select("INBOX")
status, ids = mail.search(None, 'SINCE', '1-Dec-2025')
Path("attachments").mkdir(exist_ok=True)
for mid in ids[0].split()[:3]:
status, data = mail.fetch(mid, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
for part in msg.walk():
if part.get_content_disposition() == "attachment":
fname = part.get_filename()
if fname:
Path("attachments", fname).write_bytes(part.get_payload(decode=True))
mail.logout()
Python例題3:既読化・移動・削除(安全な順序)
import imaplib
mail = imaplib.IMAP4_SSL("imap.example.com", 993)
mail.login("user@example.com", "app_password")
mail.select("INBOX")
status, ids = mail.search(None, 'UNSEEN')
for mid in ids[0].split()[:2]:
mail.store(mid, "+FLAGS", r"(\Seen)") # 既読化
mail.copy(mid, "Archive") # アーカイブへコピー
mail.store(mid, "+FLAGS", r"(\Deleted)") # 削除フラグ
mail.expunge() # まとめて実削除
mail.logout()
Python例題4:UIDベースで未読本文を安定取得
import imaplib, email
mail = imaplib.IMAP4_SSL("imap.example.com", 993)
mail.login("user@example.com", "app_password")
mail.select("INBOX")
status, ids = mail.uid("search", None, "UNSEEN")
for uid in ids[0].split()[:5]:
status, data = mail.uid("fetch", uid, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
print(msg.get("Subject"))
mail.logout()
Pythonまとめ
IMAP読み取りは「暗号化接続→ログイン→フォルダ選択→検索→取得→MIME解析」が基本線です。検索キー(UNSEEN/FROM/SUBJECT/SINCE)でサーバ側絞り込みを徹底し、emailモジュールでヘッダーのデコードと本文・添付の取り出しを正しく行う。既読・削除・移動はフラグ操作とexpungeの理解が要点で、安定参照にはUIDが有効です。認証はアプリパスワードを含むサービス側の推奨に従い、タイムアウトと例外対策を最初から入れる。これらの型を身につければ、初心者でも短いコードで「安全・確実・再現性高い」メール読み取りができます。
