Claude Code + Microsoft Graph API でTeamsを自動化する完全ガイド【2026年版】

Claude CodeとMicrosoft Graph APIを組み合わせてTeamsチャンネルへの自動投稿・ボット作成・会議スケジューリングを実現する完全ガイド。実際のプロンプトと生成コードを全公開。

この記事をシェア:
目次 クリックで開く

Claude Code 体験記 / Microsoft Teams 自動化

📅 2026年4月26日
✍️ Aurant Technologies 編集部
⏳ 読了時間:約16分
「毎朝Teamsに手動で日報を投稿している」「会議スケジュールの調整に時間がかかる」——そんな悩みをClaude CodeとMicrosoft Graph APIが解決してくれました。
本記事では、Claude Codeに日本語で指示するだけで、Teamsへのチャンネルメッセージ投稿・ボット作成・会議スケジューリングを自動化するまでの全工程を、実際のプロンプトと生成されたコードとともに公開します。
Graph APIの認証設定からPython実装まで、Claude Codeがまるごとサポートしてくれます。
Claude CodeでTeamsを自動化するデモ画像

▲ Claude Code が生成したコードでTeamsチャンネルへの自動投稿を実現した実行結果イメージ

そもそも Claude Code とは

Claude Code は、Anthropicが提供するターミナル上で動くAIコーディングエージェントです。自然言語で「こういうコードを書いて」と指示するだけで、Claude Codeがファイルを読み書きし、コードを生成・実行まで行ってくれます。

今回のテーマである「Microsoft Teams自動化」は、Claude Codeの得意分野のひとつです。Graph APIの複雑な認証フローや各エンドポイントの使い方をClaude Codeに任せれば、APIドキュメントを読み解く時間を大幅に短縮できます。

Microsoft Graph API とは: Microsoft 365のすべてのサービス(Teams・Outlook・SharePoint・OneDriveなど)に統一されたエンドポイントでアクセスできるRESTful APIです。https://graph.microsoft.com/v1.0/ベースURLから各リソースにアクセスします。

今回 Claude Code に作ってもらったもの

この体験記では、以下の6つのシナリオをClaude Codeに依頼しました。すべて実際のプロンプトと、Claude Codeが生成したコードを掲載しています。

🔐
Azure AD
認証設定
💬
チャンネル
メッセージ投稿
🤖
ボット
作成
📅
会議
スケジュール
🔔
メンション
通知
📤
バッチ
一括投稿

Step 1:Azure AD アプリ登録と認証トークン取得

Graph APIを使うには、まずAzure Active Directoryにアプリを登録してクライアントID・シークレットを取得する必要があります。この設定が一番ハードルが高いのですが、Claude Codeに手順を聞けばステップバイステップで教えてくれます。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODEMicrosoft Graph APIを使ってTeamsに自動投稿したいです。
Azure ADのアプリ登録で必要な設定手順と、
Pythonでクライアント資格情報フロー(client_credentials)を使って
アクセストークンを取得するコードを書いてください。

必要なAPIパーミッション:
– ChannelMessage.Send
– Chat.ReadWrite
– OnlineMeetings.ReadWrite
– Team.ReadBasic.All

環境変数から CLIENT_ID、CLIENT_SECRET、TENANT_ID を読み込む形にしてください。
requestsライブラリを使い、取得したトークンを再利用できるようキャッシュしてください。

🤖 CLAUDE CODE の回答
Azure ADのクライアント資格情報フローでトークンを取得し、有効期限が切れたら自動更新するGraphクライアントクラスを生成します。pip install requests python-dotenvを実行してから進めてください。
🐍 graph_client.py — Claude Code が生成したコード
import os
import time
import requests
from dotenv import load_dotenv
from typing import Optional, Dict, Any

load_dotenv()

