概要
メールをlambdaからSNSを通して送信するサンプル。
コード
import { Aspects, CfnOutput, Stack, StackProps, Tag } from "aws-cdk-lib";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Topic } from "aws-cdk-lib/aws-sns";
import { EmailSubscription } from "aws-cdk-lib/aws-sns-subscriptions";
import { Construct } from "constructs";
interface AlarmSNSStackProps extends StackProps { emailAdress: string }
export class AlarmSNSStack extends Stack {
constructor(scope: Construct, id: string, props: AlarmSNSStackProps) {
super(scope, id, props);
const topicName = 'topic'
const snsTopic = new Topic(this, topicName, { displayName: topicName, topicName, fifo: false });
snsTopic.addSubscription(new EmailSubscription(props.emailAdress));
snsTopic.grantPublish(new NodejsFunction(this, 'publishLambda', {
runtime: Runtime.NODEJS_18_X,
entry: `../src/handler/invoke/events/sendMail.ts`,
environment: { snsTopic: topicName },
}))
new CfnOutput(this, `sns-confirm-subscription`, {
value: `aws sns confirm-subscription --topic-arn ${snsTopic.topicArn} --authenticate-on-unsubscribe true --region ${props.env?.region} --token xxxCopyAndPasteTokenfromMailxxx`,
})
new CfnOutput(this, `sns-list`, {
value: `aws sns list-subscriptions-by-topic --topic-arn ${snsTopic.topicArn}`,
})
Aspects.of(this).add(new Tag('Stack', 'AlarmSNSStack'));
}
}
import { sendEMailMessage } from "@/domain/sns";
import { Handler } from "aws-lambda";
export const handler: Handler = async (event, context) => {
console.log(event);
const [__arn, __partition, __service, __region, accountId, __resourceId] = context.invokedFunctionArn.split(':');
const envList = ['snsTopic', 'AWS_REGION'] as const;
envList.forEach(k => { if (!process.env[k]) throw new Error(`${k} environment required`) });
const processEnv = process.env as Record<typeof envList[number], string>;
await sendEMailMessage({ account: accountId, region: processEnv.AWS_REGION, snsTopic: processEnv.snsTopic, subject: '件名テスト', message: '本文テスト' })
}
import { PublishCommand, SNSClient, SubscribeCommand } from "@aws-sdk/client-sns";
let snsClient: SNSClient | null = null;
const getSNSClinent = (region: string): SNSClient => {
if (snsClient) return snsClient;
snsClient = new SNSClient({ region });
return snsClient
}
const sendEMailMessage = async (props: { region: string; account: string; snsTopic: string, subject: string, message: string }) => {
const params = {
TopicArn: `arn:aws:sns:${props.region}:${props.account}:${props.snsTopic}`,
Subject: props.subject,
Message: props.message
};
const snsClient = getSNSClinent(props.region)
try {
const data = await snsClient.send(new PublishCommand(params));
console.log("Success.", data);
return data;
} catch (err) {
console.error("Error", err);
throw err;
}
}
export { sendEMailMessage };
lambda実施前にsubscriptionの設定
デプロイ直後にaws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:0000000000:topic
を実行すると下記の結果が得られる。
{
"Subscriptions": [
{
"SubscriptionArn": "PendingConfirmation",
"Owner": "0000000000",
"Protocol": "email",
"Endpoint": "hoge@gmail.com",
"TopicArn": "arn:aws:sns:ap-northeast-1:0000000000:topic"
}
]
}
Amazon SNS メール通知の「unsubscribe」リンクを無効化するを参考に、確認メールのリンクに含まれていたトークンから承認を行う。
リンクをコピーしたアドレス内に、下記のようなトークンが含まれているのでこれをつかう。
その後、aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:ap-northeast-1:0000000000:topic
を実行すると下記の結果が得られる。
{
"Subscriptions": [
{
"SubscriptionArn": "arn:aws:sns:ap-northeast-1:0000000000:topic:11111111-xxxx-xxxx-xxxx-777xxx777xxx",
"Owner": "0000000000",
"Protocol": "email",
"Endpoint": "hoge@gmail.com",
"TopicArn": "arn:aws:sns:ap-northeast-1:0000000000:topic"
}
]
}
この状態で、コンソールからLambdaを実行し、メールが届くことを確認できた。
GMailだと、サブスクリプションの確認メールは通常のフォルダだが、その後の通知メールはプロモーションフォルダに届いていた。
参考
デベロッパーガイド Amazon SNS メッセージの発行
誰かサブスクリプション解除した?を無くすために Amazon SNS によるメール通知の停止(Unsubscribe)リンクを無効化してみた