0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lambda→Slack通知の実装パターン|AI実務ノート 編集部

Last updated at Posted at 2026-01-03

1. 結論(この記事で得られること)

Lambda から Slack への通知実装、シンプルに見えて意外と奥が深いです。私も新人の頃、「とりあえず動けばいいや」で作って、本番で文字化けやリトライ地獄に遭遇しました。

この記事では以下が手に入ります:

  • 3つの実装パターン(Incoming Webhook / Slack App / EventBridge 経由)の使い分け基準
  • 本番で安全に運用できるコード(エラーハンドリング・リトライ・テスト込み)
  • AI(Claude/GPT)を使った最速デバッグ術(ログから原因特定まで5分)
  • 失敗パターン集(私が実際に踏んだ地雷を共有します)

「明日からプロジェクトで使える」レベルまで落とし込みます。

2. 前提(環境・読者層)

想定読者

  • Lambda で何らかの処理を実装した経験がある

  • Slack の Webhook URL は取得できる

  • Python or Node.js のどちらかが読める

  • Lambda: Python 3.11 / Node.js 18.x(どちらでも OK)

  • Slack: Incoming Webhook または Slack App

  • IaC: Terraform / CloudFormation(参考として提示)

3. Before:よくあるつまずきポイント

3-1. 「とりあえず動いた」で終わる危険性

初学者が書きがちなコード:

import json
import urllib.request
 
def lambda_handler(event, context):
    url = "https://hooks.slack.com/services/XXX/YYY/ZZZ"
    message = {"text": "処理完了"}
 
    req = urllib.request.Request(url, json.dumps(message).encode())
    urllib.request.urlopen(req)
 
    return {"statusCode": 200}

何が問題か?

① エラーハンドリングが皆無 → Slack 側エラーで Lambda が異常終了

② タイムアウト考慮なし → 30秒待ち続けて Lambda コストが膨らむ

③ リトライ制御なし → 一時的なネットワーク障害で通知が消失

④ テストが困難 → URL がハードコード、モックできない

私も昔、これで本番障害を起こしました。Slack の API が瞬間的に落ちたとき、Lambda が全部エラーになって DLQ が溢れた経験があります。

3-2. よくある質問と誤解

Webhook で十分では?
 現実: エラー時に再送制御が難しい。SQS 経由がベター

同期的に送って良い?
 現実: Lambda の実行時間が伸びる。非同期化推奨

Secrets Manager は必須?
 現実: 環境変数でも可。ただし暗号化は必須

4. After:基本的な解決パターン

4-1. パターン選定フローチャート

通知の重要度は高い?
  ├─ Yes → パターン3(EventBridge + SQS + Lambda)
  └─ No  → 
      ├─ リッチなフォーマットが必要?
      │   ├─ Yes → パターン2(Slack App + Block Kit)
      │   └─ No  → パターン1(Incoming Webhook)

4-2. パターン1:Incoming Webhook(基本形)

使いどころ:シンプルな通知、開発環境、PoC

import json
import urllib.request
import urllib.error
import os
from typing import Dict, Any
 
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
TIMEOUT_SECONDS = 10
 
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
    try:
        send_slack_notification(event.get("message", "No message"))
        return {"statusCode": 200, "body": "Notification sent"}
    except Exception as e:
        print(f"Error sending notification: {e}")
        # ここで失敗しても Lambda 自体は成功させる(通知失敗で本処理を止めない)
        return {"statusCode": 200, "body": "Notification failed but ignored"}
 
def send_slack_notification(message: str) -> None:
    payload = {
        "text": message,
        "username": "Lambda Bot",
        "icon_emoji": ":robot_face:"
    }
 
    req = urllib.request.Request(
        SLACK_WEBHOOK_URL,
        data=json.dumps(payload).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST"
    )
 
    try:
        with urllib.request.urlopen(req, timeout=TIMEOUT_SECONDS) as response:
            if response.status != 200:
                raise Exception(f"Slack API returned {response.status}")
    except urllib.error.URLError as e:
        raise Exception(f"Network error: {e}")

ポイント:

  • タイムアウトを明示(デフォルトは無制限で危険)
  • 通知失敗でも Lambda は成功扱い(本処理と通知を分離)
  • 環境変数から URL 取得(ハードコード厳禁)

4-3. パターン2:requests ライブラリ使用(Node.js)

Python の 「urllib」 は正直使いづらいので、実務では 「requests」 を使います。ただし Lambda Layer が必要。

Node.js なら標準で楽:

const https = require('https');
 
exports.handler = async (event) => {
    const webhookUrl = process.env.SLACK_WEBHOOK_URL;
    const message = event.message || 'No message';
 
    const payload = JSON.stringify({
        text: message,
        username: 'Lambda Bot',
        icon_emoji: ':robot_face:'
    });
 
    const url = new URL(webhookUrl);
    const options = {
        hostname: url.hostname,
        path: url.pathname + url.search,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(payload)
        },
        timeout: 10000
    };
 
    return new Promise((resolve, reject) => {
        const req = https.request(options, (res) => {
            let data = '';
            res.on('data', (chunk) => { data += chunk; });
            res.on('end', () => {
                if (res.statusCode === 200) {
                    resolve({ statusCode: 200, body: 'OK' });
                } else {
                    console.error(`Slack API error: ${res.statusCode} ${data}`);
                    resolve({ statusCode: 200, body: 'Notification failed but ignored' });
                }
            });
        });
 
        req.on('error', (e) => {
            console.error(`Request error: ${e.message}`);
            resolve({ statusCode: 200, body: 'Notification failed but ignored' });
        });
 
        req.on('timeout', () => {
            req.destroy();
            console.error('Request timeout');
            resolve({ statusCode: 200, body: 'Notification failed but ignored' });
        });
 
        req.write(payload);
        req.end();
    });
};

Node.js を選ぶ理由:

  • 標準ライブラリで完結(Layer 不要)
  • 非同期処理が自然に書ける
  • コールドスタートが Python より若干速い
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?