Python WebAcademy Blog

Pythonでメールを送るには?smtplibとEmailMessageの基本と、パスワードをコードに直書きしてはいけない理由を初心者向けに解説

|

毎朝のレポートや処理の完了通知を、Pythonから自動でメールしたい。そんなときに使うのが標準ライブラリのsmtplibとemailです。メールが届くまでの仕組み、EmailMessageで本文と件名を組み立てる方法、465番と587番のポートの違い、添付ファイルの付け方、Bccの扱い、パスワードを安全に渡す方法まで、手元で動かせるコードとともに初心者向けに解説します。

毎朝の集計結果を、上司やチームにメールで送っている。そんな作業を、Pythonに任せられたら楽だと思いませんか?

実はPythonには、メールを送るための道具が最初から入っています。追加のインストールは必要ありません。

使うのは、送信を担当するsmtplibと、メールの中身を組み立てるemailという2つの標準ライブラリです。この2つの役割を分けて考えるだけで、メール送信のコードはぐっと読みやすくなります。

今回は、メールが届くまでの仕組みから、添付ファイルの付け方、パスワードを安全に扱う方法までを順番に見ていきます。最後まで読めば、自分のスクリプトに通知メールを組み込めるようになるはずです。

メールが届くまでの仕組みをざっくり知っておく

コードを書く前に、メールがどうやって相手に届くのかを押さえておきましょう。ここがわかると、エラーが出たときに原因の見当がつけやすくなります。

メールを送るときには、SMTPという決まりごとが使われます。SMTPはSimple Mail Transfer Protocolの略で、メールを送り出すための通信のルールです。

私たちが書くPythonのプログラムは、直接相手のパソコンへメールを届けるわけではありません。まず自分が使っているメールサービスのSMTPサーバーへ渡し、そこから先はサーバー同士がバトンをつないで届けてくれます。

郵便にたとえると、SMTPサーバーは近所の郵便ポストのようなものです。Pythonの仕事は、宛名と中身をそろえた手紙をポストへ正しく入れるところまでだと考えてください。

smtplibとemailの役割分担

メール送信では、2つの標準ライブラリが別々の仕事を受け持ちます。役割の違いを表で整理してみましょう。

ライブラリ 担当する仕事 たとえるなら
email 件名・宛先・本文・添付ファイルを組み立てる 手紙と封筒を用意する
smtplib SMTPサーバーにつないでメールを渡す 郵便ポストに投函する
ssl 通信を暗号化し、相手のサーバーが本物か確かめる 中身が見えない封筒にする

昔の解説記事では、MIMETextやMIMEMultipartというクラスを組み合わせる書き方をよく見かけます。今でも動きますが、新しく書くならemail.message.EmailMessageを使うのがおすすめです。

EmailMessageを使うと、日本語の件名や本文の文字コード、添付ファイルの区切りといった面倒な部分を、ライブラリ側がまとめて面倒を見てくれます。公式ドキュメントのサンプルも、このクラスを使った形で書かれています。

まずはEmailMessageでメールの中身を作ってみる

いきなり送信まで進むと、失敗したときに原因の切り分けが難しくなります。そこで最初は、メールの中身を作って画面に表示するところまでを試してみましょう。

次のコードは、サーバーにはつながずにメールを組み立てるだけのものです。安心して何度でも実行できます。

from email.message import EmailMessage

msg = EmailMessage()
msg["Subject"] = "本日の売上レポート"
msg["From"] = "sender@example.com"
msg["To"] = "boss@example.com"
msg.set_content("お疲れさまです。\n本日の売上は前日比で105%でした。")

print(msg)

実行すると、Subject・From・Toといったヘッダーと、本文が並んだテキストが表示されます。日本語の件名が見慣れない記号の列に変わっているかもしれませんが、心配はいりません。

これはメールの世界で日本語を安全に運ぶためのエンコードで、受け取った側のメールソフトがもとの文字に戻してくれます。自分で変換処理を書かなくてよいのが、EmailMessageのありがたいところです。

宛先を複数にしたいときは、Toにカンマ区切りの文字列を入れれば大丈夫です。CcやBccも、同じように辞書のような書き方で指定できます。

SMTPサーバーにつないでメールを送る

中身ができたら、いよいよ送信です。ここで登場するのがsmtplibですね。

465番ポートと587番ポートの違い

SMTPサーバーにつなぐ方法は、大きく分けて2通りあります。どちらを使うかはメールサービスによって決まるので、まずは違いを見ておきましょう。