class GraphClient:
    """Microsoft Graph API クライアント(トークン自動更新付き)"""

    BASE_URL  = "https://graph.microsoft.com/v1.0"
    TOKEN_URL = "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"

    def __init__(self):
        self.client_id     = os.environ["CLIENT_ID"]
        self.client_secret = os.environ["CLIENT_SECRET"]
        self.tenant_id     = os.environ["TENANT_ID"]
        self._token: Optional[str] = None
        self._token_expires: float = 0

    def _get_token(self) -> str:
        """アクセストークンを取得(期限切れなら自動更新)"""
        if self._token and time.time() < self._token_expires - 60:
            return self._token

        url  = self.TOKEN_URL.format(tenant=self.tenant_id)
        resp = requests.post(url, data={
            "grant_type":    "client_credentials",
            "client_id":     self.client_id,
            "client_secret": self.client_secret,
            "scope":         "https://graph.microsoft.com/.default",
        })
        resp.raise_for_status()
        data = resp.json()
        self._token         = data["access_token"]
        self._token_expires = time.time() + data.get("expires_in", 3600)
        print(f"[Graph] トークン取得成功(有効期限: {data.get('expires_in', 3600)}秒)")
        return self._token

    def _headers(self) -> Dict[str, str]:
        return {
            "Authorization": f"Bearer {self._get_token()}",
            "Content-Type":  "application/json",
        }

    def get(self, path: str, **kwargs) -> Dict[str, Any]:
        url  = f"{self.BASE_URL}{path}"
        resp = requests.get(url, headers=self._headers(), **kwargs)
        resp.raise_for_status()
        return resp.json()

    def post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        url  = f"{self.BASE_URL}{path}"
        resp = requests.post(url, headers=self._headers(), json=body)
        resp.raise_for_status()
        return resp.json()

if __name__ == "__main__":
    client = GraphClient()
    teams  = client.get("/teams")
    for team in teams.get("value", []):
        print(f"  Team: {team['displayName']} ({team['id']})")
▶ 実行結果 (python graph_client.py)Claude CodeのTeams自動化実行結果
[Graph] トークン取得成功(有効期限: 3600秒)
Team: 営業部チーム (a1b2c3d4-0001-0002-0003-e5f6a7b8c9d0)
Team: 開発部チーム (b2c3d4e5-0011-0022-0033-f6a7b8c9d0e1)
Team: 全社アナウンス (c3d4e5f6-0111-0222-0333-a7b8c9d0e1f2)

Claude Codeが生成したコードで、トークンの自動更新付きのGraphクライアントが一発で完成しました。環境変数から認証情報を読み込む安全な実装になっています。

Step 2:Teamsチャンネルにメッセージを投稿する

認証ができたので、次はClaude CodeにTeamsチャンネルへのメッセージ投稿コードを作ってもらいます。Graph APIのChannelMessage送信エンドポイントを使います。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODE先ほど作ったGraphClientを使って、Teamsの指定チャンネルにメッセージを投稿する関数を書いてください。

要件:
– チームID・チャンネルIDを引数で受け取る
– メッセージ本文はHTML形式(太字・リスト・色付きテキストが使えるように)
– 重要度を設定できる(normal / urgent / important)
– @メンション(ユーザーIDを指定)をメッセージに含められる
– 投稿成功時は投稿されたメッセージのIDとURLを返す

実際に「日報:本日の作業完了」というタイトルでHTML本文付きの投稿テストもしてください。

🤖 CLAUDE CODE の回答
Graph APIの/teams/{teamId}/channels/{channelId}/messagesエンドポイントを使ってメッセージを投稿する関数を生成します。HTML本文・重要度・メンションをサポートします。
🐍 post_channel_message.py — Claude Code が生成したコード
from graph_client import GraphClient
from typing import List, Optional, Dict, Any

def post_channel_message(
    client: GraphClient,
    team_id: str,
    channel_id: str,
    subject: str,
    body_html: str,
    importance: str = "normal",
    mentions: Optional[List[Dict]] = None,
) -> Dict[str, Any]:
    """Teamsチャンネルにメッセージを投稿する。

    Args:
        mentions: [{"id": "user-object-id", "displayName": "田中太郎",
                    "userIdentityType": "aadUser"}]
    Returns:
        投稿されたメッセージのdict(id, webUrl を含む)
    """
    if mentions is None:
        mentions = []

    mention_entities = [
        {
            "id": i,
            "mentionText": m["displayName"],
            "mentioned": {
                "user": {
                    "id": m["id"],
                    "displayName": m["displayName"],
                    "userIdentityType": m.get("userIdentityType", "aadUser"),
                }
            },
        }
        for i, m in enumerate(mentions)
    ]

    mention_html = ""
    for i, m in enumerate(mentions):
        mention_html += f'<at id="{i}">{m["displayName"]}</at> '

    full_body = (mention_html + "<br>" + body_html) if mention_html else body_html

    payload: Dict[str, Any] = {
        "subject":    subject,
        "importance": importance,
        "body": {"contentType": "html", "content": full_body},
    }
    if mention_entities:
        payload["mentions"] = mention_entities

    path   = f"/teams/{team_id}/channels/{channel_id}/messages"
    result = client.post(path, payload)

    msg_id  = result.get("id", "")
    web_url = result.get("webUrl", "")
    print(f"[Teams] 投稿成功 → ID: {msg_id}")
    print(f"[Teams] URL: {web_url}")
    return result


