【非エンジニアのためのClaude/ClaudeCodeシリーズ】日報をAIに書かせてみた〜Google Apps Script(GAS)×Googleカレンダー×Claude×Slackで自動化した話〜

【非エンジニアのためのClaude/ClaudeCodeシリーズ】日報をAIに書かせてみた〜Google Apps Script(GAS)×Googleカレンダー×Claude×Slackで自動化した話〜

2026.04.06

はじめに

みなさん、日報書いてますか?

毎日19時に「さて日報を…」と思いながら、今日の予定を頭の中で振り返って、それをSlackにポチポチ打ち込む。そのルーティン、地味にしんどくないですか?

私はそう感じていました。

というわけで、Googleカレンダーの予定をClaudeに読み込ませて、日報を自動生成してSlackに投稿する仕組みを作ってみました。エンジニアじゃない私でも、なんとか動かせたのでその記録を残しておきます。


なぜGASを使うことになったのか

最初は「Claudeに日報を書いてもらえばいいか」と軽く考えていました。

毎日Claudeを開いて「今日の日報を書いて」と頼む、それだけのつもりでした。でもすぐに気づきます。Claudeは自分が立ち上げておかないと動いてくれない、当たり前ですが。

「自動化したい」のに「毎日Claudeを開く」では、結局手動です。日報を書く手間が、Claudeを開く手間に変わっただけ。それじゃ意味がない。

そこで調べた結果たどり着いたのが、Google Apps Script(GAS)をスケジューラーとして使うという方法です。GASはGoogleのサーバー上で動くので、PCの電源を切っていても、ブラウザを閉じていても、時間になれば自動で処理を実行してくれます。

「Claudeへの指示をGASに組み込んで、GASが毎日19時に動かす」という構造にすれば、完全自動化が実現できる。そういう発想で作ったのが今回のシステムです。


作ったものの全体像

毎日19時に、こんなことが自動で起きます。

  1. Googleカレンダーから当日の予定を取得
  2. Claudeが予定をもとに日報文章を生成
  3. 指定のSlackチャンネルのスレッドへ自動投稿

PCの電源を切っていても動きます。土日は自動スキップ。最高です。


実際の出力イメージ

セットアップの前に、まず「何ができるか」を見てもらった方がモチベーションが上がると思うので先に載せます。

こんな日報がSlackに自動投稿されます

山田 太郎の日報|2026年4月6日(月)

■ サマリー
本日は新規顧客との商談2件と、社内の週次MTGに参加しました。
午後は提案資料の修正に集中できた一日でした。

■ 業務内容
- 10:00〜11:00:株式会社○○様 初回商談(オンライン)
- 13:00〜14:00:週次営業MTG
- 14:00〜16:00:株式会社△△様 提案資料修正・送付
- 16:00〜17:00:株式会社□□様 フォローアップ商談

■ 案件対応
- 株式会社○○様:初回ヒアリング完了。先方のニーズは〜。次回は詳細提案を予定。
- 株式会社△△様:提案資料をv2に更新して送付済み。来週中に返答予定。

■ 所感
商談が続いた一日でしたが、どちらも前向きなリアクションをいただけました。
明日は資料作成に集中したいと思います。

カレンダーに登録された予定の情報だけから、ここまで書いてくれます。Googleカレンダーの予定説明欄にGoogle Docsのリンクを貼っておくと、議事録の内容まで反映してくれます。


必要なもの(全体像)

必要なもの 取得先 備考
Claude APIキー console.anthropic.com 管理者への連絡が必要な場合あり
Slack User Token(xoxp-) api.slack.com/apps 管理者承認が必要
Googleアカウント お手持ちのもの カレンダーと同じアカウント
SlackチャンネルID Slackのチャンネル詳細から Cから始まる英数字
スレッド検索キーワード 週次スレッドの親メッセージから 月曜に投稿される親メッセージ内の文字列

所要時間の目安は約1時間(管理者承認の待ち時間を除く)です。


セットアップ手順

STEP 1|Claude APIキーを取得する

  1. console.anthropic.comにアクセスしてサインアップ

Anthropicのコンソールにアクセスし、アカウントを作成します。Googleアカウントでもサインアップできます。

  1. 左メニューの「API Keys」→「Create Key」をクリック

  2. 名前を入力してキーを発行

名前は何でもOKです(例:daily-report)。発行されたキーはsk-ant-から始まる文字列です。必ずこの画面でコピーしておいてください。画面を閉じると二度と表示されません。

コスト感:日報1回の生成コストは約2〜5円(Claude Sonnet使用時)。1ヶ月毎日使っても100円前後なので安心です。

