こちらの記事で、 EventBridge Scheduler のデッドレターキューは後回しにするとしてました。
けど、その後あれこれ見ていたら、 EventBridge Scheduler の画面のすぐ近くに、 『パイプ』というのがあるのに気がつきました。

パイプ・・・古くは(今も使いますが) UNIX / Linux のシェルにつきもののパイプですかね?あれと同じような機能を模していれば、これ使えば Lambda なんて立てずとも SNS にメッセージ送れそうです。ということで、 EventBridge Pipes を調べてみます。
Amazon EventBridge Pipes - Amazon EventBridge
ほう、どうやら思ったように、ソースに指定したサービスから、ターゲットに指定したサービスにメッセージを送ることができるようです。Pipes の機能としては、フィルタリング、メッセージの変換などもやってくれるっぽいです。
となると、 Scheduler が Lambda の起動に失敗して、デッドレターキューにメッセージを送るとき、この EventBridge Pips を定義しておけば、デッドレターキューの SQS から SNS トピックにメッセージを流して、 SNS がサブスクリプションのメールに通知する、ということができそうです。
まあ、 Scheduler から Lambda を呼び出した際に失敗した際に使う部分なんで、よっぽどクリティカルなサービスを構成する以外だと、そんなに使わないかもしれませんが、せっかくなので試してみます。
デッドレターキューの設定
まずは、手作業でいろいろとやってみます。最初は SQS を作るところですね。
Amazon SQS の作成
aws のコンソールで SQS を開いて、『キューの作成』を選択します。

キューの名前は必ず入力します。あとはデフォルトのままでも問題ないかと思いますが、 SQS からメッセージを読みだす処理を作成しないなら、『メッセージ保持期間』は長めにしたほうがいいかもしれません。
特に問題がなければこんな感じにキューが作られます。

Scheduler の設定変更
次は、 Scheduler のほうで、デッドレターキューを設定します。作成済みのスケジュールを選択して、『編集』ボタンを押します。オプションの設定のところで、デッドレターキューとして先ほど作成した SQS を選択します。

なお、上記ではついでに再試行を無効としています。
また、アクセス許可の部分で、新しいロールを作成するようにしました。

これは、 SQS への書き込み権限が必要になるためです。もし、既存のロールを使うのであれば、そのロールに対して、 SQS へメッセージを書き込むための権限を与えておいてください。
"Effect": "Allow", "Action": [ "sqs:SendMessage" ], "Resource": [ "arn:aws:sqs:ap-northeast-1:xxxxxxxxxxxx:test" ]
こんな感じの権限をあたえればよいはずです。
テスト 1
いきなり EventBridge Pipes で SNS とつなぐ前に、まずはここまでのところ、 Scheduler のデッドレターキューを指定したときに、SQSにメッセージが送られることを確認したいと思います。
とはいっても、このまま起動するだけだと、ちゃんと Lambda が起動できてしまいますので、 Scheduler のロールのポリシーに対して、 Lambda 関数の ARN を存在しない関数のものに変えておきます。こんな感じですね。
"Effect": "Allow", "Action": [ "lambda:InvokeFunction" ], "Resource": [ "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:lambda-only-hello-world-PeriodicLambdaFunction-存在しないサフィックス:*", "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:lambda-only-hello-world-PeriodicLambdaFunction-存在しないサフィックス" ]
Scheduler の終了時刻を調整して、 Lambda 呼び出しを実行します。しばらくしてから、 SQS を見てみると、

メッセージが来てますね。キューをクリックすると、

『メッセージを送受信』とあるので、こちらをクリックします。

さらに『メッセージを受信』のところで、『メッセージをポーリング』ボタンをクリックすると、

こんな感じにメッセージが現れます。これをクリックすると、

詳細を確認することができました。
ここまでで、デッドレターキューに正しくメッセージが送られているのがわかりますね。
EventBridge Pipes の作成
では、次は、デッドレターキューが動作したようなので、いよいよ EventBridge Pipes を作成します。
aws コンソールからパイプを開いて、『パイプの作成』を選択します。最初にパイプ名を入力します。

次に、その下側の『パイプの構築』を選択したまま、

ソースの設定を行います。

ソースとして、 SQS を選択して、さきほど作成したキューを選択します。とりあえずは、追加設定はせずに、『次へ』をクリックします。『フィルタリング』および『強化』(エンリッチメントの訳ですね)オプションは飛ばします。
最後の『ターゲット』では、SNSトピックを選択します。

このままパイプを作成すると、ロールが新規に作成されます。もし、既存のロールを使いたいなど、ロール等の設定を変更したければ、『パイプの構築』の隣にある『パイプ設定』を選択して、内容を編集します。
設定内容が良ければ『パイプを作成』ボタンを押します。無事に、パイプの作成が成功すれば完了です。
テスト 2
パイプの作成が完了したら、早速テストを行ってみます。
でも、その前にソースとターゲットの設定以外のオプションを替えてなければ、パイプが作成された時点で有効になっています。先ほどのテストで、SQS にメッセージが入っていたと思います。
ということで、設定に問題が無ければ、この時点で先ほどのメッセージが SNS トピックに送られているはずです。改めて、 SQS を見ると、