if __name__ == "__main__":
    client     = GraphClient()
    TEAM_ID    = "a1b2c3d4-0001-0002-0003-e5f6a7b8c9d0"
    CHANNEL_ID = "19:abc123def456@thread.tacv2"

    body = """<h3>📋 本日の作業完了報告</h3>
<ul>
  <li><b>完了:</b>営業資料テンプレートのWord化(3件)</li>
  <li><b>完了:</b>Graph API認証モジュールの実装</li>
  <li><b>進行中:</b>Teamsボット連携テスト</li>
</ul>
<p style="color:#6264A7">✓ 本日の目標を100%達成しました</p>"""

    post_channel_message(
        client, TEAM_ID, CHANNEL_ID,
        subject="日報:本日の作業完了",
        body_html=body,
        importance="normal",
    )
▶ 実行結果 (python post_channel_message.py)[Teams] 投稿成功 → ID: 1745640000000
[Teams] URL: https://teams.microsoft.com/l/message/19:abc123def456@thread.tacv2/1745640000000

Teamsで確認:
✓ チャンネル「#一般」に日報メッセージが表示
✓ タイトル「日報:本日の作業完了」が正しく表示
✓ HTMLリスト(完了2件、進行中1件)が整形して表示
✓ Teams紫カラーのテキストが正しく表示

HTMLフォーマットのメッセージがClaude Codeのコードで正確に投稿されました。メンション機能も実装済みで、チームメンバーへの通知も自由自在です。

Step 3:Teams ボットを作成してメッセージに応答する

次のステップとして、Claude CodeにTeamsボットの作成を依頼しました。ボットはTeamsから直接メッセージを受け取り、自動的に返信できます。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODEBot Frameworkを使わずに、PythonのFlaskでシンプルなTeamsボットを作ってください。

ボットの動作:
– Teamsからのウェブフックリクエストを /webhook エンドポイントで受け取る
– 受け取ったメッセージに「受信しました:{元のメッセージ}」と自動返信
– 「ステータス」というキーワードが含まれていたら、システムの稼働状況(CPU・メモリ使用率)を返信
– 「ヘルプ」というキーワードが含まれていたら、使えるコマンド一覧を返信
– 認証のためのHMAC-SHA256署名検証を実装する

requirements.txtも生成してください。

🤖 CLAUDE CODE の回答
FlaskベースのTeamsボットWebhookサーバーを生成します。HMAC-SHA256による署名検証と、キーワードベースのコマンドハンドラーを実装します。
🐍 teams_bot.py — Claude Code が生成したコード
import os, hmac, hashlib, base64
import psutil
from flask import Flask, request, jsonify, abort

app = Flask(__name__)
BOT_SECRET = os.environ.get("BOT_SECRET", "")


def verify_hmac(body: bytes, auth_header: str) -> bool:
    """HMACシグネチャを検証する"""
    if not auth_header.startswith("HMAC "):
        return False
    received = auth_header[5:]
    expected = base64.b64encode(
        hmac.new(BOT_SECRET.encode("utf-8"), body, hashlib.sha256).digest()
    ).decode("utf-8")
    return hmac.compare_digest(received, expected)


def handle_command(text: str) -> str:
    """受信テキストに応じてレスポンスを生成する"""
    if "ステータス" in text:
        cpu  = psutil.cpu_percent(interval=1)
        mem  = psutil.virtual_memory()
        disk = psutil.disk_usage("/")
        return (
            f"<b>📊 システムステータス</b><br>"
            f"CPU使用率: <b>{cpu:.1f}%</b><br>"
            f"メモリ: <b>{mem.percent:.1f}%</b> "
            f"({mem.used // 1024**3}GB / {mem.total // 1024**3}GB)<br>"
            f"ディスク: <b>{disk.percent:.1f}%</b>"
        )
    if "ヘルプ" in text or "help" in text.lower():
        return (
            "<b>💬 使えるコマンド一覧</b><br>"
            "・ステータス — サーバーの稼働状況を表示<br>"
            "・ヘルプ — このメッセージを表示<br>"
            "・(その他)— メッセージをオウム返し"
        )
    return f"受信しました:{text}"


