Python | 自動化:Teams 通知

Python
スポンサーリンク

概要(Teams 通知は「Python からチャネルにメッセージを飛ばす」技)

Teams 通知は、
「Python のスクリプトから、Microsoft Teams のチャンネルに自動でメッセージを送る仕組み」
です。

やりたいことは Slack 通知とほぼ同じで、

  • バッチが正常に終わったら「成功しました」と Teams に投稿する
  • エラーが起きたら「このバッチが落ちました」と即座に知らせる
  • 毎朝の定期レポートを Teams に流す

といった “人間が見ている場所に、コード側から話しかける” ことです。

初心者が迷いがちなポイントは、

  • Teams 側でどんな設定が必要なのか
  • Python からどうやってメッセージを送るのか
  • 実務でどう組み込むのがキレイか

なので、ここを重点的にかみ砕いていきます。


全体像(Python → Teams の流れをざっくりつかむ)

仕組みのイメージ:Incoming Webhook に JSON を投げる

Teams も Slack と同じように、Incoming Webhook という仕組みを提供しています。

構造はシンプルで、

  1. Teams 側で「このチャンネルに対する Webhook」を設定し、URL を1つ発行する。
  2. Python から、その URL に対して JSON を HTTP POST する。

これだけです。

Python から見ると、「決まった URL に requests.post で JSON を送るだけ」です。
その JSON の中身の形式(カードの形)は Teams 独自のルールがありますが、
最初はかなりシンプルな形から始めて大丈夫です。


Teams の Incoming Webhook のイメージ(準備側の話)

何が手に入るのかだけ理解しておけばOK

実際のクリック手順はここでは細かく書きませんが、
「何が最終的に必要なのか」だけ押さえれば、あとが理解しやすくなります。

Teams のチャンネルに「Incoming Webhook」を追加すると、
次のような URL が1つ発行されます。

https://outlook.office.com/webhook/....
あるいは
https://<テナント>/webhook/...

この URL は、

「ここに正しい形式の JSON を POST してくれたら、このチャンネルにメッセージを表示しますよ」

という専用の入り口です。

Python から見れば、

  • この URL は “宛先”
  • JSON は “メッセージ内容”
  • HTTP POST が “投げ方”

というだけです。

この URL は「秘密の入口」なので、後でセキュリティの話もします。


一番シンプルな Teams 通知コードを書いてみる

requests を使った最小サンプル

まずは、余計なことを全部削ぎ落とした「最小の Teams 通知」を見てみましょう。

import requests
import json

TEAMS_WEBHOOK_URL = "https://outlook.office.com/webhook/......"  # 実際のURLを設定

def send_teams_message(text: str):
    payload = {
        "text": text
    }

    headers = {
        "Content-Type": "application/json"
    }

    resp = requests.post(
        TEAMS_WEBHOOK_URL,
        data=json.dumps(payload),
        headers=headers,
        timeout=5
    )
    resp.raise_for_status()
    return resp

if __name__ == "__main__":
    send_teams_message("Python から Teams 通知テストです。")
Python

行ごとに意味をかみ砕いていきます。

TEAMS_WEBHOOK_URL
Teams で発行した Incoming Webhook の URL を入れます。
ここに向かってメッセージを投げます。

payload = {"text": text}
Teams に送るデータです。
最もシンプルな形として、text フィールドを持つ JSON を送ると、
チャンネルにそのままテキストとして表示されます。

headers = {"Content-Type": "application/json"}
「これから送るのは JSON ですよ」と伝えるヘッダーです。
Teams の Webhook はこのヘッダーを期待しているので、必ず付けます。

requests.post(..., data=json.dumps(payload), headers=headers, timeout=5)
HTTP POST で JSON を送っています。
ここでは json= ではなく data= で明示的に json.dumps していますが、
Teams 側は JSON を文字列として受け取るので、この形が分かりやすいです。
timeout=5 は「5秒以上かかったら諦める」というタイムアウトです。

resp.raise_for_status()
もし Teams 側が 400 や 500 系のエラーを返した場合、ここで例外が投げられます。
「通知に失敗した」ことをきちんと検知するために、必ず入れておきましょう。

これだけで、Python から Teams のチャネルに一行メッセージを投稿できます。