方式 よく使われるポート smtplibでの書き方 特徴
最初から暗号化する 465 smtplib.SMTP_SSL つないだ瞬間から通信が暗号化される
途中から暗号化に切り替える 587 smtplib.SMTP と starttls() 平文でつないでから暗号化へ切り替える
暗号化しない 25 smtplib.SMTP のみ インターネット越しの送信には使わない

ポートとは、1台のサーバーの中で、どの窓口に話しかけるかを示す番号のことです。465番と587番のどちらに対応しているかは、利用しているメールサービスのヘルプに書かれています。

どちらの方式でも、ssl.create_default_context()で作った設定を渡すようにしてください。これを省くと、相手が本物のサーバーかどうかの確認が甘くなるおそれがあります。

送信までのコード

ここでは、465番ポートを使う形で書いてみます。パスワードの読み込み方は、このあとの章でくわしく説明します。

import os
import smtplib
import ssl
from email.message import EmailMessage

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

user = os.environ["MAIL_USER"]
password = os.environ["MAIL_PASSWORD"]

msg = EmailMessage()
msg["Subject"] = "本日の売上レポート"
msg["From"] = user
msg["To"] = "boss@example.com"
msg.set_content("お疲れさまです。\n本日の売上は前日比で105%でした。")

context = ssl.create_default_context()
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context) as server:
    server.login(user, password)
    server.send_message(msg)

print("送信しました")

with文を使っているので、ブロックを抜けると接続が自動で閉じられます。送信の途中でエラーが起きても、後片付けを忘れずに済むわけです。

587番ポートを使う場合は、smtplib.SMTP(SMTP_HOST, 587)でつないだあと、loginの前にserver.starttls(context=context)を1行足してください。違いはそれだけです。

with文が裏側で何をしているのか気になった方は、こちらの記事もあわせてどうぞ。

【関連記事】Pythonのコンテキストマネージャーって何?

パスワードをコードに直書きしてはいけない理由

さきほどのコードで、パスワードをos.environから読み込んでいたことに気づいたでしょうか。ここは今回いちばん大事なポイントです。

動作確認のつもりで、password = "abcd1234"のようにコードへ直接書いてしまう人は少なくありません。けれど、そのファイルをGitHubに上げたり、同僚に渡したりした瞬間に、メールアカウントの鍵を配って回ることになります。

私も10年ほどエンジニアとして働いてきましたが、テスト用のつもりで書いた認証情報がリポジトリに残っていて、慌てて全員分のパスワードを変更したことがあります。消したつもりでも、Gitの履歴には残り続けるのがやっかいなところです。

パスワードのような秘密の情報は、環境変数や.envファイルに置き、コードからは名前で呼び出すようにしましょう。具体的な設定方法は、次の記事でくわしく紹介しています。

【関連記事】環境変数とは?PythonでAPIキーを安全に扱う.envとos.environの使い方

Gmailを使うときはアプリ パスワードが必要になる

個人のGmailから送りたいという方も多いと思います。その場合、ふだんGoogleにログインするときのパスワードでは、たいていログインに失敗します。

Googleのヘルプによると、こうした場面で使うのがアプリ パスワードです。これは2段階認証プロセスを有効にしたアカウントで作れる、16桁の専用パスコードだと説明されています。

ただし、Google自身はアプリ パスワードの利用を推奨していないとも書いています。業務で本格的に使うなら、会社のメールサーバーや、メール配信サービスのAPIを使う方法も検討してみてください。

添付ファイルを付けて送る

レポートを送るなら、CSVやExcelのファイルを添付したくなりますよね。EmailMessageなら、add_attachmentというメソッドを1回呼ぶだけで添付できます。

次の例では、同じフォルダにあるCSVファイルを添付しています。ファイルはバイト列として読み込むのがポイントです。

from pathlib import Path
from email.message import EmailMessage

msg = EmailMessage()
msg["Subject"] = "本日の売上データ"
msg["From"] = "sender@example.com"
msg["To"] = "boss@example.com"
msg.set_content("本日の売上データを添付します。")

path = Path("sales.csv")
msg.add_attachment(
    path.read_bytes(),
    maintype="text",
    subtype="csv",
    filename=path.name,
)

maintypeとsubtypeは、ファイルの種類を受け取り側に伝えるための情報です。画像ならimageとpng、PDFならapplicationとpdfのように指定します。

どれを指定すればよいかわからないときは、標準ライブラリのmimetypesを使うと、ファイル名から推測できます。ファイルパスの扱いに慣れていない方は、pathlibの記事もあわせて読んでみてください。

【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説

初心者がつまずきやすい落とし穴

メール送信のコードは短いぶん、つまずくポイントがある程度決まっています。よくあるものを表にまとめました。