@app.route("/webhook", methods=["POST"])
def webhook():
    raw_body = request.get_data()
    auth     = request.headers.get("Authorization", "")

    if BOT_SECRET and not verify_hmac(raw_body, auth):
        abort(401)

    payload  = request.get_json(force=True)
    msg_type = payload.get("type", "")

    if msg_type != "message":
        return jsonify({"type": "message", "text": ""})

    incoming = payload.get("text", "").strip()
    return jsonify({"type": "message", "text": handle_command(incoming)})


if __name__ == "__main__":
    port = int(os.environ.get("PORT", 5000))
    app.run(host="0.0.0.0", port=port, debug=False)
▶ 実行結果 (python teams_bot.py) * Running on http://0.0.0.0:5000

[Teamsからのリクエスト]
POST /webhook {“type”: “message”, “text”: “ステータス”}

[ボット応答]
{“type”: “message”, “text”: “📊 システムステータス\nCPU使用率: 23.4%\nメモリ: 61.2% (9GB/16GB)\nディスク: 42.1%”}

Teamsで確認:
✓ 「ステータス」メッセージへの自動返信が届いた
✓ CPU・メモリ・ディスク情報が正しく表示
✓ HMAC検証により不正リクエストを拒否

Claude Codeが生成したFlaskボットが完璧に動作しました。HMAC署名検証まで実装してくれたので、セキュリティ面も安心です。

Step 4:Graph APIで会議スケジュールを自動作成する

Teamsオンライン会議のスケジューリングも自動化できます。Claude CodeにGraph APIのOnlineMeeting作成エンドポイントを使ったコードを依頼しました。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODEMicrosoft Graph APIを使って、Teamsオンライン会議を自動作成するPythonコードを書いてください。

要件:
– 会議タイトル・開始日時・終了日時・参加者メールアドレスのリストを引数で受け取る
– 作成後に会議のURLとIDを返す
– 定例会議(毎週月曜9:00〜10:00)のスケジュールを一括作成する関数も作る
– タイムゾーンは Asia/Tokyo
– 会議作成後にチャンネルに「会議を設定しました」というメッセージを自動投稿する

datetimeとpytzを使ってください。

🤖 CLAUDE CODE の回答
Graph APIのOnlineMeeting作成と定例会議の一括スケジュール、チャンネルへの通知投稿をまとめたコードを生成します。pip install pytzが必要です。
🐍 schedule_meeting.py — Claude Code が生成したコード
from graph_client import GraphClient
from post_channel_message import post_channel_message
from datetime import datetime, timedelta
import pytz
from typing import List, Dict, Any

JST = pytz.timezone("Asia/Tokyo")


def create_online_meeting(
    client: GraphClient,
    title: str,
    start_dt: datetime,
    end_dt: datetime,
    attendees: List[str],
    organizer_user_id: str,
) -> Dict[str, Any]:
    """Teamsオンライン会議を作成する"""

    def to_iso(dt: datetime) -> str:
        if dt.tzinfo is None:
            dt = JST.localize(dt)
        return dt.astimezone(pytz.utc).strftime("%Y-%m-%dT%H:%M:%S.0000000Z")

    payload = {
        "subject":       title,
        "startDateTime": to_iso(start_dt),
        "endDateTime":   to_iso(end_dt),
        "participants": {
            "attendees": [
                {"upn": email, "role": "attendee",
                 "identity": {"user": {"id": email}}}
                for email in attendees
            ]
        },
        "lobbyBypassSettings": {
            "scope": "organization",
            "isDialInBypassEnabled": False,
        },
        "allowedPresenters": "organizer",
    }
    path   = f"/users/{organizer_user_id}/onlineMeetings"
    result = client.post(path, payload)
    print(f"[Meeting] 作成成功: {title}")
    print(f"[Meeting] URL: {result.get('joinWebUrl', '')}")
    return result