ハマりポイント①:組織アカウントに紐づけようとしたら「追加の権限が必要です」と表示されました。その場合は管理者に「開発者アクセス」の付与を依頼する必要があります。個人アカウントで作れば不要です。


STEP 2|Slack Appを作成してUser Tokenを取得する

ここが一番手順が多いです。順番通りに進めれば大丈夫です。

2-1. Slack Appを新規作成する

  1. api.slack.com/appsにアクセス
  2. 右上の「Create New App」をクリック
  3. 「From scratch」を選択
  4. App Nameに日報Botと入力、Workspaceに自分のワークスペース(Classmethod)を選択
  5. 「Create App」をクリック

2-2. Incoming Webhookを設定する

  1. 左メニューの「Incoming Webhooks」をクリック
  2. 「Activate Incoming Webhooks」をONに切り替える
  3. 下部の「Add New Webhook to Workspace」をクリック
  4. 日報を投稿したいチャンネルを選択して「許可する」
  5. 表示されたhttps://hooks.slack.com/services/...のURLをコピーして保存しておく

2-3. Bot Token Scopesを設定する

  1. 左メニューの「OAuth & Permissions」をクリック
  2. 「Scopes」のセクションまでスクロール
  3. 「Bot Token Scopes」の「Add an OAuth Scope」から以下の3つを追加する
Scope 用途
channels:history スレッドの検索
chat:write メッセージの投稿
groups:history プライベートチャンネル対応

2-4. User Token Scopesを設定する

同じページの「User Token Scopes」から以下の2つを追加する

Scope 用途
channels:history スレッドTS(タイムスタンプ)の自動検索
chat:write 自分として投稿

2-5. ワークスペースにインストールする(管理者承認が必要)

  1. 「OAuth & Permissions」ページ上部の「Request to Workspace Install」をクリック
  2. Slack管理者に承認を依頼する

管理者への連絡文例
「日報自動化のSlack App『日報Bot』のインストール申請を出しました。承認をお願いできますか?チャンネルへのメッセージ投稿のみに使用します。」

  1. 承認後、xoxp-から始まるUser OAuth Tokenをコピーして保存する

ハマりポイント②:管理者承認が下りるまで時間がかかる場合があります。STEP 2を終えたらすぐ申請を出して、承認を待ちながら次のSTEPを進めるのがおすすめです。


STEP 2.5|チャンネルIDとスレッド検索キーワードを確認する

チャンネルIDの確認方法

  1. Slackで日報を投稿したいチャンネルを開く
  2. チャンネル名をクリックしてチャンネル詳細を表示
  3. 詳細の一番下に表示される英数字(Cから始まる)をコピー

例:C01ABCD2EFGのような文字列です。

ハマりポイント③:最初チャンネル名をそのまま入力してしまってエラーになりました。#generalなどのチャンネル名ではなく、チャンネルIDが必要です。必ず詳細から確認しましょう。

スレッド検索キーワードの確認方法

このシステムは、毎週月曜に投稿される週次スレッドの親メッセージを自動検索して、そこに返信する形で日報を投稿します。

  1. 日報を投稿したいスレッドの親メッセージを確認する(毎週月曜に投稿されているもの)
  2. そのメッセージに含まれる特徴的な文字列をメモする

特徴的な文字列の例:

  • BotのID(U0XXXXXXXのような形式)
  • 特定のユーザーグループへのメンション
  • メッセージ冒頭の固定フレーズ(「今週の日報スレッド」など)

STEP 3|Google Apps Script(GAS)を設定する

3-1. GASプロジェクトを作成する

  1. script.google.comにアクセス(Googleアカウントでログイン済みであればそのまま開けます)
  2. 左上の「新しいプロジェクト」をクリック
  3. プロジェクト名を「日報自動化」に変更(画面上部のプロジェクト名をクリックすると変更できます)

3-2. コードを貼り付ける

  1. 開いたエディタ画面に最初から書かれているコードを全て削除する
  2. 以下のコードをコピーして貼り付ける
// ============================================================
// 日報自動生成・Slack投稿スクリプト
// ============================================================

// ① 設定エリア(★の項目を編集してください)
var MY_NAME = "氏名をここに入力";               // ★ 自分の名前
var MY_ROLE = "担当者";                          // ★ 自分の職種(例:"営業担当者"、"エンジニア")
var CLAUDE_API_KEY = "sk-ant-ここにAPIキーを入力"; // ★ STEP1で取得
var SLACK_USER_TOKEN = "xoxp-ここにUser Tokenを入力"; // ★ STEP2で取得
var SLACK_CHANNEL_ID = "Cxxxxxxxxxx";            // ★ STEP2.5で取得したチャンネルID
var THREAD_SEARCH_KEYWORD = "ここに検索キーワードを入力"; // ★ STEP2.5で確認したキーワード
var CLAUDE_MODEL = "claude-sonnet-4-6";
var TIMEZONE = "Asia/Tokyo";

