PowerShell と Az.Communication モジュール (Azure Communication Services) でカンタンメール送信

# はじめに

PowerShell で、Azure Communication Services を使ってメール送信する方法を模索していました。

https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/email/email-overview?WT.mc_id=AZ-MVP-5003113

やっと見つけた先人の記事。この記事でご紹介いただいているように、```Invoke-RestMethod``` コマンドレットで REST API にリクエストする方法しかないのか...と思っていました。ちょっとめんどくさいです...:worried:

https://luke.geek.nz/azure/using-communication-services-and-powershell-to-send-emails/

https://learn.microsoft.com/en-us/rest/api/communication/dataplane/email/send?WT.mc_id=AZ-MVP-5003113

:::note warn
**注意**
こちらの記事のように REST API でメール送信する場合の注意点として、メールの件名や本文などに日本語が含まれる場合は、```Invoke-RestMethod``` コマンドレットの ```Body``` パラメーターには、**UTF-8 エンコードされたバイト配列を指定** しましょう。これやらないと文字化けしたメールが届きますよ。

```diff_powershell
# Convert the PowerShell object to JSON
- $body = $apiResponse | ConvertTo-Json -Depth 10
+ $body = [Text.Encoding]::UTF8.GetBytes(($apiResponse | ConvertTo-Json -Depth 10))
```
:::

が、筆者が求めていた Az モジュールが公開されていました！この「Az.Communication」モジュールがあれば、よりカンタンに開発できますね♪:laughing:

https://learn.microsoft.com/en-us/powershell/module/az.communication/?WT.mc_id=AZ-MVP-5003113

この投稿では、メール送信するための環境の構築 (メール送信できる仕組みは Azure Automation を利用) から PowerShell スクリプトの実装まで紹介しようと思います。

※執筆時点では、「Az.Communication」モジュールはまだプレビュー段階であるため、今後仕様変更があるかもしれません。ご留意ください。


# メール送信するためのセットアップ

メール送信ができるようにするための環境を用意します。


## Azure Email Communication Services リソースを作成

まず、メール通信サービス (Email Communication Services) リソースを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/64cf8e2c-aa6c-76fb-4f9e-8f3c5ebfdfd2.png)


### メールドメインを追加

メールドメインを追加します。
メール通信サービスリソースを開きます。 今回は、Azure サブドメインを使おうと思うので、下図の赤枠にある [1 クリックで追加] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/fb581006-3135-f533-a788-e7f313f25d69.png)

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/email/add-azure-managed-domains?WT.mc_id=AZ-MVP-5003113

※カスタムドメインを追加する場合は、以下のドキュメントをご参照ください。

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/email/add-custom-verified-domains?WT.mc_id=AZ-MVP-5003113

メールドメインのプロビジョニングが完了すると、(今回は)「*azurecomm.net*」のサブドメインが追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/3b58ae82-f7fc-a1b4-fdeb-9e54ef9a28fd.png)

また、メールドメインの [MailFrom address] で、メール送信者アドレスが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/284e8e54-e6b1-e69c-4e29-e5f2cb9d25f8.png)


## Azure Communication Services リソースを作成

次に、Communication Services リソースを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/3cedad5e-40c6-f516-9d2f-a7d0ace6f0fe.png)


### メールドメインを接続

メール送信ができるようにするため、下図の赤枠にある [ドメインを接続する] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/d6883959-a0e9-2d6a-5e3c-1ad3385fd3ff.png)

赤枠の部分で、メール通信サービスリソースと先ほど作成したメールドメインを選択し、[接続] をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0a3de87d-d2a2-2241-a7de-0b314b51e074.png)

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/email/connect-email-communication-resource?WT.mc_id=AZ-MVP-5003113

メールドメインが接続できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/386c2d2e-cbb5-4c0e-5be1-76b4a9ac8617.png)

これで、メール送信ができるようになりました。

