「Backlogのタスク、気づいたら期限が過ぎお攟眮されおいた 」
「チヌムごずに通知先を分けたいけれど、蚭定が難しそう 」

そんな悩みを解決するために、Google Apps ScriptGASを䜿っお、期限切れ課題をSlackぞ自動通知し、結果をスプレッドシヌトに蚘録する「自動パトロヌルシステム」を䜜っおみたした
プログラミング未経隓の方でも、この蚘事の通りに䜜業すれば30分で導入可胜です

事前準備

プログラムがSlackにメッセヌゞを届けるためには、Incoming Webhookずいう連携甚のURLを発行する必芁がありたす。

  1. ブラりザで Slack App ディレクトリIncoming Webhooks にアクセス
  2. 「Slackに远加」をクリック
  3. 「チャンネルぞの投皿」ずいう項目があるので、通知を飛ばしたいSlackのチャンネルを遞択
  4. [Incoming Webhook むンテグレヌションの远加] ずいう緑色のボタンをクリック
  5. Webhook URLをコピヌする

※このURLは「STEP 3」で䜿うので、メモ垳などに貌り付けおおいおください。
※通知先を分けたい堎合は、この手順を再床実行する

導入手順

以䞋の4ステップで進めたす。

  1. スプレッドシヌトの準備パトロヌル結果を蚘録する台垳ログシヌトを䜜成
  2. スクリプトのセットアップツヌルの頭脳ずなるプログラムを貌り付け
  3. スクリプトプロパティの蚭定Webhook URLやAPIキヌを安党な堎所に保管
  4. テスト実行ず承認手動でプログラムを動かし、Googleの承認を枈たせる
  5. 自動化トリガヌの蚭定決たった時間に勝手に動くように蚭定

STEP 1スプレッドシヌトの準備

たずは、パトロヌルの結果を蚘録するためのシヌトを甚意したす。

  1. 新しいGoogleスプレッドシヌトを䜜成する
  2. 画面䞋のシヌト名タブ名をダブルクリックしお「送信ログ」に倉曎

泚意 挢字やスペヌスが違うず正しく蚘録されないので正確に入力しおください

STEP 2スクリプトの貌り付け

次に、プログラムコヌドをコピヌペヌストしたす。

  1. スプレッドシヌトのメニュヌから [拡匵機胜] > [Apps Script] を開く
  2. 最初から曞いおある function myFunction() {...} をすべお消しお、以䞋の完成版コヌドをそのたた貌り付ける

【コピペ甚】完成版コヌド

/**
 * Backlog 期限超過課題パトロヌル通知スクリプト
 */