自動化バッチに組み込むための形に整える

通知関数を作って、成功時と失敗時で使い分ける

実務で使うなら、「どこからでも呼べる通知関数」として切り出しておくと便利です。

import os
import json
import logging
from pathlib import Path

import requests

BASE_DIR = Path(__file__).resolve().parent
LOG_FILE = BASE_DIR / "batch.log"

TEAMS_WEBHOOK_URL = os.environ.get("TEAMS_WEBHOOK_URL")

def setup_logging():
    logging.basicConfig(
        filename=LOG_FILE,
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(name)s - %(message)s"
    )

def send_teams(text: str):
    if not TEAMS_WEBHOOK_URL:
        logging.warning("TEAMS_WEBHOOK_URL が設定されていないため、Teams 通知をスキップします。")
        return

    payload = {"text": text}
    headers = {"Content-Type": "application/json"}

    try:
        resp = requests.post(
            TEAMS_WEBHOOK_URL,
            data=json.dumps(payload),
            headers=headers,
            timeout=5
        )
        resp.raise_for_status()
    except Exception as e:
        logging.exception(f"Teams 通知の送信に失敗: {e}")

def main():
    logging.info("日次バッチ開始")
    # ここにメイン処理を書く
    # 例: 1 / 0 でわざとエラー
    logging.info("日次バッチ正常終了")

if __name__ == "__main__":
    setup_logging()
    BATCH_NAME = "日次売上集計バッチ"
    try:
        main()
        send_teams(f"[OK] {BATCH_NAME} 正常終了")
    except Exception:
        logging.exception(f"{BATCH_NAME} で予期せぬ例外")
        send_teams(f"[ERROR] {BATCH_NAME} 異常終了。ログを確認してください。")
        raise
Python

このパターンは、現場でもそのまま使える“定番の形”です。

重要なポイントを深掘りします。

最初に、Webhook URL を環境変数から読んでいること。
URL をソースコードにベタ書きして Git に上げるのは危険なので、
TEAMS_WEBHOOK_URL は環境変数で渡すのが安全です(.env や設定ファイルから読むのも可)。

次に、通知関数の中で try/except を入れていること。
Teams 通知自体もネットワークに依存しているので、失敗します。
そのときに、通知エラーのせいで元のバッチの例外情報が隠れないよう、
ここでは logging.exception だけに留めて、本体の例外フローを壊さないようにしています。

最後に、成功と失敗でメッセージを分けていること。
Teams のタイムラインを見たときに、
「どのバッチがいつ成功したのか」「どれが失敗なのか」がひと目で分かるようにするのが大事です。


メッセージ内容を設計する(何を書いておくと助かるか)

「見た瞬間に状況が分かる」情報の組み立て方

通知メッセージには、全部の詳細を書く必要はありません。
むしろ、長すぎると読まれなくなります。

欲しいのは、「何が起きたかを一瞬で理解するための要約」です。

例えば、次のような情報があると便利です。

  • バッチ名(何の処理か)
  • 環境名(dev / staging / prod)
  • 成功なのか、失敗なのか
  • 実行開始・終了の時刻

これを踏まえて、メッセージ組み立ての関数を作ってみます。

from datetime import datetime

def build_success_message(batch_name: str, env: str = "prod") -> str:
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[OK][{env}] {batch_name} 正常終了\n完了時刻: {now}"

def build_error_message(batch_name: str, env: str = "prod") -> str:
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[ERROR][{env}] {batch_name} 異常終了\n発生時刻: {now}\n詳細はログを確認してください。"
Python

こうすると、main 側は次のように書けます。

if __name__ == "__main__":
    setup_logging()
    BATCH_NAME = "日次売上集計バッチ"
    ENV = "prod"

    try:
        main()
        send_teams(build_success_message(BATCH_NAME, ENV))
    except Exception:
        logging.exception(f"{BATCH_NAME} で予期せぬ例外")
        send_teams(build_error_message(BATCH_NAME, ENV))
        raise
Python

Teams を開いた瞬間に、

  • どの環境
  • どの処理
  • 成否
  • 時刻

がすぐに分かるようになります。

詳細(エラー内容)はログと役割分担する