function runDailyReport() {
  var day = new Date().getDay();
  if (day === 0 || day === 6) { Logger.log("土日のためスキップ"); return; }
  Logger.log("=== 日報自動生成開始 ===");
  var events = getTodayEvents();
  Logger.log("取得した予定数: " + events.length);
  if (events.length === 0) { Logger.log("本日の予定がないため終了"); return; }
  var memos = getEventMemos(events);
  var reportText = generateReportWithClaude(events, memos);
  var threadTs = findLatestMondayThread();
  if (!threadTs) { Logger.log("対象スレッドが見つかりませんでした"); return; }
  postToSlack(threadTs, reportText);
  Logger.log("=== 日報投稿完了 ===");
}

function getTodayEvents() {
  var today = new Date();
  var startDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
  var endDay = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
  var calendar = CalendarApp.getDefaultCalendar();
  var rawEvents = calendar.getEvents(startDay, endDay);
  var results = [];
  for (var i = 0; i < rawEvents.length; i++) {
    var e = rawEvents[i];
    results.push({
      title: e.getTitle(),
      startTime: Utilities.formatDate(e.getStartTime(), TIMEZONE, "HH:mm"),
      endTime: Utilities.formatDate(e.getEndTime(), TIMEZONE, "HH:mm"),
      location: e.getLocation() || "",
      description: e.getDescription() || "",
      attachmentIds: extractDocIds(e.getDescription() || ""),
    });
  }
  return results;
}

function extractDocIds(description) {
  var regex = /docs\.google\.com\/document\/d\/([a-zA-Z0-9_-]+)/g;
  var ids = [];
  var match;
  while ((match = regex.exec(description)) !== null) { ids.push(match[1]); }
  return ids;
}

function getEventMemos(events) {
  var memos = [];
  for (var i = 0; i < events.length; i++) {
    var event = events[i];
    if (!event.attachmentIds || event.attachmentIds.length === 0) continue;
    for (var j = 0; j < event.attachmentIds.length; j++) {
      var docId = event.attachmentIds[j];
      try {
        var doc = DocumentApp.openById(docId);
        var content = doc.getBody().getText();
        if (content.trim().length < 50) continue;
        memos.push({ eventTitle: event.title, docTitle: doc.getName(), content: content.substring(0, 3000) });
      } catch (err) { Logger.log("議事録取得エラー (" + docId + "): " + err.toString()); }
    }
  }
  return memos;
}

function generateReportWithClaude(events, memos) {
  var today = Utilities.formatDate(new Date(), TIMEZONE, "yyyy年MM月dd日(E)");
  var eventsText = "";
  for (var i = 0; i < events.length; i++) {
    var e = events[i];
    var loc = e.location ? "(" + e.location + ")" : "";
    eventsText += "- " + e.startTime + "〜" + e.endTime + ":" + e.title + loc + "\n";
  }
  var memosText = "議事録なし";
  if (memos.length > 0) {
    memosText = "";
    for (var j = 0; j < memos.length; j++) {
      var m = memos[j];
      memosText += "【" + m.eventTitle + "の議事録】\n" + m.content + "\n\n---\n\n";
    }
  }
  var prompt = "あなたは" + MY_NAME + "(" + MY_ROLE + ")の日報作成アシスタントです。\n"
    + "以下の情報から、" + MY_NAME + "の視点でSlack投稿用の日報を作成してください。\n\n"
    + "## 本日の日付\n" + today + "\n\n"
    + "## 本日のカレンダー予定\n" + eventsText + "\n"
    + "## 議事録\n" + memosText + "\n"
    + "## 日報作成の指示\n"
    + "冒頭に「" + MY_NAME + "の日報」と日付を明記する\n"
    + "1. サマリー(2〜3文)\n2. 業務内容(時系列)\n3. 案件対応\n4. 所感\n\n"
    + "Slack markdown形式、500〜800文字程度";
  var payload = JSON.stringify({
    model: CLAUDE_MODEL, max_tokens: 2000,
    messages: [{ role: "user", content: prompt }],
  });
  var options = {
    method: "post", contentType: "application/json",
    headers: { "x-api-key": CLAUDE_API_KEY, "anthropic-version": "2023-06-01" },
    payload: payload, muteHttpExceptions: true,
  };
  var response = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", options);
  var result = JSON.parse(response.getContentText());
  if (result.error) throw new Error("Claude API エラー: " + JSON.stringify(result.error));
  return result.content[0].text;
}

