AWS Lambda+SNSのメール配信におけるエラーハンドリングを実装する

# はじめに
本記事ではAWSのLambdaとSNSを使用したメール送信において、エラーハンドリングを実装していきます。


# ゴール
下記の２点を確認することをゴールとします。

1. Lambda→SNS段階のエラーを検知し、エラーメールを送信できてる事
2. SNS→メール配信段階のエラー検知し、エラーメールを送信できてる事

# 処理フロー

```
【正常系】
Lambda → SNS → メール配信

【エラー系1: Lambda→SNS段階】
Lambda → エラー発生 → エラーメール送信

【エラー系2: SNS→メール配信段階】
SNS → エラー発生 → CloudWatchメトリクスで検知 → アラーム状態 → エラーメール送信
```

# 作るもの
- AWS Lambda：1つ
- Amazon SNS：1つ
- Amazon CloudWatch：1つ


# 1. SNSトピックの作成

## トピック・サブスクリプションの作成

トピックとサブスクリプションの作成手順については、次の記事に記載されてる方法を参照ください。

https://qiita.com/enumura1/items/f35a22c538f17fe9cc27

# 2. Lambda関数の作成

LambdaからSNSを利用したメール通知に関しても、次の記事に記載されてるソースを参考に実装しています。

https://qiita.com/enumura1/items/f35a22c538f17fe9cc27

```python:lambda_function.py
import json
from notifier import send_notification, send_error_notification

def lambda_handler(event, context):
    TOPIC_ARN = 'SNSトピックのARN'

    subject = "Lambda からの通知"
    message = "テスト通知メッセージ"
    
    try:
        response = send_notification(TOPIC_ARN, message, subject)
        
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': '通知が送信されました',
                'messageId': response['MessageId']
            })
        }
        
    except Exception as e:
        error_message = str(e)
        
        send_error_notification(
            TOPIC_ARN,
            error_message,
            "Lambda-SNS通信エラー"
        )
        
        return {
            'statusCode': 500,
            'body': json.dumps({
                'error': 'メール送信に失敗しました',
                'details': error_message
            })
        }
```

<details><summary>notifier.py</summary>

```python:notifier.py
import json
import boto3
from datetime import datetime

sns = boto3.client('sns')

def send_notification(topic_arn, message, subject):
    response = sns.publish(
        TopicArn=topic_arn,
        Message=message,
        Subject=subject
    )
    return response


def send_error_notification(topic_arn, error_message, error_type):
    error_subject = f"【エラー通知】{error_type}"
    error_body = f"""
エラーが発生しました。
発生時刻: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
エラー種別: {error_type}
エラー詳細: {error_message}
    """
    
    try:
        sns.publish(
            TopicArn=topic_arn,
            Message=error_body,
            Subject=error_subject
        )
    except Exception:
        pass
```

</details>

:::note warn

Lambdaを動かす際にはIAMのポリシーからSNSの権限付与を行っていてください。

:::

# 3. CloudWatchアラームの作成

- 「アラーム」→「すべてのアラーム」を選択し、「アラームの作成」をクリック
- メトリクスの選択：
    - メトリクス名: NumberOfNotificationsFailed
    - トピック名: 監視対象のSNSトピック名を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/53be1028-9218-9d63-29da-1f442e5bb06a.png)


- メトリクスオプションの設定：
    - 統計: Sum
    - 期間: 5分

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/0e3de9ff-9fc1-7249-9599-36f467088e0a.png)

- 条件の設定
    - しきい値の種類: 静的
    - アラーム条件: 以上
    - しきい値: 1

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/d2323121-dd15-3c15-7326-f948116d88d4.png)


- アラームアクションの設定
    - アラーム状態トリガー：アラーム状態
    - SNSトピックを選択
    - 通知の送信先：エラー通知用のSNSトピック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/47637235-fcde-1276-c1ff-9fb69692fda2.png)


# 動作の確認

## 正常系の確認

Lambdaをテスト実行し、正常にメールが通知されることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/14d3212a-a717-3ebd-e7b3-fbd37b3719f5.png)

```:内容
件名：Lambda からの通知
本文：これはテスト通知メッセージです。
```

## エラー系1: Lambda→SNS通信エラーの確認

意図的にLambdaのソースを誤ったものにし、Lambdaを実行してみます。

```python:誤った例
response = sns.publish(
        TopicArn=topic_arnあああ,
        Message=message,
        Subject=subject
    )
```

Lambda実行後、Lambda側で定義した内容のエラーメールが送信されることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/63a86f2d-e782-9e73-b275-b61a924e287b.png)

```:エラーメール
件名：【エラー通知】Lambda-SNS通信エラー
本文：
エラーが発生しました。
発生時刻: 2024-10-31 15:48:41
エラー種別: Lambda-SNS通信エラー
エラー詳細: name 'topic_arnあああ' is not defined
```

## エラー系2：SNS配信エラーの確認

AWS CLIを使用してメトリクスをアラーム状態にし、配信エラーを擬似的に再現します。

```
# アラーム状態に設定
aws cloudwatch set-alarm-state \
  --alarm-name アラーム名 \
  --state-value ALARM \
  --state-reason "Testing alarm notification"
```

:::note info
次のコマンドではアラームをOK状態に戻すことができます。
```
# OK状態に設定
aws cloudwatch set-alarm-state \
  --alarm-name アラーム名 \
  --state-value OK \
  --state-reason "Test completed"
```
:::

コマンド実行後、CloudWatchアラームによるエラーメールが送信されることを確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247619/3290f979-af0c-a033-b53c-6ca6933c2089.png)


# 参考

https://dev.classmethod.jp/articles/tsnote-cloudwatch-alarm-test-001/

https://qiita.com/enumura1/items/f35a22c538f17fe9cc27