「スタックトレースやエラーメッセージ全部を Teams に流したい」という気持ちは分かりますが、
現場ではあまりおすすめしません。

理由は、

  • メッセージが長くなりすぎて見づらい
  • チャネルがエラーログで埋まる
  • スマホで見ると地獄

だからです。

基本は、

  • Teams には「気づきのための要約だけ」
  • 詳細なエラー内容やスタックトレースは logging のログファイルで確認

という役割分担にしたほうが、運用が長続きします。


どこまで通知するか(頻度としきい値の考え方)

すべてのエラーを Teams に飛ばすと、すぐに死ぬ

例えば、バッチの中で 1000 レコードを処理していて、
その中の 10 レコードだけ形式がおかしくてスキップしたとします。

この 10 レコードそれぞれで「エラー通知」を飛ばしてしまうと、
チャネルはあっという間にノイズだらけになります。

人間はすぐに慣れてしまい、

  • 最初はちゃんと読む
  • 次第に「またか」で流し読み
  • 最後はミュートして、重大な通知も見なくなる

というパターンに陥ります。

そこで、

  • バッチ全体が止まったときだけ ERROR 通知。
  • レコード単位の失敗はログにのみ記録。
  • 失敗件数があるしきい値を超えたら Warning 通知。

といったルールを決めると良いです。

しきい値ベースの Teams 通知例

例えば、「API 失敗が 10 件以上あったら注意喚起したい」というケース。

ERROR_THRESHOLD = 10

def run_batch():
    error_count = 0
    for uid in user_ids:
        try:
            fetch_user(uid)
        except Exception:
            logging.exception(f"ユーザー情報取得エラー: user_id={uid}")
            error_count += 1

    if error_count >= ERROR_THRESHOLD:
        send_teams(f"[WARN] ユーザー情報取得エラーが多発しています: {error_count}件")
Python

これで、

  • 1〜2件の偶発的な失敗 → ログのみ
  • 10件以上の異常な失敗 → Teams に Warning 通知

というメリハリがつきます。


セキュリティと堅牢性で気をつけるポイント

Webhook URL は「パスワードと同じ扱い」にする

Teams の Webhook URL が漏れると、
その URL を知っている誰でも、そのチャンネルに勝手に投稿できてしまいます。

なので、

  • ソースコードに直書きしない
  • GitHub など公開リポジトリに絶対含めない
  • 共有するときは環境変数や設定ファイル経由にする

といった配慮が必要です。

初心者でもすぐできる対策は、

Python 側では os.environ.get("TEAMS_WEBHOOK_URL") のように環境変数から読む。
各環境(ローカル・テスト・本番)の設定は OS 側に持たせる。

という形です。

通知処理が「本体のエラー」を上書きしないようにする

通知処理も I/O を含むので、
Teams 側の障害やネットワーク不良で失敗することがあります。

このときにやってはいけないのは、

通知で起きた例外が、元の本番処理の例外を隠してしまうこと

です。

先ほどのコードのように、

  • 本体の例外は、トップレベルで logging.exception+raise
  • Teams 通知の中では try/except し、失敗はログにだけ残す

という二段構えにしておくと、
少なくとも「元のエラー原因はログで追える」状態を保てます。


まとめ(Teams 通知は「自動化とチームをつなぐ声」)

Python 自動化における Teams 通知の要点を整理すると、こうなります。

Teams 通知は、Python スクリプトから Teams チャンネルにメッセージを飛ばす仕組みで、バッチの成功・失敗やレポート配信と相性が良い。
Incoming Webhook を使えば、「発行された Webhook URL に JSON を POST するだけ」でメッセージを送れる。
通知関数(send_teams)を用意し、トップレベルの try/except の中で「成功時」「失敗時」に呼び分けると、自動化バッチにきれいに組み込める。
メッセージには「バッチ名・環境・時刻・成功/失敗」を含め、詳細はログに任せることで、Teams は“気づき”に専念させる。
Webhook URL や認証情報は環境変数などで管理し、通知する範囲と頻度(どのエラーで通知するか、しきい値をどうするか)を決めておくことで、通知疲れを防ぐ。

ここまで押さえれば、「Python のバッチが Teams にしゃべりかけてくる」状態はいつでも作れます。

タイトルとURLをコピーしました