このように、『利用可能なメッセージ』が 0 件になってます。SNSトピックのサブスクリプションに指定したメールアドレスを見ると、

こんな感じに、 JSON が本文に入ったメールが来てました。この時点で、パイプが正しく動いてそうです。
念のため、再度 Scheduler を起動すると、Lambda を呼び出したタイミングで、先ほどのメールアドレスにメールが届きます。問題ないようですね。
入力トランスフォーマー
ま、一応メールは届いてますが、さすがにちょっとわかりくくて、いかがなものかと思います。ということで、パイプの機能にある入力トランスフォーマーというのを使って、最低限の整形だけしたいと思います。
Amazon EventBridge Pipes の入力変換 - Amazon EventBridge
作成したパイプを編集して、ターゲットを選択します。すると、オプションとして『ターゲット入力トランスフォーマー』を設定する画面が表示されるので、中央のフィールドに、変換したいルールをJSONの形式で入力します。

{ "SCHEDULED_TIME": <$.messageAttributes.SCHEDULED_TIME.stringValue>, "TARGET_ARN": <$.messageAttributes.TARGET_ARN.stringValue>, "SCHEDULE_ARN": <$.messageAttributes.SCHEDULE_ARN.stringValue>, "ERROR_MESSAGE": <$.messageAttributes.ERROR_MESSAGE.stringValue>, "ERROR_CODE": <$.messageAttributes.ERROR_CODE.stringValue> }
この程度の情報が送られてくれば、あとは aws のコンソール側で調べられるのでいいかと思います。
ただ、残念ながらターゲットが SNS トピックの場合、メールの件名は変更できないようです(常に『AWS Notification Message』の件名で来ます)。もし、メールの件名を変更するのだったら、 Step Function や Lambda を介さないといけないっぽくて、それだと簡単にデッドレターキューを設定したいという意図から外れるので、今回はやりませんでした。
入力トランスフォーマーのテスト
で、上記を設定後、再度スケジュールで Lambda 呼び出しを失敗するテストをやってみます。すると、

こんな感じに抜粋されたメッセージが送られてきました。いい感じですね。
SAM で設定
せっかくなので、ここまでの内容を SAM でまとめて作ってみたいと思います。下記などを参考に作ったものを
SAM による EventBridge Scheduler の定期実行(デッドレターキューの設定あり) · GitHub
こちらの gist に置いておきますので、ご興味のある方は参考にしてください。
これを元にして、デプロイしておきます。
なお、デプロイするとわかりますが、 EventBridge Scheduler のロールに特に指定しなくても、デッドレターキューを指定すると

のように権限が追加されていました。
Ref と GetAtt
ちなみにこれ作るとき、 Ref と GetAtt で混乱してしまい、結構時間がかかってしまいました。この部分です。
EventBridgePipe: Type: AWS::Pipes::Pipe Properties: Name: scheduler-dead-letter Source: !GetAtt SchedulerDeadLetterQueue.Arn Target: !Ref NotificationTopic RoleArn: !GetAtt EventBridgePipeRole.Arn
これは最終的にうまくいったやつですが、それまで、
Source: !Ref SchedulerDeadLetterQueue Target: !Ref NotificationTopic
とか
Source: !GetAtt SchedulerDeadLetterQueue.Arn Target: !GetAtt NotificationTopic.Arn
とかして、エラーになってました。
結論からすると、下記の記事にあるように
CloudFormation の参照周りで意識すべきポイント・Tips | DevelopersIO
リソースによって Ref で返されるものが違うので公式ドキュメントを確認する必要があるとのことでした。で、実際、 SNS と SQS のドキュメントをあたってみます。
これらをみると、Ref で SQS は URL を返しています。ついでに、 SNS は GetAtt で Arn を取得するときは TopicArn を使う、となってます。
まじかー、ということで小一時間以上はまってました。こういう細かい違いは最終的に自分で確認しないと(今のところは) ChatGpt とか Copilot が出してくるサンプルだけだとわかんないですね。
なので、最終的に
Source: !GetAtt SchedulerDeadLetterQueue.Arn Target: !Ref NotificationTopic
の形としました。なお、
Source: !GetAtt SchedulerDeadLetterQueue.Arn Target: !GetAtt NotificationTopic.TopicArn
でもいけます。どっちの記述のほうがいいんだろうか?このあたりはしばらくやらないと慣れなさそうです。
テスト 3
では、早速テストです。一度このままの状態で動かします。定期的にメールが送られてくるのがわかります。
次に、このままだと正しく Lambda が呼ばれるだけなので、 Scheduler のロールのポリシーを

から、

のように変更して、 Lambda 呼び出しに失敗するようにします。
すると、先ほどのようにパイプで指定した通り、 SQS -> SNS の流れで整形したメールが送られてくることが確認できました。権限を元に戻すと、正しいメール(Lambdaからのメール)が到着するようになりました。
まとめ
EventBridge Scheduler のデッドレターキューに SQS ってどうなんだろうか?と思ってましたが、便利にアプリケーションを構成できるもんですね。