🐍

Pythonでのメール送信

2022/02/05に公開
2

Pythonを使ってメール送信の機能を使う場面としては、AWS LambdaやGCPのCloudFunctionsなどで、お知らせやエラー通知などの通知系で使う機会が多いです。

Pythonにはメール送信を行うための機能として、smtplibというSMTPクライアントのライブラリが標準として提供されています。
メール送信時にはMIME形式のデータを扱う必要がありますが、こちらもemailというライブラリが標準で提供されています。

特にサードパーティ系のライブラリをインストールする必要はないため、Python自体が動く環境があれば今回のサンプルコードを動かすことは可能です。

Pythonのバージョンは3.8を使用していますが、3系であれば基本的に動作には問題ないと思います。

必要なライブラリのインポート

まずはメール送信に必要なライブラリをインポートします。上記で説明したsmtplibとemailの2つです。

from smtplib import SMTP

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

from email.utils import formatdate

また、メール送信時にはMIME形式のデータを扱う必要がありますが、こちらも標準ライブラリで用意されています。

メール本文の作成処理

from email.mime.text import MIMEText
from email.utils import formatdate

まずはメール本文を作る部分を作っていきます。
MIME形式のデータを送信するため、emailライブラリにあるMIMETextを使用します。もう一つは時間を扱うためのformatdateも使用します。

コードをまとめて読みやすくするためにcreateMIMETextというMIMEText形式のオブジェクトを作成して返すための独自のメソッドを作成していきます。
引数にはメール送信元となるfrom、メールの宛先となるto、メッセージ本文のmessage、メール件名となるsubjectの4つをとります。

def createMIMEText(from, to, message, subject):
    """
    処理部分
    """

今回、createMIMETextメソッド内で使うMIMETextクラスに与える引数はmessageとなるメールの本文です。
第二引数にはplain、第三引数utf-8を指定します。文字コードとなる第三引数はデフォルトでは"us-ascii"になっています。

msg = MIMEText(message, "plain", "utf-8")

HTML形式のメールを送りたい場合は、第二引数はhtmlにします。

msg = MIMEText(message, "html", "utf-8")

本文以外の残りのsubject、from、toは以下のようにキーワード引数を用います。

  • 件名はSubject
  • 送信元メールアドレスはfrom
  • 宛先メールアドレスはTo
  • 日付はDate

日付の記述方法に関しては上記でインポートしたformatdate()を使用します。

msg = MIMEText(message, "html")
msg["Subject"] = subject
msg["From"] = from
msg["To"] = to
msg['Date'] = formatdate()

formatdateの中身を見てみると以下のように出力されます。

print("formatdate()", formatdate())
# formatdate() :  Sat, 05 Feb 2022 05:49:43 -0000

メソッドのコード全体

コード全体は以下のようになります。createMIMETextを呼び出すための呼び出し元となる処理も書いておきましょう。

def createMIMEText(from, to, message, subject):
    # MIMETextを作成
    msg = MIMEText(message, "html")
    # msg = MIMEText(message)
    # msg = MIMEText(message, "plain", 'utf-8')

    msg["Subject"] = subject
    msg["From"] = from
    msg["To"] = to
    msg['Date'] = formatdate()

    return msg

# メールの送り主
from_email = "source@fuga.com"

# メール送信先
to_email = "destination@hoge.com"

# メール件名とメール本文
subject = "メール件名"
message = "メール本文"

mime = createMIMEText(from_email, to_email, message, subject)
print("mime", mime)

createMIMETextの戻り値となるmimeの中身をprint文で出力してみると以下のようになるはずです。

mime Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Subject: =?utf-8?b?44Oh44O844Or5pys6aGM?=
From: source@fuga.com
To: destination@hoge.com
Date: Sat, 05 Feb 2022 06:01:01 -0000

44Oh44O844Or5pys5paH

メール送信の処理を作っていく

次にメール送信用の処理を作ります。
こちらでもメール送信のためのsend_emailという独自のメソッドを作成し、引数にはMIMETextオブジェクトのmsgを取ります。

def def send_email(msg):
    """
    処理部分
    """

基本的なメソッドの中身としては以下のようになります。
しかし、以下のこの処理ではSMTPサーバの認証処理が無い場合でしか動作しません。通常、SMTPサーバには認証処理を行わなければいけないからです。

from smtplib import SMTP

def send_email(msg):
    host = 'SMTPサーバのホスト名'
    port = 25

    # サーバを指定する
    server = SMTP(host, port)

    # メールを送信する
    server.send_message(msg)
    
    # 閉じる
    server.quit()

SMTPサーバの認証

メールを送信するためにもSMTPサーバの認証処理も書いていいます。

メールの内容が暗号化されていないSMTPSやSTARTTLSを使用しない場合は、以下の処理だけになります。単純にloginメソッドを呼び出すだけです。

# SMTPサーバのユーザ名とパスワード
account = ""
password = ""

host = 'SMTPサーバのホスト名'
port = 25

server = SMTP(host, port)
server.login(account, password)

STARTTLSに対応させる処理

暗号化の一つであるメールをSTARTTLSに対応させる場合はloginの前に、以下のようにstarttlsメソッドを呼び出します。

server = SMTP(host, port)
server.starttls()

STARTTLSが何かがわからない方は以下でわかりやすく解説しています。

https://medium-company.com/smtps-starttls-違い/

STARTTLSの詳細は以下がわかりやすいです。

https://sendgrid.kke.co.jp/blog/?p=12756

一般的にSTARTTLSは587番ポートを使用する場合が多いので、587をここでは指定しています。

# SMTPサーバのユーザ名とパスワード
account = ""
password = ""

host = 'SMTPサーバのホスト名'
port = 587

server = SMTP(host, port)
server.starttls()
server.login(account, password)