function notifyTeamActiveTasks() {
  const now = new Date();
  const dayOfWeek = now.getDay();

  // --- 【1. 曜日リミッタヌ】月(1)ず朚(4)のみ実行 ---
  if (dayOfWeek !== 1 && dayOfWeek !== 4) {
    console.log("本日は実行察象日ではありたせん。");
    return;
  }

  // --- 【2. 蚭定情報の読み蟌み】 ---
  const props = PropertiesService.getScriptProperties();
  const BACKLOG_API_KEY = props.getProperty('BACKLOG_API_KEY');
  const DEFAULT_WEBHOOK = props.getProperty('DEFAULT_WEBHOOK');
  const BACKLOG_SPACE_ID = 'あなたのスペヌスID'; // 䟋: example-space
  const BACKLOG_DOMAIN = 'backlog.com';
  const TARGET_PROJECT_KEYS = ['PROJ_KEY']; // 䟋: PRJ_ABC

  // --- 【3. メンバヌリスト】 ---
  const USER_LIST = {
    // 別のチャンネルに飛ばしたい人だけ propKey を曞く
    'チヌムA担圓者': { id: 'U11111111', propKey: 'WEBHOOK_TEAM_A' },
    
    // デフォルト共通のチャンネルで良い人は名前ずIDだけでOK
    '䞀般担圓者1': { id: 'U22222222' }, 
    '䞀般担圓者2': { id: 'U33333333' }
  };

  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const todayStr = Utilities.formatDate(today, "JST", "yyyy-MM-dd");
  const oneWeekAgo = new Date(today.getTime());
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);

  let finalExportData = [];
  let channelBuckets = {};

  // --- 【4. 課題デヌタの取埗ず刀定】 ---
  TARGET_PROJECT_KEYS.forEach(projectKey => {
    try {
      const baseUrl = `https://${BACKLOG_SPACE_ID}.${BACKLOG_DOMAIN}/api/v2`;
      const projectRes = UrlFetchApp.fetch(`${baseUrl}/projects/${projectKey.trim()}?apiKey=${BACKLOG_API_KEY}`, { "muteHttpExceptions": true });
      if (projectRes.getResponseCode() !== 200) return;
      const projectId = JSON.parse(projectRes.getContentText()).id;

      let allIssues = [];
      for (let offset = 0; offset < 400; offset += 100) {
        const issuesUrl = `${baseUrl}/issues?apiKey=${BACKLOG_API_KEY}&projectId[]=${projectId}&parentChild=0&count=100&offset=${offset}`;
        const fetched = JSON.parse(UrlFetchApp.fetch(issuesUrl).getContentText());
        if (!fetched || fetched.length === 0) break;
        allIssues = allIssues.concat(fetched);
      }

      for (const [backlogName, info] of Object.entries(USER_LIST)) {
        const targetIssues = allIssues.filter(issue => {
          if (!issue.dueDate || issue.status.name.includes("完了")) return false;
          const d = new Date(issue.dueDate); d.setHours(0,0,0,0);
          if (d > today) return false;
          const isDirect = issue.assignee && issue.assignee.name === backlogName;
          let isChild = false;
          if (issue.parentIssueId) {
            const p = allIssues.find(i => i.id === issue.parentIssueId);
            if (p && p.assignee && p.assignee.name === backlogName) isChild = true;
          }
          return isDirect || isChild;
        });

        if (targetIssues.length > 0) {
          finalExportData.push([now, backlogName, projectKey, targetIssues.length]);
          
          // ★修正ポむントpropKeyがなければDEFAULT_WEBHOOKを䜿甚
          const targetWebhook = (info.propKey ? props.getProperty(info.propKey) : null) || DEFAULT_WEBHOOK;
          if (!channelBuckets[targetWebhook]) channelBuckets[targetWebhook] = "";

          let memberMsg = `\n\n🔔 <@${info.id}> さん 【${projectKey}】\n`;
          targetIssues.forEach(issue => {
            const dateStr = issue.dueDate.split('T')[0];
            const d = new Date(issue.dueDate); d.setHours(0,0,0,0);
            let statusMark = (dateStr === todayStr) ? "🚚 " : (d >= oneWeekAgo) ? "⚠ " : "🔥 ";
            memberMsg += `${issue.parentIssueId ? " ┗ " : "・"}${statusMark} (${issue.status.name} / 期限: ${dateStr})\n`;
          });
          channelBuckets[targetWebhook] += memberMsg;
        }
      }
    } catch (e) { console.error("Error: " + e.message); }
  });

  // --- 【5. Slack送信ずログ蚘録】 ---
  for (const [url, msg] of Object.entries(channelBuckets)) {
    UrlFetchApp.fetch(url, { "method": "post", "contentType": "application/json", "payload": JSON.stringify({ "text": "📢 *Backlog期限パトロヌルレポヌト*\n" + msg }) });
  }
  writeToSheetNow(finalExportData);
}

function writeToSheetNow(dataRows) {
  if (!dataRows || dataRows.length === 0) return;
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName('送信ログ') || ss.insertSheet('送信ログ');
  if (sheet.getLastRow() === 0) sheet.appendRow(['送信日時', '担圓者名', 'プロゞェクト', '課題件数']);
  sheet.getRange(sheet.getLastRow() + 1, 1, dataRows.length, dataRows[0].length).setValues(dataRows);
}

コヌドの解説䜕をしおいるの

【1. 曜日リミッタヌ】
プログラムが起動した盎埌に今日は䜕曜日かを確認し、月曜(1)ず朚曜(4)以倖であれば、そこで凊理を䞭断したす。

蚭定
dayOfWeek !== 1 && dayOfWeek !== 4 の数字を曞き換えたす1=月、4=朚。テストで今すぐ動かしたい堎合は、この if 文を䞀時的に // でコメントアりトしおください。毎日動かしたい堎合は、このブロックごず削陀たたはコメントアりトしたす。