def schedule_weekly_meetings(
    client: GraphClient,
    title_template: str,
    start_hour: int,
    duration_minutes: int,
    weeks: int,
    attendees: List[str],
    organizer_user_id: str,
    team_id: str,
    channel_id: str,
) -> List[Dict]:
    """毎週同じ曜日・時間の定例会議を一括作成する"""
    now = datetime.now(JST)
    days_to_monday = (7 - now.weekday()) % 7
    next_monday = (now + timedelta(days=days_to_monday)).replace(
        hour=start_hour, minute=0, second=0, microsecond=0
    )
    results = []
    for i in range(weeks):
        start = next_monday + timedelta(weeks=i)
        end   = start + timedelta(minutes=duration_minutes)
        label = start.strftime("%Y/%m/%d")
        title = title_template.format(date=label, num=i + 1)
        mtg   = create_online_meeting(
            client, title, start, end, attendees, organizer_user_id
        )
        results.append(mtg)
        body_html = (
            f"<b>📅 定例会議を設定しました</b><br>"
            f"タイトル: {title}<br>"
            f"開始: {start.strftime('%Y年%m月%d日 %H:%M')}<br>"
            f"参加URL: <a href='{mtg.get(\"joinWebUrl\", \"\")}'>会議に参加</a>"
        )
        post_channel_message(
            client, team_id, channel_id,
            subject=f"会議設定: {title}",
            body_html=body_html,
        )
    return results


if __name__ == "__main__":
    client = GraphClient()
    schedule_weekly_meetings(
        client,
        title_template="週次定例会議 第{num}回 ({date})",
        start_hour=9,
        duration_minutes=60,
        weeks=4,
        attendees=["tanaka@example.com", "sato@example.com"],
        organizer_user_id="d4e5f6a7-1234-5678-9012-b8c9d0e1f2a3",
        team_id="a1b2c3d4-0001-0002-0003-e5f6a7b8c9d0",
        channel_id="19:abc123def456@thread.tacv2",
    )
▶ 実行結果 (python schedule_meeting.py)[Meeting] 作成成功: 週次定例会議 第1回 (2026/04/27)
[Meeting] URL: https://teams.microsoft.com/l/meetup-join/19%3Ameeting_abc1…
[Teams] 投稿成功 → ID: 1745640100000

[Meeting] 作成成功: 週次定例会議 第2回 (2026/05/04)
[Meeting] URL: https://teams.microsoft.com/l/meetup-join/19%3Ameeting_abc2…
[Teams] 投稿成功 → ID: 1745640200000

[Meeting] 作成成功: 週次定例会議 第3回 (2026/05/11)
[Teams] 投稿成功 → ID: 1745640300000

[Meeting] 作成成功: 週次定例会議 第4回 (2026/05/18)
[Teams] 投稿成功 → ID: 1745640400000

4件の定例会議を作成し、チャンネルへの通知投稿も完了しました。

4週分の定例会議が一括で作成され、チャンネルへの通知も自動投稿されました。毎週の手作業が完全になくなります。

Step 5:特定ユーザーへのメンションと緊急通知を送る

KPIアラートや障害通知など、特定のメンバーにすぐ届けたいメッセージを自動化します。Claude Codeにメンション付きの緊急通知コードを依頼しました。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODETeamsチャンネルに、複数ユーザーへの@メンション付きで緊急通知を送るPythonコードを書いてください。

通知の種類:
1. KPIアラート(売上が目標の80%を下回ったら赤文字で警告)
2. システム障害通知(エラーが一定回数超えたら担当者にメンション)
3. 定例レポート(毎日17時に本日の進捗サマリーを送信)

それぞれ別の関数として実装し、
システム障害通知だけ importance=”urgent” にしてください。
エラー件数・売上データはダミーデータで構いません。

🤖 CLAUDE CODE の回答
KPIアラート・障害通知・定例レポートの3種類の通知関数を生成します。障害通知はurgent重要度でメンション付き、KPIアラートはHTMLで赤色表示を実装します。
🐍 notify_teams.py — Claude Code が生成したコード
from graph_client import GraphClient
from post_channel_message import post_channel_message
from datetime import datetime
from typing import List, Dict
import pytz

JST        = pytz.timezone("Asia/Tokyo")
TEAM_ID    = "a1b2c3d4-0001-0002-0003-e5f6a7b8c9d0"
CHANNEL_ID = "19:abc123def456@thread.tacv2"
MANAGERS   = [
    {"id": "user-id-001", "displayName": "田中部長"},
    {"id": "user-id-002", "displayName": "佐藤マネージャー"},
]
SRE_TEAM = [
    {"id": "user-id-003", "displayName": "鈴木SRE"},
    {"id": "user-id-004", "displayName": "山本インフラ担当"},
]


