【AWS Lambda使い方ガイド】NodemailerのAWS SESクライアントを使ってLambdaからEmailを送信してみる


※ 当ページには【広告/PR】を含む場合があります。
2024/10/26
【AWS Lambda使い方ガイド】AWS CLIから関数URLを設定してLambdaをAPIとして公開する
蛸壺の技術ブログ|NodemailerのAWS SESクライアントを使ってLambdaからEmailを送信してみる

『Nodemailer』を使えば、比較的簡単にnodejsアプリケーションから適当なSMTPサーバーを介してEmailを送信させることができます。

Nodemailer

ここ最近、nodemailerのEmail送信クライアントに
「AWS SES」が設定できることを知ったので、LambdaからEmail送信できないか試した際の個人的な模索の記録です。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

クライアント側からSESでメール送信

nodemailerのデフォルトSMTPとして良く紹介があるのはgmailアカウントでずが、『AWS SES』もメーラーとして選択することができます。

参考|Nodemailer > Other transports

ローカルの環境変数にAWSのアカウント情報が
.envに記述され、きちんと適用されていることが前提で、

            
            AWS_ACCESS_KEY_ID="....";
AWS_SECRET_ACCESS_KEY="....";
        
以下のように、nodemailerクライアントを実装することで、ご自分の保有するSESメーラーアカウントから、メールを送信できる機能があります。

            
            const nodemailer = require("nodemailer");
const aws = require("@aws-sdk/client-ses");

const ses = new aws.SES({
    apiVersion: "2010-12-01",
    region: "us-east-1",
});

const transporter = nodemailer.createTransport({
    SES: { ses, aws },
});
        
ローカル上のアプリケーションで動かすならこれでEメール送信することも可能ですが、サーバーサイドで動かすとなるともう少し手を加える必要があります。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

Lambdaハンドラにnodemailer-SESクライアントを組み込む

Lambdaのハンドラ関数で使うnodemailer/SESのパートの仕込みとしては以下のようなものになります。

            
            import nodemailer from 'nodemailer';
import * as aws from "@aws-sdk/client-ses";

const ses = new aws.SES({
    apiVersion: "2010-12-01",
    region: "us-east-1", //👈自身のSESの設定済みリージョンであることを確認
});

export function sendEmail() {
    //👇送信先のアドレス
    const email = 'okurisaki@oaite.jp';

    //👇送信する内容
    const mail = {
        from: 'hogehoge@piyopiyo.com',
        to: email,
        subject: 'SESからのメールです',
        text: 'こんにちは!',
        html: `<p>こんにちは!</p>`,
    };

    try {
        const transport = nodemailer.createTransport({
            SES: { ses, aws },
        });

        const result = await transport.sendMail(mail);

        return JSON.stringfy(result);
    } catch (err) {
        return JSON.stringfy(err);
    }
}
        
これでLambdaハンドラのどこかでsendEmailを呼び出すとEmailが送信するための実行ロールが必要となります。

Lambdaに直接AWSクレデンシャル情報を環境変数としてセットできない

まずはうまく動かない例から紹介します。

『aws-sdk』モジュールを正しく使うためには、AWSのアカウント情報が設定されている必要があります。

ローカル環境だと
.envでアカウント情報が設定されていることで、プロセスに実行権限が与えられてSESクライアントがメールを送信できるようになっています。

            
            AWS_ACCESS_KEY_ID="....";
AWS_SECRET_ACCESS_KEY="....";
        
では、Lamdaに直接これらのクレデンシャル情報を環境変数として指定して使ってみるとうまく行くのでは無いかと考えなしに設定してみます。

