Python | Web / API:ファイル添付メール

Python Python
スポンサーリンク

概要(ファイル添付は「正しいMIMEと暗号化」で確実に届ける)

Python標準のsmtplibとemail.message.EmailMessageを使えば、PDFや画像、Excelなどを安全に添付して送信できます。重要なのは、SMTPの接続方式(587+STARTTLS または 465+SSL)、正しいMIMEタイプの付与、本文と添付を持つmultipartメールの組み立て、UTF-8での日本語対応、そして失敗時の例外処理です。まずは最短の添付送信を理解し、次に複数添付やHTML+インライン画像、CC/BCCまで広げると実務で困りません。


基本の考え方(MIMEとmultipartの土台)

添付の正体は「MIMEパートの積み重ね」

メールはMIMEという規格で構成されます。本文はtext/plainやtext/html、ファイルはapplication/pdfやimage/pngなどのタイプを持つ「パート」として追加され、全体がmultipart/mixedとして送られます。EmailMessageはこのMIME構造を自動的に組んでくれるため、初心者でも間違えにくいのが利点です。

文字コードと日本語の扱い

本文はUTF-8で統一すると文字化けしにくく、EmailMessage.set_content(…, charset=”utf-8″)で明示できます。件名・差出人・宛先に日本語を含む場合でも、EmailMessageが適切にエンコードしてくれます。


最短の添付付き送信(STARTTLSの基本形)

テキスト本文+1ファイル添付の最短パターン

import smtplib
from email.message import EmailMessage
import mimetypes

SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587  # STARTTLS
USERNAME = "user@example.com"
PASSWORD = "your_app_password"

msg = EmailMessage()
msg["Subject"] = "週次レポート送付"
msg["From"] = USERNAME
msg["To"] = "to@example.com"
msg.set_content("レポートを添付します。ご確認ください。", charset="utf-8")

path = "report.pdf"
ctype, _ = mimetypes.guess_type(path)
maintype, subtype = (ctype.split("/", 1) if ctype else ("application", "octet-stream"))
with open(path, "rb") as f:
    msg.add_attachment(f.read(), maintype=maintype, subtype=subtype, filename="report.pdf")

with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) as server:
    server.starttls()               # 暗号化開始(STARTTLS)
    server.login(USERNAME, PASSWORD)
    server.send_message(msg)
Python

この型を覚えれば、PDFでも画像でも基本的に送れます。mimetypes.guess_typeでファイル拡張子からMIMEタイプを推定し、未知の拡張子はapplication/octet-streamに倒すのが安全です。

SMTPS(465)を使う場合の最短パターン

import smtplib
from email.message import EmailMessage
import mimetypes

SMTP_HOST = "smtp.example.com"
SMTP_PORT = 465  # SMTPS

msg = EmailMessage()
msg["Subject"] = "SSL経由での添付送信"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"
msg.set_content("SSL接続で添付ファイルを送信します。", charset="utf-8")

path = "image.png"
ctype, _ = mimetypes.guess_type(path)
maintype, subtype = (ctype.split("/", 1) if ctype else ("application", "octet-stream"))
with open(path, "rb") as f:
    msg.add_attachment(f.read(), maintype=maintype, subtype=subtype, filename="image.png")

with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=15) as server:
    server.login("user@example.com", "your_app_password")
    server.send_message(msg)
Python

465では最初からTLSで保護された接続を使うため、starttls()は不要です。


実務の添付パターン(複数添付・大きなファイル・拡張子対応)

複数ファイルをまとめて添付する

import smtplib
from email.message import EmailMessage
import mimetypes

files = ["report.xlsx", "chart.png", "notes.txt"]

msg = EmailMessage()
msg["Subject"] = "資料一式"
msg["From"] = "user@example.com"
msg["To"] = "team@example.com"
msg.set_content("資料を3点添付します。", charset="utf-8")

for path in files:
    ctype, _ = mimetypes.guess_type(path)
    maintype, subtype = (ctype.split("/", 1) if ctype else ("application", "octet-stream"))
    with open(path, "rb") as f:
        msg.add_attachment(f.read(), maintype=maintype, subtype=subtype, filename=path)