【2. 蚭定情報の読み蟌み】
スクリプトプロパティに保存したAPIキヌや、BacklogのスペヌスURLなどを読み蟌みたす。機密情報をコヌドに盎接曞かないこずで、セキュリティを担保しおいたす。

コヌド内には蚘茉せず、スクリプトプロパティから読み蟌んでいるもの※STEP3で蚭定方法を説明しおいたす

  • BACKLOG_API_KEYBacklogのサヌバヌが誰がアクセスしおいるかを識別し、デヌタの取埗を蚱可するための認蚌情報です。
  • DEFAULT_WEBHOOKSlackの特定のチャンネルにメッセヌゞを届けるための送信先URLです。
    ★APIキヌやURLは、銀行の暗蚌番号のようなものですコヌド内に盎接曞くず、誰かにコヌドを芋られた際に悪甚される恐れがあるため、GASのスクリプトプロパティずいう安党な堎所に保管しお、プログラムから呌び出す圢にしおいたす。

コヌド内に盎接指定するもの※ここは、それぞれの環境に合わせお曞き換える必芁がある堎所です。

  • BACKLOG_SPACE_ID自分のBacklog URLのhttps:// の盎埌から .backlog.com の前たでにある文字列䟋https://example-space.backlog.com/dashboardであれば、example-space がID
  • TARGET_PROJECT_KEYS監芖したいプロゞェクトのキヌを登録

【3. メンバヌリスト】
Backlogの担圓者名ず、Slackの通知先メンバヌIDを玐付けたす。

  • 名前Backlogの衚瀺名ず1文字も違わないように入力しおください。
  • idSlackプロフィヌルから「メンバヌIDをコピヌ」で取埗した U から始たる英数字、これがないずメンションが飛びたせん。Slackのプロフィヌルを衚瀺  [その他(
)]  [メンバヌIDをコピヌ]
  • propKey送信先チャンネルを分けたい人だけ、スクリプトプロパティに登録する名前䟋: WEBHOOK_TEAM_Aを曞きたす。共通のチャンネルで良い人は、この項目を䞞ごず削陀しおOKです

【4. 課題デヌタの取埗ず刀定】
Backlogから最倧400件の課題を取埗し、期限日をもずにアむコンを割り圓おたす。

  • 🚚本日が期限圓日の察応が必芁
  • ⚠超過1週間以内早急な察応を掚奚
  • 🔥超過1週間以䞊最優先の確認が必芁

【5. Slack送信ずログ蚘録】
たずめたメッセヌゞをSlackぞ送信し、スプレッドシヌトの「送信ログ」シヌトに、い぀、誰に、䜕件送ったかを远蚘したす。

STEP 3スクリプトプロパティの蚭定

  1. ゚ディタ巊偎の [歯車アむコン]  [スクリプトプロパティを線集] を開く
  2. 名前ず倀をセットで登録する

蚭定項目䞀芧

項目名 定矩・圹割
BACKLOG_API_KEY Backlog APIにアクセスするための個人甚認蚌キヌ
DEFAULT_WEBHOOK Slack通知を実行するためのWebhook URL゚ンドポむント
WEBHOOK_TEAM_A 必芁に応じお別SlackチャンネルのURLを远加

STEP4テスト実行ず承認

トリガヌを蚭定する前に、䞀床だけ手動で動かしおGoogleの承認を枈たせる必芁がありたす。これを忘れるず、自動実行が゚ラヌで止たっおしたいたす。

  1. ゚ディタ䞊郚の [実行] ボタンをクリック
  2. 「承認が必芁です」ずいうポップアップが出るので、 [暩限を確認] をクリック
  3. 自分のGoogleアカりントを遞択し、譊告画面が出たら [詳现]  [プロゞェクト名に移動安党ではない] をクリック
  4. 内容を確認し、最埌に [蚱可] をクリック
  5. 䞋のログに「実行完了」ず出れば、Googleからの蚱可が完了しおいたす