なお、Azure ポータルからメール送信を行うことができます (Try Email)。この機能を使って事前に動作確認することができますね。この操作方法については、以下のドキュメントをご参照ください。

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/email/send-email?WT.mc_id=AZ-MVP-5003113&pivots=platform-azportal


# 実行環境のセットアップ

今回は、Automation Runbook からメールを送信しようと思います。


## マネージド ID にロールを割り当てる

マネージド ID で Azure に接続します。今回は、システム割り当て済みマネージド ID を使い、以下の Azure ロールを割り当てます。

- Communication Services リソースへの[共同作成者](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles/privileged?WT.mc_id=AZ-MVP-5003113#contributor)ロール
- メール通信サービス ドメイン リソースへの[閲覧者](https://learn.microsoft.com/ja-jp/azure/role-based-access-control/built-in-roles/general?WT.mc_id=AZ-MVP-5003113#reader)ロール

※後述する [Runbook](#runbook-%E3%82%92%E5%AE%9F%E8%A3%85) を動作確認した結果、これらのロールを割り当てることで正常に動作しました。なお、これは執筆時点のもので、動作については今後変更されるかもしれません。ご留意ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/7e2abe56-f94e-c06e-54dd-cba85de5b7f0.png)

割り当てる Azure ロールについては、以下の記事もご参照ください。

https://azure.github.io/jpazpaas/2024/01/19/Azure-Role-for-using-Azure-Email-Services.html


## ランタイム環境を作成

別途パッケージを追加するため、ランタイム環境を用意します。

https://learn.microsoft.com/ja-jp/azure/automation/manage-runtime-environment?WT.mc_id=AZ-MVP-5003113

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/73e11756-9343-26c1-124e-46d9d1a7e437.png)

「パッケージ」タブで、ギャラリーから必要なモジュールを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/0a87c6d6-75f3-d394-2aaa-6011440c4fb8.png)

「[Az.Communication](https://learn.microsoft.com/ja-jp/powershell/module/az.communication/?WT.mc_id=AZ-MVP-5003113)」だけ追加した場合だと、Runbook 実行時に以下のように「[Az.Accounts](https://learn.microsoft.com/ja-jp/powershell/module/az.accounts/?WT.mc_id=AZ-MVP-5003113) が必要です」というメッセージが出力されるので、併せて追加します。

> This module requires Az.Accounts version 3.0.5. An earlier version of Az.Accounts is imported in the current PowerShell session. Please open a new session before importing this module. This error could indicate that multiple incompatible versions of the Azure PowerShell cmdlets are installed on your system. Please see https://aka.ms/azps-version-error for troubleshooting information.


## Runbook を作成

Runbook は、前述の「[ランタイム環境](#%E3%83%A9%E3%83%B3%E3%82%BF%E3%82%A4%E3%83%A0%E7%92%B0%E5%A2%83%E3%82%92%E4%BD%9C%E6%88%90)」上で実行させるので、それを選択して作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/e4cbb3e9-9ca2-0391-3760-13f472074e70.png)


# Runbook を実装


## エンドポイントを取得

まず、Communication Services リソースのエンドポイントを用意します。

エンドポイントは、```Get-AzCommunicationService``` の戻り値の ```HostName``` プロパティ (FQDN) を基に ```URI``` を作成します。

```powershell:Send-EMail (Get-EmailEndpoint)
<#
.DESCRIPTION
Azure Communication Services リソースのエンドポイントを取得します
.PARAMETER ResourceGroupName
リソースグループ名
.PARAMETER CommunicationServiceName
Communication Services リソース名
.OUTPUTS
エンドポイント
#>
function Get-EmailEndpoint {
    param (
        [Parameter(Mandatory=$true)]
        [string]$ResourceGroupName,
        [Parameter(Mandatory=$true)]
        [string]$CommunicationServiceName
    )

    # エンドポイント
    $endpoint = $null

    # Azure Communication Services リソースを取得
    $commService = Get-AzCommunicationService `
        -ResourceGroupName $ResourceGroupName `
        -Name $CommunicationServiceName

    # Azure Communication Services リソースが取得できた場合
    if ($null -ne $commService) {
        # エンドポイントを作成
        $endpoint = "https://$($commService.HostName)"
    }

    return $endpoint
}
```

https://learn.microsoft.com/en-us/powershell/module/az.communication/get-azcommunicationservice?WT.mc_id=AZ-MVP-5003113


## メール送信者アドレスを取得

次に、メール送信者アドレスを用意します。

メール送信者アドレスは、```Get-AzEmailServiceDomain``` の戻り値の ```MailFromSenderDomain``` プロパティ (メール送信者ドメイン) と、```Get-AzEmailServiceSenderUsername``` の戻り値の ```Username``` プロパティ (送信者ユーザー名) を取得して、メールアドレスを作成します。

```powershell:Send-EMail (Get-EmailSenderAddress)
<#
.DESCRIPTION
メール送信者アドレスを取得します
.PARAMETER ResourceGroupName
リソースグループ名
.PARAMETER EmailServiceName
Email Services リソース名
.OUTPUTS
メール送信者アドレス
#>
function Get-EmailSenderAddress {
    param (
        [Parameter(Mandatory=$true)]
        [string]$ResourceGroupName,
        [Parameter(Mandatory=$true)]
        [string]$EmailServiceName
    )

    # メール送信者アドレス
    $emailSenderAddress = $null

    # Email Services ドメインを取得
    $emailDomains = Get-AzEmailServiceDomain `
        -ResourceGroupName $ResourceGroupName `
        -EmailServiceName $EmailServiceName

    # Email Services ドメインが取得できた場合
    if ($emailDomains.Count -gt 0) {
        # Email Services 送信者を取得
        $emailSenders = Get-AzEmailServiceSenderUsername `
            -ResourceGroupName $ResourceGroupName `
            -EmailServiceName $EmailServiceName `
            -DomainName $emailDomains[0].Name

        # Email Services 送信者が取得できた場合
        if ($emailSenders.Count -gt 0) {
            # メール送信者アドレスを作成
            $emailSenderAddress = `
                "$($emailSenders[0].Username)@$($emailDomains[0].MailFromSenderDomain)"
        }
    }

    return $emailSenderAddress
}
```

https://learn.microsoft.com/en-us/powershell/module/az.communication/get-azemailservicedomain?WT.mc_id=AZ-MVP-5003113

https://learn.microsoft.com/en-us/powershell/module/az.communication/get-azemailservicesenderusername?WT.mc_id=AZ-MVP-5003113


## メールを送信

メインロジックは、以下の順番で処理を実行します。

1. マネージド ID を使って Azure に接続する
1. Communication Services リソースのエンドポイント URI を取得 (用意) する
1. メール送信者アドレスを取得 (用意) する
1. メッセージを編集する
    - メール送信者
    - メール受信者
    - 件名
    - 本文 (HTML 形式 or テキスト形式)
    - ユーザー エンゲージメント トラッキングを無効にするかどうか
    - など
1. メールを送信する (キューに入れる)

```powershell:Send-EMail
# モジュールを読み込む
Import-Module Az.Accounts
Import-Module Az.Communication

# メール件名
$Subject = 'テストメール'
# メール本文
$Body = '<html><body><p>Hello World by メール.</p></body></html>'
# メール受信者 ※複数個の場合はカンマ区切りで指定
$Recipient = 'example@contoso.com'

# リソースグループ名
$ResourceGroupName = 'rg-demo202412'
# 通信サービスリソース名
$CommunicationServiceName = 'comm-demo202412'
# メール送信サービスリソース名
$EmailServiceName = 'email-comm-demo202412'

try {
    # Runbook 内で AzContext を継承しない
    Disable-AzContextAutosave -Scope Process

    # システムに割り当てられたマネージド ID で Azure に接続
    $azContext = (Connect-AzAccount -Identity).context 

    # コンテキストの設定と保存
    $azContext = Set-AzContext `
        -Subscription $azContext.Subscription `
        -DefaultProfile $azContext

    # エンドポイントを取得
    $endpoint = Get-EmailEndpoint `
        -ResourceGroupName $ResourceGroupName `
        -CommunicationServiceName $CommunicationServiceName
    Write-Output "Endpoint: $endpoint"

    # メール送信者アドレスを取得
    $senderAddress = Get-EmailSenderAddress `
        -ResourceGroupName $ResourceGroupName `
        -EmailServiceName $EmailServiceName
    Write-Output "SenderAddress: $senderAddress"

    # メール受信者を編集
    $emailRecipientTo = @()
    foreach ($recipient in $Recipient.Split(',')) {
        $recipientItem = @{
            Address = $recipient
            DisplayName = $recipient
        }
        $emailRecipientTo += $recipientItem
    }

    # メッセージを編集
    $emailMessage = @{
        SenderAddress = $senderAddress
        RecipientTo = @($emailRecipientTo)
        ContentSubject = $Subject
        ContentHtml = $Body
        UserEngagementTrackingDisabled = $true
    }
    Write-Output "Message: $($emailMessage | ConvertTo-Json)"

    # メールを送信
    Send-AzEmailServicedataEmail -Endpoint $endpoint -Message $emailMessage

    Write-Output "Done"
}
catch {
    Write-Error "Failed: $_`n$($_.ScriptStackTrace)"
}
```

https://learn.microsoft.com/en-us/powershell/module/az.communication/send-azemailservicedataemail?WT.mc_id=AZ-MVP-5003113

この Runbook を実行すると、下図のようなメールが届きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/230264/efad464e-5fb8-a8de-9fd9-8d606b7b91aa.png)

複数の宛先にメールを送信したい場合は、```Address``` と ```DisplayName``` プロパティを持つオブジェクトの配列で指定します。この PowerShell スクリプトでは、メール受信者をカンマ区切りで指定した文字列変数  (```$Recipient```) からオブジェクト配列変数 (```$emailRecipientTo```) に変換して ```RecipientTo``` プロパティに値をセットします。
※CC (```RecipientCc``` プロパティ)、BCC (```RecipientBcc``` プロパティ)、メールの返信先 (```ReplyTo``` プロパティ) も同様

```powershell:(例)
# メール受信者 ※複数個の場合はカンマ区切りで指定
$Recipient = 'user1@contoso.com,user2@contoso.com,user3@contoso.com'

### (中略) ###

# メール受信者を編集
$emailRecipientTo = @()
foreach ($recipient in $Recipient.Split(',')) {
    $recipientItem = @{
        Address = $recipient
        DisplayName = $recipient
    }
    $emailRecipientTo += $recipientItem
}
```

また、この PowerShell スクリプトでは、メール本文は HTML 形式で ```ContentHtml``` プロパティに値をセットしていますが、テキスト形式の場合は ```ContentPlainText``` プロパティに値をセットします。

その他に、ファイルを添付したり (```Attachment``` プロパティ)、受信者が届いたメールを返信した時の送付先 (メールの返信先) のメールアドレス (```ReplyTo``` プロパティ) を指定したりすることもできます。

(ここで紹介していないものも含めて) 詳しくは、以下のドキュメントをご参照ください。

https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.powershell.cmdlets.emailservicedata.models.iemailmessage?WT.mc_id=AZ-MVP-5003113


# まとめ

PowerShell でも ```Az.Communication``` モジュールを使えば、カンタンにメールを送信する仕組みを作ることができました。このモジュールがまだプレビュー段階だったり、マネージド ID に割り当てる Azure ロールがちょっと気になりますが、みなさまもぜひお試しいただければと思います。