Azure FunctionsでAzure Communication Service Email サービスを使う

# はじめに

Azure Functions から Eメールを送る方法としては、[SendGrid アウトプットバインディング](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-sendgrid?pivots=programming-language-csharp&tabs=in-process%2Cfunctionsv2
)がありますが、この記事では、[2023年4月に GA](https://techcommunity.microsoft.com/t5/azure-communication-services/simpler-faster-azure-communication-services-email-now-generally/ba-p/3788541)された Azure Communication Service の Email サービスを使用して Azure Functions から Email を送信する方法について記載します。

# Azure Functions で Email Service を使う

## Functions 内でSDKを利用する

基本的には各言語で用意されている SDK を用いることで Email 送信自体は簡単にできる。

https://learn.microsoft.com/en-us/azure/communication-services/concepts/sdk-options#languages-and-publishing-locations

Azure Functions 上で用いる注意点としては Singleton にするなど SNAT ポート枯渇が発生しないようにする工夫が必要である。

https://learn.microsoft.com/ja-jp/azure/azure-functions/manage-connections?tabs=csharp

.NET だと以下のような感じで送信可能

```c#
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Azure;
using Azure.Communication.Email;

namespace FunctionApp1
{
    public static class Function1
    {

        private static EmailClient emailClient = new EmailClient(System.Environment.GetEnvironmentVariable("AzureWebJobsACSEmailConnecionString"));

        [FunctionName("Function4")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var emailContent = new EmailContent("This is the subject")
            {
                PlainText = "This is the body",
                Html = "<html><body>This is the html body</body></html>"
            };
            var emailMessage = new EmailMessage(
                senderAddress: System.Environment.GetEnvironmentVariable("fromAddress"),
                recipientAddress: System.Environment.GetEnvironmentVariable("toAddress"),
                content: emailContent);

            try
            {
                EmailSendOperation emailSendOperation = await emailClient.SendAsync(WaitUntil.Completed, emailMessage, default(CancellationToken));
                log.LogInformation($"Email operation id = {emailSendOperation.Id}, Status = {emailSendOperation.Value.Status}");
            }
            catch (RequestFailedException ex)
            {
                /// OperationID is contained in the exception message and can be used for troubleshooting purposes
                log.LogError($"Email send operation failed with error code: {ex.ErrorCode}, message: {ex.Message}");
            }

            return new OkObjectResult("done");
        }
    }
}

```

## カスタムバインディングにする

[SendGrid](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-twilio?pivots=programming-language-csharp&tabs=in-process%2Cfunctionsv2) や [Twillio](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-twilio?pivots=programming-language-csharp&tabs=in-process%2Cfunctionsv2)のようにアウトプットバインディングを利用できると便利そうなので作ってみた。（Nuget packageとしてのリリースは未実施）

https://github.com/georgeOsdDev/ACSEmailOutputBinding

`ACSEmail`バインディングを利用することで、SDK クライアントの管理をアプリケーションコード側で行う必要がなくなって便利。もちろん .NET 以外の言語で利用することも可能。

```c#
[FunctionName("Function1")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
    [ACSEmail] out ACSEmailContext mail,
    ILogger log)
{

    string address1 = Environment.GetEnvironmentVariable("testAddress1");
    string address2 = Environment.GetEnvironmentVariable("testAddress2");
    string address3 = Environment.GetEnvironmentVariable("testAddress3");
    string address4 = Environment.GetEnvironmentVariable("testAddress4");

    mail = new ACSEmailContext();
    mail.RecipientToAddresses = new List<string>() { address1, address2 };
    mail.RecipientCCAddresses = new List<string>() { address3 };
    mail.RecipientBCCAddresses = new List<string>() { address4 };
    mail.Subject = "Test acs email from function";
    mail.PlainTextContent = "Hello world";
    mail.HtmlContent = "<html><body>This is the html body</body></html>";

    mail.AttachmentFilePath = $"{Directory.GetCurrentDirectory()}\\host.json";
    mail.AttachmentName = "host.json";
    mail.AttachmentContentType = "application/json";
    return (ActionResult) new OkObjectResult("OK");
}
```

### 参考記事

まずはじめに読んだ

https://qiita.com/TsuyoshiUshio@github/items/345887a5fa5d59e7444a

公式ドキュメント(正直これだけだとかなり辛い)

https://github.com/Azure/azure-webjobs-sdk/wiki/Creating-custom-input-and-output-bindings

.NETビギナーなので、[SendGrid 拡張](https://github.com/Azure/azure-webjobs-sdk-extensions/tree/dev/src/WebJobs.Extensions.SendGrid)を~~パクっ~~写経してACSEmailクライアントに置き換えました。