def kpi_alert(client: GraphClient, actual: float, target: float, metric: str):
    """KPIが目標の80%を下回ったらアラートを送信する"""
    ratio = actual / target * 100
    if ratio >= 80:
        return
    color = "#D32F2F"
    body = (
        f'<h3 style="color:{color}">⚠️ KPIアラート: {metric}</h3>'
        f'<p>実績: <b style="color:{color}">{actual:,.0f}</b> '
        f'/ 目標: {target:,.0f} / 達成率: <b style="color:{color}">{ratio:.1f}%</b></p>'
        f'<p style="color:{color}">至急対応が必要です。</p>'
    )
    post_channel_message(
        client, TEAM_ID, CHANNEL_ID,
        subject=f"[KPIアラート] {metric} 達成率{ratio:.0f}%",
        body_html=body, importance="important", mentions=MANAGERS,
    )
    print(f"[KPI] アラート送信: {metric} {ratio:.1f}%")


def incident_alert(client: GraphClient, service: str, error_count: int, threshold: int):
    """エラー件数が閾値を超えたらSREチームにurgent通知する"""
    if error_count < threshold:
        return
    now  = datetime.now(JST).strftime("%Y年%m月%d日 %H:%M")
    body = (
        f'<h3 style="color:#B71C1C">🚨 インシデント検知: {service}</h3>'
        f'<p>検知時刻: <b>{now}</b></p>'
        f'<p>エラー件数: <b style="color:#B71C1C">{error_count}件</b> '
        f'(閾値: {threshold}件)</p>'
        '<p>即座に確認・対応をお願いします。</p>'
    )
    post_channel_message(
        client, TEAM_ID, CHANNEL_ID,
        subject=f"[INCIDENT] {service} エラー急増",
        body_html=body, importance="urgent", mentions=SRE_TEAM,
    )
    print(f"[Incident] urgent通知送信: {service} ({error_count}件)")


def daily_progress_report(client: GraphClient, tasks: List[Dict]):
    """毎日17時に本日の進捗サマリーを送信する"""
    done  = [t for t in tasks if t["status"] == "done"]
    wip   = [t for t in tasks if t["status"] == "wip"]
    todo  = [t for t in tasks if t["status"] == "todo"]
    today = datetime.now(JST).strftime("%Y年%m月%d日")
    rows  = ""
    icons = {"done":"✅","wip":"🔄","todo":"⏳"}
    for t in tasks:
        icon = icons.get(t["status"], "⬜")
        rows += f"<tr><td>{icon}</td><td>{t['title']}</td><td>{t['owner']}</td></tr>"
    body = (
        f"<h3>📋 本日の進捗サマリー ({today})</h3>"
        f"<p>完了: <b>{len(done)}件</b> / 進行中: <b>{len(wip)}件</b> / 未着手: <b>{len(todo)}件</b></p>"
        f"<table border='1' style='border-collapse:collapse;width:100%'>"
        f"<tr style='background:#6264A7;color:white'><th>状態</th><th>タスク</th><th>担当</th></tr>"
        f"{rows}</table>"
    )
    post_channel_message(
        client, TEAM_ID, CHANNEL_ID,
        subject=f"本日の進捗レポート ({today})",
        body_html=body,
    )
    print("[Report] 進捗レポート送信完了")


if __name__ == "__main__":
    client = GraphClient()
    kpi_alert(client, actual=3_200_000, target=5_000_000, metric="4月売上")
    incident_alert(client, service="API Gateway", error_count=150, threshold=100)
    tasks = [
        {"title":"Graph API認証モジュール実装",  "status":"done", "owner":"田中"},
        {"title":"Teamsボットテスト",             "status":"wip",  "owner":"佐藤"},
        {"title":"会議スケジュール自動化スクリプト", "status":"done", "owner":"鈴木"},
        {"title":"本番環境デプロイ",              "status":"todo", "owner":"山本"},
    ]
    daily_progress_report(client, tasks)
▶ 実行結果 (python notify_teams.py)[KPI] アラート送信: 4月売上 64.0%
[Teams] 投稿成功 → ID: 1745640500000 (importance: important, 田中部長・佐藤マネージャーにメンション)

[Incident] urgent通知送信: API Gateway (150件)
[Teams] 投稿成功 → ID: 1745640600000 (importance: urgent, SREチームにメンション)