function findLatestMondayThread() {
  var today = new Date();
  var dayOfWeek = today.getDay();
  var daysToMon = (dayOfWeek === 0) ? 6 : dayOfWeek - 1;
  var lastMonday = new Date(today);
  lastMonday.setDate(today.getDate() - daysToMon);
  lastMonday.setHours(10, 0, 0, 0);
  var oldest = String(Math.floor(lastMonday.getTime() / 1000) - 3600);
  var latest = String(Math.floor(lastMonday.getTime() / 1000) + 3600);
  var url = "https://slack.com/api/conversations.history"
    + "?channel=" + SLACK_CHANNEL_ID + "&oldest=" + oldest + "&latest=" + latest + "&limit=20";
  var response = UrlFetchApp.fetch(url, {
    headers: { "Authorization": "Bearer " + SLACK_USER_TOKEN },
    muteHttpExceptions: true,
  });
  var result = JSON.parse(response.getContentText());
  if (!result.ok) throw new Error("Slack API エラー: " + result.error);
  var messages = result.messages || [];
  for (var i = 0; i < messages.length; i++) {
    if (messages[i].text && messages[i].text.indexOf(THREAD_SEARCH_KEYWORD) !== -1) return messages[i].ts;
  }
  return null;
}

function postToSlack(threadTs, message) {
  var payload = JSON.stringify({
    channel: SLACK_CHANNEL_ID, text: message, thread_ts: threadTs,
  });
  var response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
    method: "post", contentType: "application/json",
    headers: { "Authorization": "Bearer " + SLACK_USER_TOKEN },
    payload: payload, muteHttpExceptions: true,
  });
  var result = JSON.parse(response.getContentText());
  if (!result.ok) throw new Error("Slack投稿エラー: " + result.error);
  Logger.log("投稿成功: " + result.message.ts);
}

function setupTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for (var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() === "runDailyReport") ScriptApp.deleteTrigger(triggers[i]);
  }
  ScriptApp.newTrigger("runDailyReport").timeBased().everyDays(1).atHour(19).create();
  Logger.log("トリガー設定完了:毎日19時に runDailyReport が実行されます");
}

3-3. 設定値を書き換える

コードの上部にある★マークの6項目を自分の情報に書き換えます。

変数名 入力する値
MY_NAME 自分の名前(例:「山田 太郎」)
MY_ROLE 自分の職種(例:「営業担当者」「エンジニア」「PM」)
CLAUDE_API_KEY STEP1で取得したsk-ant-から始まるキー
SLACK_USER_TOKEN STEP2で取得したxoxp-から始まるトークン
SLACK_CHANNEL_ID STEP2.5で取得したCから始まるチャンネルID
THREAD_SEARCH_KEYWORD STEP2.5で確認したキーワード

書き換えたら、右上の「保存」ボタン(またはCmd+S / Ctrl+S)で保存します。

ハマりポイント④:コードを貼り付けた後、変数名をSLACK_WEBHOOK_URLのままにしていてエラーになりました(古いコードとの混在が原因)。コードを貼る前に既存コードを全選択して削除してから作業するのが安全です。


STEP 4|テスト実行とトリガー設定

4-1. 手動テスト実行

  1. エディタ上部のドロップダウンで「runDailyReport」を選択
  2. 「実行」ボタンをクリック
  3. 初回は「Googleアカウントの権限承認」画面が表示されるので「許可」をクリック(2回目以降は不要)
  4. 画面下部の「実行ログ」に=== 日報投稿完了 ===と表示されれば成功!

Slackを確認して、指定のチャンネルに投稿されていればOKです。

4-2. 自動トリガーを設定する(毎日19時)

  1. ドロップダウンで「setupTrigger」を選択
  2. 「実行」ボタンをクリック
  3. 実行ログに「トリガー設定完了:毎日19時にrunDailyReportが実行されます」と表示されれば完了

これで設定完了です!以降は何もしなくてOK。PCを閉じていても毎日19時に自動投稿されます。


まとめ

  • セットアップ時間:約1時間(管理者承認の待ち時間を除く)
  • ランニングコスト:月100円前後
  • 毎日の日報作業時間:ほぼゼロ

一番のハードルは管理者承認を待つことかもしれません(笑)。でも一度設定してしまえば、あとは完全放置で日報が飛んでいきます。

日報を毎日書いている方、ぜひ試してみてください。


【非エンジニアのためのClaude/ClaudeCodeシリーズ】の他の記事はこちら

この記事をシェアする

関連記事