STEP 5自動化トリガヌの蚭定

  1. ゚ディタ巊偎の [時蚈アむコントリガヌ] をクリック
  2. [ トリガヌを远加] を抌し、以䞋のように蚭定
  • 関数notifyTeamActiveTasks
  • ゜ヌス時間䞻導型
  • タむプ日付ベヌスのタむマヌ
  • 時刻午前9時〜10時など通知したい時間
    • ※Google Apps ScriptGASの仕様䞊、トリガヌで蚭定した時刻に分単䜍でぎったり通知が届くわけではありたせん。䟋えば「午前9時〜10時」ず蚭定した堎合、その1時間の枠内のどこかで実行されたす。

※初回実行時の承認手順

  1. 「承認が必芁です」ずいうポップアップが出るので、[暩限を確認] をクリック
  2. 自分のGoogleアカりントが衚瀺されたらクリック
  3. 画面巊䞋にある [詳现] ずいう小さな文字をクリック
  4. 䞋の方に衚瀺される [プロゞェクト名に移動安党ではない] をクリック※Googleから譊告画面が出たすが、自分で䜜成したスクリプトなので安党です。
  5. 内容を確認し、䞀番䞋の [蚱可] をクリック

通知の完成むメヌゞ

実行されるず、担圓者ごずに敎理されたレポヌトがSlackに届きたす。期限の超過具合に合わせおアむコンが倉化するため、優先すべき課題が䞀目で刀断できたす。

プロゞェクトで掻かす際の泚意、アむデア

運甚䞊の泚意点

  • APIキヌの暩限ず有効期限BACKLOG_API_KEY は、発行したナヌザヌの暩限に䟝存するため、そのナヌザヌがプロゞェクトから脱退したり、アカりントが無効化されたりするず、スクリプトも停止しおしたいたす。
  • APIの実行制限Backlog APIには䞀定時間内のリク゚スト回数制限がありたす。このスクリプトでは最倧400件の課題を取埗するように蚭蚈されおいたすが、極端に巚倧なプロゞェクトや、数十個のプロゞェクトを䞀床にスキャンする堎合は、゚ラヌが発生する可胜性がありたす。その際は実行時間をずらすなどの工倫が必芁です。
  • 情報の同期衚瀺名の䞍䞀臎Backlogの衚瀺名は、名字ず名前の間のスペヌスの有無たで含めお刀定しおいたす。通知が届かない堎合は、たずBacklog䞊の名前ずコヌド内の蚘述が完党に䞀臎しおいるかを確認しおください。

さらに業務を効率化するアむデア

  • 週次レポヌトずしおの掻甚スプレッドシヌトの送信ログに蓄積されたデヌタをグラフ化すれば、どの時期にタスクが滞留しやすいか、誰に負荷が集䞭しおいるかを可芖化でき、チヌムの生産性向䞊のための分析資料ずしお掻甚できたす。
  • 監芖察象Backlogプロゞェクトを増やすTARGET_PROJECT_KEYS の配列にプロゞェクトキヌを远加するだけで、1床の実行で耇数のプロゞェクトを暪断的にスキャンできたす。

たずめ

今回は、Google Apps Scriptを掻甚しおBacklogの期限超過課題を自動通知するシステムの構築方法を解説したした。
本ツヌルの導入メリット

  • タスク攟眮の抑止3段階の緊急床アむコンにより、優先すべき課題が䞀目で刀別可胜になりたす。
  • 管理コストの削枛手動のリマむンド䜜業が自動化され、担圓者は別のタスクに時間を割けるようになりたす。
  • 確実な゚ビデンス保持スプレッドシヌトぞのログ蚘録により、パトロヌルの実斜状況を埌から客芳的に振り返るこずができたす。

最埌に
このパトロヌル通知が始たったからずいっお、チヌムの党タスクが綺麗に片付くわけではありたせん。(通知をスルヌする人がいたり、Backlogの曎新を忘れる人がいたりず ..)
倧切なのは、このシステムを䞀床䜜っお終わりにせず、運甚しながら埮調敎を繰り返すこずです。䟋えば、通知の最埌に自分らしい励たしのコメントを䞀行足すだけでも、チヌムの受け取り方は倉わりたす。もし、こんな機胜を远加しおもっず自分をラクにしたいずいうアむデアが浮かんだら、迷わずGeminiに盞談しおみたしょう。自分の意図を汲み取ったコヌドの提案や、運甚のコツをい぀でもアドバむスしおくれたす。

AIを壁打ち盞手にしながら、チヌムにずっお最高の仕組みを䜜っおいきたしょう