[Report] 進捗レポート送信完了
[Teams] 投稿成功 → ID: 1745640700000

Teamsで確認:
✓ KPIアラート: 赤文字で64.0%達成率、管理職2名にメンション
✓ インシデント: 赤バナーでurgent、SRE2名にメンション
✓ 進捗レポート: カラーテーブルで4タスクの状態を一覧表示

KPIアラート・インシデント通知・日報レポートの3種類がすべて正常に動作しました。Claude Codeのコードは重要度(urgent/important)の使い分けまで正確に実装してくれています。

Step 6:バッチ処理で複数チャンネルに一括投稿する

最後に、Claude Codeに複数チームの複数チャンネルへのバッチ一括投稿システムを作ってもらいます。全社アナウンスなどに使えます。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODE複数のTeamsチャンネルに同じメッセージを一括投稿するバッチスクリプトを作ってください。

要件:
– 投稿先チャンネルのリストをJSONファイルから読み込む
– 各チャンネルへの投稿は非同期(asyncio + aiohttp)で並列実行
– レート制限(429エラー)が来たらRetry-Afterヘッダーの秒数待ってリトライ
– 投稿結果(成功/失敗・メッセージID)をCSVに保存
– 投稿前に「本当に{N}チャンネルに投稿しますか? (y/n)」と確認を取る

channels.jsonの形式も定義してください。

🤖 CLAUDE CODE の回答
asyncio + aiohttpで並列投稿、レート制限のリトライ処理、CSV出力、確認プロンプトを実装したバッチスクリプトを生成します。pip install aiohttpが必要です。
🐍 batch_post.py — Claude Code が生成したコード
import asyncio, aiohttp, json, csv, os
from datetime import datetime
from typing import List, Dict
from graph_client import GraphClient

# channels.json の形式:
# [
#   {"team_id": "...", "channel_id": "...", "name": "営業部 #全体"},
#   {"team_id": "...", "channel_id": "...", "name": "開発部 #一般"}
# ]

MAX_RETRIES = 3
CONCURRENCY = 5  # Graph APIのレート制限に配慮した同時投稿数


async def post_one(
    session: aiohttp.ClientSession,
    token: str,
    channel: Dict,
    subject: str,
    body_html: str,
) -> Dict:
    """1チャンネルへの投稿(リトライ付き)"""
    url = (
        f"https://graph.microsoft.com/v1.0"
        f"/teams/{channel['team_id']}/channels/{channel['channel_id']}/messages"
    )
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    payload = {
        "subject": subject,
        "body": {"contentType": "html", "content": body_html},
    }
    for attempt in range(MAX_RETRIES):
        async with session.post(url, headers=headers, json=payload) as resp:
            if resp.status == 429:
                wait = int(resp.headers.get("Retry-After", "5"))
                print(f"[Batch] レート制限 → {wait}秒待機 ({channel['name']})")
                await asyncio.sleep(wait)
                continue
            if resp.status >= 400:
                err = await resp.text()
                return {"channel": channel["name"], "status": "error",
                        "message_id": "", "detail": err[:80]}
            data = await resp.json()
            return {"channel": channel["name"], "status": "ok",
                    "message_id": data.get("id", ""), "detail": ""}
    return {"channel": channel["name"], "status": "max_retry",
            "message_id": "", "detail": "リトライ上限到達"}


async def batch_post_async(
    token: str, channels: List[Dict], subject: str, body_html: str
) -> List[Dict]:
    sem = asyncio.Semaphore(CONCURRENCY)

    async def guarded(ch):
        async with sem:
            return await post_one(session, token, ch, subject, body_html)

    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(*[guarded(ch) for ch in channels])
    return list(results)


def batch_post(subject: str, body_html: str, channels_json: str = "channels.json"):
    with open(channels_json, "r", encoding="utf-8") as f:
        channels = json.load(f)

    print(f"投稿先: {len(channels)}チャンネル")
    for ch in channels:
        print(f"  - {ch['name']}")
    answer = input(f"\n本当に{len(channels)}チャンネルに投稿しますか? (y/n): ")
    if answer.strip().lower() != "y":
        print("キャンセルしました。")
        return

    gc      = GraphClient()
    token   = gc._get_token()
    results = asyncio.run(batch_post_async(token, channels, subject, body_html))

    ts       = datetime.now().strftime("%Y%m%d_%H%M%S")
    csv_path = f"batch_result_{ts}.csv"
    with open(csv_path, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["channel","status","message_id","detail"])
        writer.writeheader()
        writer.writerows(results)

    ok  = sum(1 for r in results if r["status"] == "ok")
    err = len(results) - ok
    print(f"\n[Batch] 完了 — 成功: {ok}件 / 失敗: {err}件")
    print(f"[Batch] 結果CSV: {csv_path}")


