Python標準ライブラリでiCloudメールを送受信
Pythonの標準ライブラリのみを使ってメールの送受信をしてみました。
他人に送信するのはもちろん、自分に送信すればエラーなどの自動通知システムとして利用することもできます。(自分はこれが目的)
今回使うメーラーはiCloudメールですが、パスワードを用意すればGmailなどでも動作するはずです。
Tip
前提知識:
メールの送信にはSMTPというプロトコルが使われます。
ついさっき知ったのですが、SMTPはメールサーバー同士だけでなく、クライアントからサーバーへの送信にも使われます。
そして、SMTPは送信専用でサーバーからクライアントへの受信にはIMAPという別のプロトコルが使われます。
アプリパスワードの生成
メール送信にはパスワードが必要です。このパスワードはApple Accountのパスワードとは別のもので、事前に作成する必要があります。
https://account.apple.com にアクセスしてサインインすると、下の方にアプリ用パスワードという項目があります。
以降はここで生成したパスワードを使います。
送信
emailに関連するデータを扱うemailパッケージとSMTPを扱うsmtplibを使います。いずれも標準ライブラリなのでインストール不要です。
開いたSMTPセッションは最後にQUITコマンドで閉じる必要がありますが、Pythonのwith構文を使うことでスコープを抜けた際に自動で処理されます。
import smtplib
from email.message import EmailMessage
SMTP_SERVER = "smtp.mail.me.com"
SMTP_PORT = 587
ADDR_FROM = "[email protected]"
ADDR_TO = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
SUBJECT = "Message from Python"
MESSAGE = "hello, world!"
message = EmailMessage()
message.set_content(MESSAGE)
message["Subject"] = SUBJECT
message["From"] = ADDR_FROM
message["To"] = ADDR_TO
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as smtp:
smtp.starttls()
smtp.login(ADDR_FROM, PASSWORD)
smtp.send_message(message)
Tip
SMTPサーバーのホストはGmail:smtp.gmail.com、Outlook:smtp-mail.outlook.comとなります。
ちなみにiCloudメールのSMTPサーバーがsmtp.mail.me.comなのは、iCloudの前身であるMobileMeでme.comが使われていた名残だそうです。
html送信
プレーンテキストではなく、htmlを送信する場合はset_contentの引数にsubtype='html'を渡します。
message.set_content("<h1>hello, world!</h1>", subtype='html')
受信
先述の通り、SMTPは送信専用で受信はIMAPなので、こちらはimaplibを使います。
smtplib同様、Python標準ライブラリなのでインストール不要です。
IMAPはコマンドでやり取りするスタイルで、imaplibではそれぞれのコマンドに対応した関数が用意されています。
そのため、使いこなすにはIMAPコマンドの使い方を調べることをおすすめします。
受信サンプルコード
最新メッセージ取得
最新の5件のメッセージを取得して、新しい順に表示します。
import imaplib
import email
from email import policy
IMAP_SERVER = "imap.mail.me.com"
MAIL_ADDR = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(MAIL_ADDR, PASSWORD)
message_total = int(imap.select("INBOX")[1][0]) # メールボックスを選択してメッセージの総数を取得
for i in range(5):
status, messages = imap.fetch(str(message_total-i), "(BODY.PEEK[])") # 本文を既読フラグを立てずに全て取得
message = email.message_from_bytes(messages[0][1], policy=policy.default)
print(f"Date: {message.get('Date')}")
print(f"From: {message.get('From')}")
print(f"To: {message.get('To')}")
print(f"Subject:{message.get('Subject')}")
print(f"Message:{message.get_body(('plain','html')).get_content()}")
print("--------------------------------------------------------------------------------\n")
imap.close()
imap.logout()
未読メッセージ取得
未読メッセージを取得します
import imaplib
import email
from email import policy
IMAP_SERVER = "imap.mail.me.com"
MAIL_ADDR = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(MAIL_ADDR, PASSWORD)
imap.select("INBOX")
status, data = imap.search(None, "UNSEEN")
if data[0]==None:
print("no result")
else:
unseen_messages = data[0].decode().split()
for message_id in unseen_messages:
status, messages = imap.fetch(message_id, "(BODY.PEEK[])")
message = email.message_from_bytes(messages[0][1], policy=policy.default)
print("---------------------------------")
print(f"Date: {message.get('Date')}")
print(f"From: {message.get('From')}")
print(f"To: {message.get('To')}")
print(f"Subject:{message.get('Subject')}")
imap.close()
imap.logout()
SELECTコマンド
メールボックスを選択します。
成功するとメッセージの総数が返されます。
>>> print(imap.select("INBOX"))
('OK', [b'87'])
FETCHコマンド
指定したメッセージのデータを取得します。
メッセージ番号は1,3,7として複数指定したり、5:10として範囲指定することもできます。ただ、返されるメッセージの順番はRFCで規定されてないようで、指定順に関わらずメッセージ番号の昇順で返されることが多いようです。
Note
メッセージ番号とUID
多くのコマンドで帰ってくるメッセージ番号はただの連番で、メールの削除などによって変化します。
一方UIDはメールボックス内で固有で、新しいメッセージに対し昇順で割り当てられます。
オプションによって取得するデータを指定することができ、スペースで区切れば複数のデータを指定することもできます。
オプションの例
BODY[]: 本文を取得([ ]内はmultipartの番号、空なら全て取得)
BODY.PEEK[]: BODY[]と同じですが、既読にしません
FLAGS: フラグを取得
UID: メッセージ固有のIDを取得
imaplibではfetch関数の第一引数にメッセージ番号を、第二引数にオプションを渡します。
オプションは丸括弧で囲った文字列として渡します。
imap.fetch("1,5", "(BODY[] FLAGS)")
# 1と5番のメッセージの本文とフラグを取得
SEARCHコマンド
メッセージを検索するコマンドです。
検索条件には以下のようなものがあります。
ALL: 全てのメッセージ
UNSEEN: 未読メッセージ
TEXT "検索文字列": 件名、本文、送信元など、全体を検索
SUBJECT "検索文字列": 件名で検索
FROM "送信元": 送信元で検索
スペースで区切るとAND検索、OR検索は(OR TEXT "hello" TEXT "world")のように記述します。
imaplibではsearchメソッドの第一引数にcharset,第二引数に検索クエリを渡します。
>>> imap.search(None, '(UNSEEN FROM "[email protected]")')
('OK', [b'77 8 6 50 86 75 88'])
日本語で検索する場合はutf-8でエンコードします。
imap.search("UTF-8", '(SUBJECT "こんにちは")'.encode('utf-8'))
multipart
メールにはmultipartという仕組みがあり、ファイルの添付(multipart/mixed)やhtmlとplain-textの複数形式の送信(multipart/alternative)などに使われます。
multipartはネストが可能なため、本来であれば再帰関数で処理する必要がありますが、email.messageクラスではwalk()メソッドでpartのリスト(正確にはgenerator)を取得することができます。
また、EmailMessage.get_body()関数で必要とするContent-Typeのpartを取得することもできます。該当するものが無い時はNoneが返ります。
multipartを表示するサンプルプログラム
emailパッケージにはmultipartの構造を表示する`_structure()`というデバッグ関数があります。import imaplib
import email
from email import policy
from email.iterators import _structure
IMAP_SERVER = "imap.mail.me.com"
MAIL_ADDR = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(MAIL_ADDR, PASSWORD)
message_total = int(imap.select("INBOX")[1][0])
for i in range(5):
status, messages = imap.fetch(str(message_total-i), "(BODY.PEEK[])")
message = email.message_from_bytes(messages[0][1], policy=policy.default)
print("\n------------------------------\n")
print(f"Date: {message.get('Date')}")
print(f"From: {message.get('From')}")
print(f"To: {message.get('To')}")
print(f"Subject:{message.get('Subject')}")
print("")
iterators._structure(message)
imap.close()
imap.logout()
Date: Thu, 02 Jul 2026 17:18:23 +0900
From: moris <[email protected]>
To: [email protected]
Subject:pdfファイル添付テスト
multipart/mixed
multipart/alternative
text/plain
text/html
application/pdf
--------------------------
Date: Mon, 29 Jun 2026 23:06:20 +0000
From: [email protected]
To: [email protected]
Subject:iCloud メールテスト
text/html
--------------------------
Date: Mon, 29 Jun 2026 08:56:46 +0000
From: Google <[email protected]>
To: [email protected]
Subject:[email protected] のセキュリティ通知
multipart/alternative
text/plain
text/html
--------------------------
Date: Sun, 28 Jun 2026 13:22:36 +0000
From: Cloudflare Registrar <[email protected]>
To: [email protected]
Subject:30 Day Domain Renewal Notice
multipart/alternative
text/plain
text/html
メッセージ削除
メッセージをゴミ箱に入れるには\Deletedフラグを追加します。
IMAPでフラグの操作にはSTOREコマンドを使います。
Googleのログイン通知をゴミ箱に入れるサンプル
import imaplib
IMAP_SERVER = "imap.mail.me.com"
MAIL_ADDR = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(MAIL_ADDR, PASSWORD)
imap.select("INBOX")
status, message_ids = imap.search("UTF-8", '(FROM "[email protected]" SUBJECT "セキュリティ通知")'.encode('utf-8'))
if message_ids[0]==None:
print("no result")
else:
message_ids = message_ids[0].split()
for message_id in message_ids:
imap.store(message_id, "+FLAGS", "\\Deleted")
print(f"{message_id} +FLAG Deleted")
imap.close()
imap.logout()
メールボックス一覧を表示
import imaplib
IMAP_SERVER = "imap.mail.me.com"
MAIL_ADDR = "[email protected]"
PASSWORD = "xxxx-xxxx-xxxx-xxxx"
imap = imaplib.IMAP4_SSL(IMAP_SERVER)
imap.login(MAIL_ADDR, PASSWORD)
status, boxes = imap.list()
for box in boxes:
print(box.decode())
imap.logout()
() "/" "Archive"
() "/" "Junk"
(\Noinferiors) "/" "INBOX"
(\Trash) "/" "Deleted Messages"
(\Sent) "/" "Sent Messages"
() "/" "Drafts"
参考ページ
Python email.message: Representing an email message
https://docs.python.org/3/library/email.message.html
Python smtplib — SMTP protocol client
https://docs.python.org/3/library/smtplib.html
Python imaplib — IMAP4 protocol client
https://docs.python.org/3/library/imaplib.html
ITmedia IMAP4のSEARCH条件の一覧
https://atmarkit.itmedia.co.jp/fnetwork/rensai/netpro09/imap4-searchoption.html
ITmedia IMAP4のFETCHオプションの一覧
https://atmarkit.itmedia.co.jp/fnetwork/rensai/netpro09/imap4-fetch.html