もしかしたら役に立つ人がいるかもしれないので、ちょっと昔に書いたコードをメモとして公開。
SES → SNS → Lambda 連携の設定方法やテスト方法などは
といったところを参考にしていただけたら。
(他にも多数の方がまとめていますが、とりあえず検索して一番に見つかった記事を挙げてみました)
注意点
動作させるためには Lambda の関数の環境変数に Slack の URL を設定する必要があります
Lambda の関数の 設定 > 環境変数 の「編集」から環境変数を追加して
- キー:SLACK_INCOMING_WEBHOOK_URL
- 値:通知したい Slack チャンネルへの Incoming Webhook の URL(Slack App の Incoming Webhooks を利用します)
を指定して下さい。
Lambda のランタイムは Node.js 18.x 以上を想定。
/* global fetch */
console.log('Loading function');
// Slack の incoming webhook の設定を環境変数から取り込む
const slackUrl = process.env.SLACK_INCOMING_WEBHOOK_URL;
if (!slackUrl) {
const errMsg = 'Error: SLACK_INCOMING_WEBHOOK_URL is not defined.';
console.error(errMsg);
throw new Error(errMsg);
}
export const handler = async (event) => {
// for debug: log event entirely
// console.log(JSON.stringify(event, null, 2));
const results = [];
// SNS のイベントにある全 Records を処理
for (const record of (event.Records || [])) {
if (!record.Sns) continue;
try {
// Slack へイベントを通知
console.log('--- Call fetch start');
const response = await fetch(slackUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(makeSlackMessage(record.Sns))
});
console.log(`Response status: ${response.status}`);
results.push(response.status);
console.log('--- Call fetch end');
} catch (e) {
console.error('--- Fetch error:', e);
results.push(500);
}
}
return results;
};
// Slack 用のメッセージ作成
function makeSlackMessage(sns) {
let sesNotify;
try {
sesNotify = JSON.parse(sns.Message);
} catch (e) {
return { text: makeJsonCodeBlock('Amazon SES からの不明な通知', sns) };
}
if (!sesNotify.notificationType) {
return { text: makeJsonCodeBlock('Amazon SES からの不明な通知', sns) };
}
// SES の通知を notificationType に応じた処理分け
// reference: https://docs.aws.amazon.com/ja_jp/ses/latest/dg/notification-contents.html
let type = sesNotify.notificationType;
let details = "";
switch (type) {
case 'Bounce':
type = `メールの不達(${sesNotify.bounce.bounceType})`;
details = makeJsonCodeBlock('不達先の詳細情報', sesNotify.bounce.bouncedRecipients);
break;
case 'Complaint':
type = `メールへの苦情(${sesNotify.complaint.complaintFeedbackType})`;
details = makeJsonCodeBlock('苦情元の詳細情報', sesNotify.complaint.complainedRecipients);
break;
case 'Delivery':
type = "メールの送信成功";
details = makeJsonCodeBlock('送信先の詳細情報', sesNotify.delivery.recipients);
break;
default:
// Nothing to do
}
// SES 通知の元になったメール情報を取得
const from = sesNotify.mail?.commonHeaders?.from?.join(',') || sesNotify.mail?.source || "-";
const subject = sesNotify.mail?.commonHeaders?.subject || "-";
const notificationAll = makeJsonCodeBlock('通知の全情報', sesNotify);
// 最終的な Slack 用メッセージ作成
return {
text: `【${type}】SES通知: ${subject}`, // 通知ポップアップ用
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: `Amazon SES からの通知(common)\n種類:${type}\nメール送信元:${from}\nメール件名:${subject}\n${details}`
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: notificationAll
}
}
]
};
}
function makeJsonCodeBlock(title, data) {
// 全情報のダンプ(Slackの3000文字制限対策付き)
const fullJson = JSON.stringify(data, null, 2);
const displayJson = fullJson.length > 2900 ? `${fullJson.slice(0, 2900)}...` : fullJson;
return `${title}:\n\`\`\`json\n${displayJson}\n\`\`\``;
}