きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Pythonのjinja2でマルチパートメールのテンプレートを取得する

Pythonでマルチパートメールを送る方法をブログにしました。しかし、前回の記事はファイルからテンプレートを取得していないので、マルチパートメールを実質運用できません。今回はjinja2を使用してファイルからテンプレートを取得することで実運用できるようにします。

環境

  • Python
    • 3.12.3
  • jinja2
    • 3.1.3
  • MailHog
    • v1.0.1

事前条件

前回記事をベースに、jinja2でファイルのテンプレートを取得できるようにする。

対応

テンプレートを格納する

HTMLファイルとテキストファイルを配備します。

今回の例では次のディレクトリにファイルを配備します。

  • src/resources/templates/signin.html
  • src/resources/templates/signin.txt

また、今回のテンプレートにはnameという変数を用意しています。

# src/resources/templates/signin.txt
Welcome to our platform, {{ name }}!

テンプレート読み込む

jinja2を用いてテンプレートを読み込みます。今回の例ではsrc/resources/templatesにメールテンプレートを配備しているので、FileSystemLoaderにディレクトリを渡します。また、レンダリングする際にパラメータが不足した時にエラーとなるようにEnvironmentundefinedを定義します。発生したエラーは次のようなものが発生します。

jinja2.exceptions.UndefinedError: 'name' is undefined

次のコードを元に、HTMLメールとテキストメールのテンプレートを読み込むようにします。

from jinja2 import Template, Environment, FileSystemLoader, StrictUndefined


@classmethod
def get_templates(cls, path: str):
    file_loader = FileSystemLoader("src/resources/templates")
    env = Environment(loader=file_loader, undefined=StrictUndefined)
    template_text = env.get_template(f"{path}.txt")
    template_html = env.get_template(f"{path}.html")

    return template_text, template_html

ファイルレンダリング

読み込んだテンプレートに変数を埋め込んで、メール送信できる文字列に変換します。

@classmethod
def build_body(cls, path: str, params={}):
    text, html = cls.get_templates(path)
    return text.render(**params), html.render(**params)

メソッドの呼び出し

ファイル名、パラメータのキーと値を渡せばファイルからテンプレートを読めます。

body_text, body_html = Mailer.build_body("signin", params={"name": "NAME"})

前回の記事と合わせて次のようなメソッドになります。

@classmethod
def send(cls) -> None:
    sender = "no-reply@example.com"
    receiver = "1@example.com"
    subject = "Python SMTP Mail Subject"
    body_text, body_html = Mailer.build_body("signin", params={"name": "NAME"})

    msg = EmailMessage()
    msg.set_content(body_text)
    msg.add_alternative(body_html, subtype="html")

    msg["Subject"] = subject
    msg["From"] = sender
    msg["To"] = receiver
    try:
        with SMTP(host=cls.host, port=cls.port) as smtp:
            smtp.send_message(msg)
    except Exception as e:
        print(f"Failed to send email. Error {str(e)}")
        raise e

ソースコード

今回のテスト用に少々テストコードを変更しています。

終わりに

取り急ぎ本番でも運用できる状態になったと思います。他にもjinja2には良さそうなオプションがあるので色々と素振りしたいですね。

類似情報

Pythonでマルチパートメールを送る(smtplib)

Pythonでメールを送る方法を素振りしたかったのでメモします。SMTPを使用し、マルチパートメールを送るところまでを目標とします。

なお、実務ではSESを使用したり、SendGridを使用することが多いと思うので、あくまでローカルでメールの動作確認をする程度しか使用できません。

環境

  • Python
    • 3.12.2
  • MailHog
    • v1.0.1

事前条件

MailHogを利用して、SMTPを受け取るメールテストサーバを立てます。

  mail:
    image: mailhog/mailhog:latest
    ports:
      - 8025:8025 # WebUI
      - 1025:1025 # SMTP

対応

Pythonのビルトインライブラリのsmtplibを使用します。

基本的にはライブラリのメソッドしか使用していないので、次のコードを参考にしてください。

マルチパートメールのためにEmailMessageset_contentadd_alternativeくらいしか特色はないです。

from typing import Final
from email.message import EmailMessage
from smtplib import SMTP


class Mailer:
    class Local:
        host: Final[str] = "localhost"
        port: Final[int] = 1025

        @classmethod
        def send(cls):
            body_text = "Hello, this is a test email sent by Python smtplib."
            body_html = (
                "<html>Hello, this is a test email sent by Python smtplib.</html>"
            )

            msg = EmailMessage()
            msg.set_content(body_text)
            msg.add_alternative(body_html, subtype="html")

            msg["Subject"] = "Python SMTP Mail Subject"
            msg["From"] = "no-reply@example.com"
            msg["To"] = "1@example.com"
            try:
                with SMTP(host=cls.host, port=cls.port) as smtp:
                    smtp.send_message(msg)
            except Exception as e:
                print(f"Failed to send email. Error {str(e)}")
                raise e

ソースコード

テスト時にメールが飛ばないように、モックにしています。

終わりに

大きく解説するような箇所は無いのですが、マルチパートメールがあるのでブログにしました。

ビルトインライブラリがあるから、悩まなくていいのもいいですね。

類似情報

GitHubでDraftのPRを除外してOpenだけにしたい

GitHubでレビューを依頼されたときに、レビュー依頼が多すぎてOpen状態のPullRequestだけを確認したい時のメモ。

環境

  • GitHub
    • 2024/04/20

対応

次のどちらかをフィルタに設定してください。

draft:false

-is:draft

ちなみに、JetBrainsのPull Requestsでも有効です。

終わりに

作業開始したらDraftでレビュー依頼を先に出すルールで仕事しているので、本当にレビューしたらよい対象をみつけるのがたいへんでした。

フィルタに-を付けたらNotの意味になることも分かったので、今後は地味に作業改善ができます。

参考情報