場合によってはサーバがSTARTTLSに対応していないことも考えられます。接続先のサーバがSTARTTLSに対応しているかを確かめるメソッドも用意されています。

以下のようなコードでサーバがSTARTTLSに対応している場合は、サーバにSTARTTLSで接続して暗号化処理を開始します。

if server.has_extn('STARTTLS'):
    server.starttls()
# SMTPサーバのユーザ名とパスワード
account = ""
password = ""

host = 'SMTPサーバのホスト名'
port = 587

server = SMTP(host, port)

if server.has_extn('STARTTLS'):
    # ehloは内部で勝手に実行してくれるが、あえで手動で実行したい場合は明示的にechoすることができる
    server.ehlo()
    server.starttls()
    server.ehlo()

server.login(account, password)

SSL/TLSに対応させる処理

STARTTLSだけでなく、SSL/TLSを使用したSMTPSのメール送信の方法も解説します。
SSL/TLS暗号化通信の場合は465番ポートを使用します。

通常のSMTPでは通信内容が暗号化されず平文のままメールを送信します。しかし、平文のままでは盗聴されるリスクもあるため、SSL/TLSを使い通信内容を暗号化したSMTPSがあります。

以下の処理はSMTPSを利用したメール送信の方法です。

import ssl
from smtplib import SMTP_SSL

host = 'SMTPサーバのホスト名'
host = "smtp.gmail.com"
port = 465

context = ssl.create_default_context()
server = SMTP_SSL(host, port, context=context)

メールヘッダの書き換え

emailライブラリにあるutilsを使用して、メールヘッダを書き換えることができます。

import email.utils

msg = MIMEText(message, "plain", "subject")
msg["Subject"] = subject
msg['To'] = email.utils.formataddr(('xxxxx', from_email))

上記の処理を行うと以下のようにメールヘッダが書き換えられます。

Subject: =?utf-8?b?44Oh44O844Or5pys6aGM?=
From: source@fuga.com
To: xxxxx <estination@hoge.com>

メールに添付ファイルを追加する

メールを送信する際には画像やpdfなどのファイルを添付することがあると思います。ファイル添付をするには以下のライブラリをインポートする必要があります。

emailライブラリのMIMEMultipartを使用します。

from email.mime.multipart import MIMEMultipart

ファイルを添付するコードは以下のようになります。

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# メール本文
message = ""

msg = MIMEMultipart()
msg['Subject'] = '件名を入力'
msg['From'] = from_mail
msg['To'] = to_mail
msg.attach(MIMEText(message, 'plain', 'utf-8'))

# 添付ファイルの設定
filename = '添付ファイル名'
path = '添付ファイルのPATH'
with open(path, 'r') as fp:
    attach_file = MIMEText(fp.read(), 'plain')
    attach_file.add_header(
        "Content-Disposition", 
        "attachment", 
        filename=filename
    )
    msg.attach(attach_file)

上記で作成したcreateMIMETextに添付ファイルを追加するコードをまとめてみます。createMIMETextに引数を追加して、添付ファイルの引数がある場合のみ添付ファイルを追加する処理を行うようにします。

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

def createMIMEText(from, to, message, subject, filepath=None, filename=""):
    # MIMETextを作成
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from
    msg['To'] = to
    msg.attach(MIMEText(message, 'plain', 'utf-8'))

    # 添付ファイルの設定
    if filepath:
        path = filepath
        with open(path, 'r') as fp:
            attach_file = MIMEText(fp.read(), 'plain')
            attach_file.add_header(
                "Content-Disposition", 
                "attachment", 
                filename=filename
            )
            msg.attach(attach_file)
    return msg

コード全体の完成図

コード全体は以下のようになります。
上記で解説した部分の処理をまとめるために2つほどメソッドを作成しました。

ここではSMTPS(SSL/TLS)を使用してのコードになります。

import ssl
from smtplib import SMTP, SMTP_SSL

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate

def createMailMessageMIME(from, to, message, subject, filepath=None, filename=""):
    # MIMETextを作成
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = from
    msg['To'] = to
    msg.attach(MIMEText(message, 'plain', 'utf-8'))

    # 添付ファイルの設定
    if filepath:
        path = filepath
        with open(path, 'r') as fp:
            attach_file = MIMEText(fp.read(), 'plain')
            attach_file.add_header(
                "Content-Disposition", 
                "attachment", 
                filename=filename
            )
            msg.attach(attach_file)
    return msg

def send_email(msg):
    account = "アカウント名"
    password = "パスワード"

    host = 'SMTPサーバのホスト名'
    port = 465

    # サーバを指定する
    # server = SMTP(host, port)
    context = ssl.create_default_context()
    server = SMTP_SSL(host, port, context=context)

    # ログイン処理
    server.login(account, password)

    # メールを送信する
    server.send_message(msg)
    
    # 閉じる
    server.quit()

# メールの送り主
from_email = "source@fuga.com"

# メール送信先
to_email = "estination@hoge.com"

subject = "メール件名"
message = "メール本文"

# MIME形式の作成
mime = createMailMessageMIME(from_email, to_email, message, subject)

# メールの送信
send_email(mime)

参考資料

https://mat0401.info/blog/how-to-use-smtplib/
https://code-graffiti.com/how-to-send-gmail-in-python/
https://www.python.ambitious-engineer.com/archives/2034
https://develop.blue/2020/03/python-use-mail/
https://zenn.dev/takahashi_m/articles/d0fb009398e92c562662
https://www.python.ambitious-engineer.com/about-us

Discussion

松嶋松松嶋松

参考にさせていただきました!ありがとうございます!
コード全体完成図のところ、最後のcreateMailMessageMIME関数名はcreateMIMETextが正しいかしら?