ダッシュボードからでも設定は出来ますが、今回は以下のようなCDKコードで設定するやり方で説明しますと、

            
            import {
    Stack,
    StackProps,
    aws_lambda,
    //...中略
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkTacokinShopStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        //...いろいろ中略

        const lambda = new aws_lambda.Function(this, 'Server', {
            runtime: aws_lambda.Runtime.NODEJS_20_X,
            code: aws_lambda.Code.fromAsset(join(__dirname, '../../build/lambda')),
            handler: 'index.handler',
            architecture: aws_lambda.Architecture.ARM_64,
            memorySize: 128,
            timeout: Duration.seconds(30),
            environment: {
                //☆👇クレデンシャル変数は直接Lambdaへ注入できない!
                "AWS_ACCESS_KEY_ID": process.env.AWS_ACCESS_KEY_ID,
                "AWS_SECRET_ACCESS_KEY": process.env.AWS_SECRET_ACCESS_KEY,
            }
        }).addFunctionUrl({
            authType: aws_lambda.FunctionUrlAuthType.NONE,
            invokeMode: aws_lambda.InvokeMode.RESPONSE_STREAM
        });

        //...以下略
        
Lambda関数のコンストラクタの設定プロパティであるenvirnomentにクレデンシャル変数を仕込むことで実行できそうですが、これはただしく機能しません。

つまり、LambdaへのAWSアカウント変数の直接の付与はセキュリティ上の観点から禁止されています。

ということで、サーバーサイドでSESにEmail送信権限を別の方法で与える必要が出てきます。

SES用のLamboda実行ロールを作成

先程は先に失敗する例から説明しましたが、リモートのLambdaから
@aws-sdk/sesを動かすには、基本のLambda実行ロールの他に以下で紹介されているSES用のポリシーが必要です。

参考|E メール送信アクションへのアクセスのみを許可

なお、SESの他にも、LambdaからS3、DynamoDBへアクセスする場合にも同じように適当な専用ロールが必要になります。

CDKからは、このSES用のポリシーを作成し、Lambdaのロールへ付与して使う必要があります。

            
            import {
    Stack,
    StackProps,
    aws_lambda,
    aws_iam,
    //...中略
} from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkTacokinShopStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        //...いろいろ中略

        //👇Lambda用の基本ポリシー
        const lambda_basic_policy = new aws_iam.ManagedPolicy(this, 'Lambda_basic_policy', {
            managedPolicyName: 'lambda_basic_policy',
            statements: [
                new aws_iam.PolicyStatement({
                    effect: aws_iam.Effect.ALLOW,
                    actions: [
                        "logs:CreateLogGroup",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents"
                    ],
                    resources: [
                        'arn:aws:logs:*:*:*'
                    ],
                }),
            ],
        });

        //☆👇SES用のメール送信ポリシー
        const ses_sender_policy = new aws_iam.ManagedPolicy(this, 'Ses_sender_policy', {
            managedPolicyName: 'ses_sender_policy',
            statements: [
                new aws_iam.PolicyStatement({
                    effect: aws_iam.Effect.ALLOW,
                    actions: [
                        "ses:SendEmail",
                        "ses:SendRawEmail"
                    ],
                    resources: [
                        '*'
                    ],
                }),
            ],
        });

        //👇カスタムロールの作成
        const ses_role = new aws_iam.Role(this, 'ses_sender_role', {
            roleName: 'ses_sender_role',
            assumedBy: new aws_iam.ServicePrincipal('lambda.amazonaws.com'),
        });
        //👇先ほど作成した2つのポリシーをアタッチ
        ses_role.addManagedPolicy(lambda_basic_policy);
        ses_role.addManagedPolicy(ses_sender_policy);

        const lambda = new aws_lambda.Function(this, 'Server', {
            runtime: aws_lambda.Runtime.NODEJS_20_X,
            code: aws_lambda.Code.fromAsset(join(__dirname, '../../build/lambda')),
            handler: 'index.handler',
            architecture: aws_lambda.Architecture.ARM_64,
            memorySize: 128,
            timeout: Duration.seconds(30),
            //☆👇カスタム実行ロールを指定
            role: ses_role
        }).addFunctionUrl({
            authType: aws_lambda.FunctionUrlAuthType.NONE,
            invokeMode: aws_lambda.InvokeMode.RESPONSE_STREAM
        });

        //...以下略
        
これでLambdaからでもnodemailer-SESクライアントでEmail送信が問題なく行えると思います。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

まとめ

以上、LambdaからでもNodemailer/SESクライアントでメールを送信するためのポイントを解説してみました。

これで外部のEmail操作用のAPIサービスを利用せずとも、AWS内だけでメール送信用サービスが完結できるので、シンプルなビジネスを行う場合に最適かと思います。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集