with smtplib.SMTP("smtp.example.com", 587, timeout=20) as s:
    s.starttls(); s.login("user@example.com", "your_app_password"); s.send_message(msg)
Python

EmailMessage.add_attachmentを繰り返し呼ぶだけで、複数添付のmultipart/mixedが自動で組まれます。

大きなファイルの注意点と代替案

メールにはサーバ側のサイズ制限(例:10〜25MB程度)があり、超えると配送失敗します。可能ならクラウドの共有リンクへ切り替え、メール本文にURLを記載する方が現実的です。どうしても送る場合は圧縮してサイズを抑え、失敗時のエラーハンドリングを入れておきます。

拡張子とMIMEタイプの明示

未知の拡張子や独自形式ではmimetypesがNoneを返すことがあります。その場合はapplication/octet-streamに倒し、拡張子付きのfilenameを必ず付けると受信側で扱いやすくなります。


HTMLメール+インライン画像(見やすさと一体感)

テキスト+HTMLの multipart/alternative とインライン画像の追加

from email.message import EmailMessage
from email.utils import make_msgid
import smtplib, mimetypes

msg = EmailMessage()
msg["Subject"] = "レポート(HTML版+インライン画像)"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"

msg.set_content("HTML版が表示できない環境向けのテキスト版です。", charset="utf-8")

cid = make_msgid()  # <...@domain> の形式
html = f"""
<html><body>
<h1>売上レポート</h1>
<p>詳細は以下の画像をご覧ください。</p>
<img src="cid:{cid[1:-1]}" alt="chart" />
</body></html>
"""
msg.add_alternative(html, subtype="html")

path = "chart.png"
ctype, _ = mimetypes.guess_type(path)
maintype, subtype = (ctype.split("/", 1) if ctype else ("image", "png"))
with open(path, "rb") as f:
    msg.get_payload()[1].add_related(f.read(), maintype=maintype, subtype=subtype, cid=cid, filename="chart.png")

with smtplib.SMTP("smtp.example.com", 587) as s:
    s.starttls(); s.login("user@example.com", "your_app_password"); s.send_message(msg)
Python

multipart/alternativeでテキストとHTMLの両方を持たせ、HTML側にcidで参照するインライン画像をadd_relatedで追加します。外部リンク画像よりも安定表示になりやすいです。


宛先の扱い(複数宛先・CC・BCC)

複数宛先とCC/BCCの正しい送信

import smtplib
from email.message import EmailMessage

to_list = ["a@example.com", "b@example.com"]
cc_list = ["c@example.com"]
bcc_list = ["d@example.com"]  # ヘッダーには載せない

msg = EmailMessage()
msg["Subject"] = "定例報告"
msg["From"] = "user@example.com"
msg["To"] = ", ".join(to_list)
msg["Cc"] = ", ".join(cc_list)
msg.set_content("To・Ccに同報。Bccはヘッダーに載りません。", charset="utf-8")

with smtplib.SMTP("smtp.example.com", 587) as s:
    s.starttls(); s.login("user@example.com", "your_app_password")
    s.send_message(msg, to_addrs=to_list + cc_list + bcc_list)
Python

BCCはヘッダーに記載しないため、send_messageのto_addrsでまとめて渡すのが正しいやり方です。


エラー対策と安全運用(認証・タイムアウト・情報保護)

失敗に備える例外処理の基本線

import smtplib, socket
from email.message import EmailMessage

msg = EmailMessage()
msg["Subject"] = "添付送信テスト"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"
msg.set_content("本文", charset="utf-8")

try:
    with smtplib.SMTP("smtp.example.com", 587, timeout=15) as s:
        s.starttls()
        s.login("user@example.com", "your_app_password")
        s.send_message(msg)
except smtplib.SMTPAuthenticationError:
    print("認証エラー:ユーザー名・パスワードやアプリパスワードを再確認")
except smtplib.SMTPConnectError:
    print("接続エラー:ホスト・ポート・ネットワーク到達性を確認")
except (smtplib.SMTPException, socket.timeout) as e:
    print("送信失敗:", e)
Python

接続・認証・一般的なSMTP例外、タイムアウトを捕捉して原因を切り分けます。timeoutを必ず付けて「待ち続け」を防ぐのが基本です。

認証情報の守り方と運用の注意