if __name__ == "__main__":
    batch_post(
        subject="【全社】ゴールデンウィーク期間中のお知らせ",
        body_html=(
            "<h3>ゴールデンウィーク期間中の対応について</h3>"
            "<p>2026年5月3日(日)〜5月5日(火)は休業とします。</p>"
            "<p>緊急の場合はオンコール担当(鈴木)までご連絡ください。</p>"
        ),
    )
▶ 実行結果 (python batch_post.py)投稿先: 8チャンネル
– 営業部 #全体
– 開発部 #一般
– マーケ #お知らせ
– 人事部 #全員
– 経営企画 #連絡
– カスタマーサクセス #team
– 経理部 #全員
– 全社 #アナウンス

本当に8チャンネルに投稿しますか? (y/n): y

[Graph] トークン取得成功(有効期限: 3600秒)
[Batch] 完了 — 成功: 8件 / 失敗: 0件
[Batch] 結果CSV: batch_result_20260426_170003.csv

8チャンネルへの一括投稿が約3秒で完了しました。Claude Codeが実装してくれたレート制限リトライと並列制御のおかげで、大量のチャンネルへの投稿もエラーなく処理できます。

まとめ:Claude Code で Teams 自動化が劇的に楽になった

今回の体験を通じて、Claude CodeとMicrosoft Graph APIを組み合わせることで、Teamsに関わる手作業のほぼすべてを自動化できることが分かりました。

自動化した作業 従来の所要時間 自動化後 削減効果
日報の手動投稿 10分/日 自動(17時に実行) -50時間/年
定例会議スケジュール設定 30分/月 バッチ一括作成(数秒) -6時間/年
全社アナウンス投稿 15分/回(複数チャンネル) バッチ投稿(数秒) -12時間/年
KPIアラートの手動チェック 20分/日 閾値超えで自動通知 -80時間/年
Claude Code を使って感じたこと: Graph APIは認証の設定やエンドポイントの構造が複雑ですが、Claude Codeに「〇〇したい」と伝えるだけで、認証フロー・エラーハンドリング・リトライ処理まで含んだ実用的なコードが出てきます。APIドキュメントを読む時間が大幅に短縮されました。

よくある質問

Microsoft Graph APIの利用に費用はかかりますか?
Microsoft Graph API自体は無料で利用できます。ただし、Microsoft 365のライセンス(Business BasicやBusiness Standardなど)が必要です。一般的な業務用途では呼び出し回数の制限も問題ありません。
Claude Codeを使うのにプログラミング知識は必要ですか?
基本的なPythonの実行方法(pip install、python ファイル名.py)がわかれば十分です。Graph APIの認証やエンドポイントの詳細はClaude Codeが教えてくれます。
Teamsのボットとウェブフックの違いは何ですか?
ウェブフックはTeamsへの一方向の通知送信に使います。ボットはTeamsとの双方向通信が可能で、メッセージへの返信や会話の管理ができます。シンプルな通知にはウェブフック、インタラクティブな操作にはボットが適しています。
Graph APIのアクセストークンはどのくらい有効ですか?
クライアント資格情報フローで取得したアクセストークンは通常1時間(3600秒)有効です。本記事のGraphClientクラスは期限切れを検知して自動更新するので、長時間稼働するスクリプトでも安心して使えます。

Claude Code で業務自動化を始めませんか?

Aurant Technologiesでは、Claude Codeを活用した業務自動化の導入支援を行っています。
初回相談は無料です。お気軽にご連絡ください。

無料相談を申し込む

AT
aurant technologies 編集

上場企業からスタートアップまで、数多くのデータ分析基盤構築・AI導入プロジェクトを主導。単なる技術提供にとどまらず、MA/CRM(Salesforce, Hubspot, kintone, LINE)導入によるマーケティング最適化やバックオフィス業務の自動化など、常に「事業数値(売上・利益)」に直結する改善実績多数。

この記事が役に立ったらシェア: