Amplifyで作成したLambdaにロールを付与してSESでメール送信する

前回の続き。

LambdaからSESによるメール送信を行う方法として、前回はIAMを作成し、アクセスキーを使ったが、今回はLambdaにロールを付与してみる。

環境

前回と同様。

実装

以下のissueに記載の通り、Lambda関数を作成し、生成されたCloudFormation定義を編集して、ロールを付与できる。

github.com

SESのドメインの検証は省略。

Lambdaの作成

amplify add function で、メール送信用のLambdaを作成する。関数名や言語は前回と同じものを指定。

記事作成時点で、コマンド実行時のオプション等で任意のロール付与は行えないため、advanced settingsでは何も設定しない。

また、前回は Select the categories you want this function to have access toAPIを選択したが、不要だったため除外している。

amplify add function
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: sendMail
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration

? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource sendMail locally.

Lambdaへのロール付与

生成された amplify/backend/function/<関数名>/<関数名>-cloudformation-template.json がCloudFormation定義ファイル。

Resources > lambdaexecutionpolicy > Properties > PolicyDocument > Statement に追加すればいい。

今回の作成方法の場合、 amplify/backend/function/sendMail/sendMail-cloudformation-template.json が対象。

生成段階で、ログ用にリソース指定された定義が存在するため、それとは別に "Resource": "*" で追加する。

// 前略
"Statement": [
// ここから追加
  {
    "Effect": "Allow",
    "Action": [
      "ses:SendEmail",
      "ses:SendRawEmail"
    ],
    "Resource": "*"
  },
// ここまで追加
// 以下、もともと存在していたログ用設定
  {
    "Effect": "Allow",
    "Action": [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ],
    "Resource": {
      "Fn::Sub": [
        "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
        {
          "region": {
            "Ref": "AWS::Region"
          },
          "account": {
            "Ref": "AWS::AccountId"
          },
          "lambda": {
            "Ref": "LambdaFunction"
          }
        }
      ]
    }
  }
]
// 後略

Lambdaの実装

今回はSystems Managerが不要なので、SESのパッケージだけインストールする。

cd amplify/backend/function/sendMail/src
yarn add @aws-sdk/client-ses

Lambdaの内容も前回と変わらず、 amplify/backend/function/<関数名>/src/index.js だが、実装はシークレットが不要になったのでシンプルになった。

// ファイル先頭の Amplify Params コメントは省略

const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses')

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
exports.handler = async event => {
  console.log(JSON.stringify(event))

  const sesClient = new SESClient({
    region: 'ap-northeast-1',
  })

  const params = event.arguments
  const sendEmailCommandInput = {
    Source: params.from,
    Destination: {
      ToAddresses: params.to,
      CcAddresses: params.cc,
      BccAddresses: params.bcc,
    },
    Message: {
      Subject: { Data: params.subject },
      Body: {
        Text: {
          Data: params.body,
        },
      },
    },
  }

  const sendEmailCommand = new SendEmailCommand(sendEmailCommandInput)
  const sendEmailCommandOutput = await sesClient.send(sendEmailCommand)

  return {
    statusCode: 200,
    body: sendEmailCommandOutput.MessageId,
  }
}

自分の設定が悪いのか、この方法だと amplify mock function <関数名> でのテストが失敗する。

amplify function pushAWS側に反映し、AWS ConsoleのLambdaのテストから実行できることを確認。

GraphQL定義およびコードからの呼び出し

前回と同様のため省略。

振り返り

Amplify CLIから設定できないAWSのサービスも、今回の方法を応用すれば、Lambda経由でどうとでもなりそう。

一方で、Amplifyの仕組みの外となるので、使いすぎるのは管理対象が増えるなど、デメリットにもなりそう。使いどころは考える必要があると感じた。