Azure Communication Service Email を使ってメール通知を送ってみた

# はじめに

Microsoft Azure Tech Advent Calendar 2022 16日目の記事です。

日本マイクロソフトの Cloud Solution Architect というロールで主に AI/ML 周りの支援をしている伊藤と言います。

機械学習モデルを使用した異常検知システムから、何かしら API なり SDK (できれば Python) を使ってメール通知を送りたいと思って色々調べていたところ、Azure Communication Service Email が使えそうな雰囲気だったので使ってみました。

なお、Azure Communication Service Email は2022年12月現在パブリックプレビューの位置づけです。もし使用する場合はその前提でご利用ください。

# リソース準備

必要なリソースは

- Azure Communication Service
- Azure Communication Service Email

の2つです。前者がエンドポイントの役割を果たし、後者が実際にメールを送るためのドメイン周りの設定を担っているように見えます。

## Azure Communication Service Email

### リソース作成

日本語では「メール通信サービス」となっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/30e60d5a-7a09-45b3-3666-5ef8c3861e8b.png)

Data location は現在 United States しか選べないようなのでそうします。

### ドメイン設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/d0a839aa-ba09-bfcf-884d-35e7c147b387.png)

Azure が用意したサブドメインか、自前のドメインを設定できるようです。

「1クリックで追加」を押すとサブドメインの展開が始まります。数分で完了し、「DoNotReply@9a4e06c5-c3c6-42b3-b197-161b9b339e60.azurecomm.net」という自動生成感が非常に強いアドレスが割り当てられました。

カスタムドメインを追加する場合はボタンクリック後に追加したいドメインを入力すると、ドメインの所有確認のための DNS レコードの情報が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/381b242b-4c60-2c7e-6533-7ee268d86332.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/e07fba8c-583c-8c82-a171-7b80ec59dbb4.png)

TXT レコードによる所有確認後、SPF、DKIM、DKIM2 の3つについてもレコード追加を行うと使用可能になります。DNS のレコードを Azure 側で取得できるようになるまでそれなりに時間がかかります。僕の環境ではおおよそ1時間程度で「検証済み」に変化しました。

## Azure Communication Service

### リソース作成

日本語では「通信サービス」という名前になっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/5437df3e-f34d-ae1e-48c7-62e02722d6f6.png)

データの場所くらいしか固有の設定が無かったのですが、Email の方が United States しか選べなかったのでそれに合わせてこちらも United States にしました。

### Email リソースの紐づけ

左メニューの「ドメイン」から Email リソースを紐づけるメニューに飛ぶことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/366c6275-c4fc-fd6a-ef6e-58cb3427aeef.png)

Email リソースおよび検証済みのドメインを選択して接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/78096163-93c2-33ba-8191-033f9b2ffdec.png)

### 接続文字列取得

Azure Communication Service リソース左メニューの「キー」からエンドポイントと接続文字列を取得することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/cbe19447-355e-8df4-394f-d7892d9564cf.png)

# Python SDK によるメール送信

## 使用するパッケージ

Python 3.10.8 環境に以下パッケージをインストールしました。

- azure-communication-email==1.0.0b1
- Pillow==9.3.0

## コード

```Python
from azure.communication.email import (
    EmailClient,
    EmailRecipients,
    EmailContent,
    EmailAddress,
    EmailMessage,
    EmailAttachment,
)
import base64

COMMUNICATION_SERVICE_CONNECTION_STRING = "<connection_string>"

def send_email(to_email_address: str, name: str, subject: str, body: str, attachment_name=None, attachment=None) -> None:
    # https://azure.github.io/azure-sdk-for-python/communication.html#azure-communication-email
    client = EmailClient.from_connection_string(COMMUNICATION_SERVICE_CONNECTION_STRING)

    content = EmailContent(
        subject=subject,
        plain_text=body,
        html=f"<html>{body}</html>",
    )
    address = EmailAddress(email=to_email_address, display_name=name)

    if attachment is None:
        message = EmailMessage(
            sender="DoNotReply@9a4e06c5-c3c6-42b3-b197-161b9b339e60.azurecomm.net",
            content=content,
            recipients=EmailRecipients(to=[address]),
        )
    else:
        file_bytes_b64 = base64.b64encode(attachment)
        attachment = EmailAttachment(
            name=attachment_name,
            attachment_type="png",
            content_bytes_base64=file_bytes_b64.decode(),
        )
        message = EmailMessage(
            sender="DoNotReply@9a4e06c5-c3c6-42b3-b197-161b9b339e60.azurecomm.net",
            content=content,
            recipients=EmailRecipients(to=[address]),
            attachments=[attachment],
        )

    response = client.send(message)
```

SDK を使用してこんな感じで実装しました。テストのため接続文字列をそのままハードコードしていますが、本番環境では絶対に真似しないでください。

```sender```は Email リソースからドメインをクリックするとドメインを表現するサブリソースに飛べるのですが、そちらに記載があります。

```EmailAdress```、```EmailContent```、```EmailAttachment```、```EmailMessage```などのメールを構成する各種要素を表現するクラスを使ってメールを組み上げ、メールを表現するインスタンスを```EmailClient```の```send```関数に渡すことで送信するという流れになっています。

テストメールを送るとちゃんとメールが来ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/b03abf2c-41a8-5868-1d5e-4d374514cdb7.png)

続いて画像を添付してみます。

バイナリとして画像を読み込むコードは以下の通りです。

```Python
import io
from PIL import Image

img = Image.open('tired_engineer.png')
img_bin = io.BytesIO()
img.save(img_bin, format="PNG")
```

```img_bin.getvalue()```でバイナリを取り出せるので、この値を```send_email```関数の```attachment```に渡し送信してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/a4b7c507-4c07-1ee0-1b9e-23966db14101.png)

週末疲れ果てたタイミングで Stable Diffusion に「tired engineer」とプロンプトを渡したら出力された趣ある表情の被生成エンジニアの画像がちゃんと来ていますね。

無事成功です。

# 制限

プレビューであることに加え、Azure Communication Service Email には以下の制限があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359618/140c0fca-1713-d7b3-b2d0-74465445d345.png)

https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/service-limits#email

1通/秒とかで送ると引っかかりますね。

# おわりに

Azure Communication Service Email の Python SDK を使用してメールを送信することができました。

先輩から「Azure からメールを送るときは色々注意が必要」と聞いていたのですが、パブリックプレビューの新サービスにより先輩から聞いていた「色々」を回避して、ヌルっとメール通知を送ることができて大満足です。