症状 よくある原因 対処のしかた
SMTPAuthenticationErrorが出る パスワードの誤り、アプリ パスワード未設定 サービスのヘルプでログイン方法を確認する
接続がタイムアウトする ポート番号の誤り、社内ネットワークでの制限 465と587のどちらが正しいか確認する
SSLのエラーや切断が起きる 465番にSMTPでつないだ、587番にSMTP_SSLでつないだ ポートと方式の組み合わせを見直す
迷惑メールに入る 送信者アドレスとログインしたアカウントが違う Fromにはログインしたアドレスを使う

中でも多いのが、ポートと方式の組み合わせの間違いです。エラーメッセージが英語で長く表示されると焦りますが、まずはこの表の3行目を疑ってみてください。

Bccに入れた宛先はどうなるのか

もうひとつ、知っておきたいのがBccの扱いです。Bccに入れた人の名前が、ほかの受信者に見えてしまわないか心配になりますよね。

公式ドキュメントによると、send_messageはTo・Cc・Bccに書かれたアドレスをすべて宛先として使います。そのうえで、送信前にBccのヘッダーをメッセージから取り除いてくれます。

つまり、EmailMessageとsend_messageの組み合わせなら、Bccの宛先がほかの人に漏れる心配はありません。ただし、古い書き方のsendmailメソッドに文字列を渡す場合は自分で気をつける必要があるので、新しく書くならsend_messageを使いましょう。

送信の失敗に備えて例外を受け止める

メールサーバーは外部のサービスなので、いつでも必ずつながるとは限りません。夜中に動かしている自動送信が、サーバーのメンテナンスで失敗することもあります。

smtplibのエラーは、SMTPExceptionという例外の仲間としてまとめられています。try文で受け止めて、失敗したことをログに残しておくと、翌朝に原因を追いかけやすくなります。

次のコードは、さきほどの送信のコードで作った変数をそのまま使う前提で書いています。

import smtplib

try:
    with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context) as server:
        server.login(user, password)
        server.send_message(msg)
except smtplib.SMTPAuthenticationError:
    print("ログインに失敗しました。ユーザー名とパスワードを確認してください")
except (smtplib.SMTPException, OSError) as e:
    print("メールを送れませんでした:", e)

OSErrorも一緒に受け止めているのは、ネットワークにつながらないときのエラーがこちらで届くことがあるからです。try文の基本の流れを復習したい方は、こちらをどうぞ。

【関連記事】Pythonのtry・except・else・finallyとは?

メール送信を毎日の自動化に組み込む

ここまでできれば、あとはスクリプトを決まった時間に動かすだけです。毎朝8時にレポートを送る、夜間バッチが終わったら結果を知らせる、といった使い方ができます。

私が実務でいちばん助けられたのは、失敗したときだけメールを送る仕組みでした。毎回成功の連絡が来るとそのうち誰も読まなくなりますが、失敗のときだけ届くメールは、さすがに全員が開きます。

定期実行の方法は、cronやタスクスケジューラの記事で紹介しています。チャットでの通知のほうが合っている職場なら、SlackやTeamsへ送る方法も選択肢に入るでしょう。

【関連記事】Pythonで定期実行するには?cronとタスクスケジューラの基本

メールとチャットのどちらが向いているかは、受け取る人の働き方しだいです。まずは自分宛てに送るところから始めて、使い勝手を確かめてみてください。

まとめ

今回は、Pythonでメールを送る基本を見てきました。最後に、押さえておきたいポイントを振り返っておきましょう。

メールの中身はemailのEmailMessageで組み立て、送信はsmtplibに任せます。465番ならSMTP_SSL、587番ならSMTPとstarttlsの組み合わせで、どちらの場合もssl.create_default_context()を渡すのが基本です。

そして何より、パスワードはコードに直書きせず、環境変数から読み込むようにしてください。これを守るだけで、うっかり認証情報を公開してしまう事故の多くは防げます。

最初の一歩としては、printでメールの中身を確認するところから始めるのがおすすめです。中身が思いどおりにできていることを確かめてから、自分宛てに1通送ってみましょう。

ここまでお読みいただきありがとうございました。

参考情報

次のアクション

記事で学んだ内容を実際に動かしてみよう

Python WebAcademyでは、ブラウザ上でコードを書きながら基礎から実践まで体系的に学べます。

Python WebAcademyの学習画面

あわせて読む

関連記事

ブログ一覧へ

Python学習ロードマップ

まずはこの3講座から

記事で気になったテーマを、順番に手を動かしながら学べます。

ロードマップを見る