認証情報はコードに直書きせず、環境変数や秘密管理を使います。Gmailなどは2段階認証+アプリパスワードが推奨です。大量配信や外部宛にはドメイン側のSPF/DKIM整備で到達率が上がります。添付はサイズ上限に注意し、超える可能性があるものは共有リンクへ切り替える設計が現実的です。


例題で身につける(定番から実務まで)

例題1:PDF添付をSTARTTLSで送る

import smtplib
from email.message import EmailMessage
import mimetypes

msg = EmailMessage()
msg["Subject"] = "PDF添付送信"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"
msg.set_content("PDFを添付しました。", charset="utf-8")

ctype, _ = mimetypes.guess_type("report.pdf")
maintype, subtype = (ctype.split("/", 1) if ctype else ("application", "octet-stream"))
with open("report.pdf", "rb") as f:
    msg.add_attachment(f.read(), maintype=maintype, subtype=subtype, filename="report.pdf")

with smtplib.SMTP("smtp.example.com", 587, timeout=10) as s:
    s.starttls(); s.login("user@example.com", "your_app_password"); s.send_message(msg)
Python

例題2:画像とExcelの複数添付

import smtplib
from email.message import EmailMessage
import mimetypes

msg = EmailMessage()
msg["Subject"] = "画像+Excelの添付"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"
msg.set_content("画像とExcelを添付します。", charset="utf-8")

for path in ["chart.png", "data.xlsx"]:
    ctype, _ = mimetypes.guess_type(path)
    maintype, subtype = (ctype.split("/", 1) if ctype else ("application", "octet-stream"))
    with open(path, "rb") as f:
        msg.add_attachment(f.read(), maintype=maintype, subtype=subtype, filename=path)

with smtplib.SMTP("smtp.example.com", 587) as s:
    s.starttls(); s.login("user@example.com", "your_app_password"); s.send_message(msg)
Python

例題3:HTML+インライン画像で見やすく

from email.message import EmailMessage
from email.utils import make_msgid
import smtplib, mimetypes

msg = EmailMessage()
msg["Subject"] = "HTML+インライン画像"
msg["From"] = "user@example.com"
msg["To"] = "to@example.com"
msg.set_content("テキスト版です。", charset="utf-8")

cid = make_msgid()
html = f'<html><body><h2>ダッシュボード</h2><img src="cid:{cid[1:-1]}"></body></html>'
msg.add_alternative(html, subtype="html")

ctype, _ = mimetypes.guess_type("chart.png")
maintype, subtype = (ctype.split("/", 1) if ctype else ("image", "png"))
with open("chart.png", "rb") as f:
    msg.get_payload()[1].add_related(f.read(), maintype=maintype, subtype=subtype, cid=cid, filename="chart.png")

with smtplib.SMTP("smtp.example.com", 587) as s:
    s.starttls(); s.login("user@example.com", "your_app_password"); s.send_message(msg)
Python

例題4:CC/BCC込みの安全送信

import smtplib
from email.message import EmailMessage

to_list = ["a@example.com", "b@example.com"]
cc_list = ["c@example.com"]
bcc_list = ["d@example.com"]

msg = EmailMessage()
msg["Subject"] = "配信テスト(CC/BCC)"
msg["From"] = "user@example.com"
msg["To"] = ", ".join(to_list)
msg["Cc"] = ", ".join(cc_list)
msg.set_content("CCとBCCを含む送信例です。", charset="utf-8")

with smtplib.SMTP("smtp.example.com", 587, timeout=15) as s:
    s.starttls(); s.login("user@example.com", "your_app_password")
    s.send_message(msg, to_addrs=to_list + cc_list + bcc_list)
Python

まとめ

ファイル添付メールの核心は、SMTPの暗号化(587+STARTTLSまたは465+SSL)と正しいMIME構造の組み立てです。EmailMessage.add_attachmentで拡張子に見合うMIMEタイプを付け、本文はUTF-8で明示。複数添付やHTML+インライン画像、CC/BCCも同じ型で拡張できます。認証情報は安全に管理し、タイムアウトと例外処理を必ず入れて「落ちない・届く」運用を徹底する。これらを身につければ、初心者でも短いコードで実務品質の添付メールを安定して送れます。

タイトルとURLをコピーしました