忘れない努力より、気付ける仕組みを作る
社内では課題管理ツールとしてBacklogを利用しており、日々のタスク管理に活用しています。
プロジェクトを進めていると、
- 今日が期限だった…
- 気付いたら期限を過ぎていた…
- 対応するつもりだったのに忘れてしまった…
という経験は、一度はあるのではないでしょうか。
もちろん、期限を守ることは社会人として当たり前です。
一方で、「忘れないように気を付ける」「毎朝その日に対応すべきタスクを確認する」といった個人の努力だけでは、忙しい日々の中で見落としが起きてしまうこともあります。
そこで今回は、Backlog API・Google Apps Script(GAS)・Google Chat を組み合わせ、担当している未完了課題の期限を毎朝自動で通知する仕組みを作成しました。
目指したのは、「忘れないように頑張る」のではなく、意識しなくても自然に期限が目に入る環境です。
なぜ期限切れは発生するのか
期限切れの課題が発生してしまう理由は、必ずしも担当者のモチベーションだけではないと思います。
例えば、
- 複数案件を並行して進めている
- 優先順位が日々変わる
- Backlogを頻繁に開く習慣がない
- 新規課題が増え、一覧の中に埋もれてしまう
など、誰にでも起こり得る要因があります。
私自身も、そんな「うっかり」をこれまで何度も経験してきました。
だからといって、「もっと注意しよう」と意識するだけでは、忙しい日々の中で限界があります。
そこで、忘れない努力をするのではなく、忘れても自然に気付ける仕組みを作ることにしました。
普段の業務の流れの中で自然と期限を意識できる環境があれば、期限切れになる前に対応しやすくなり、今抱えているタスクだけでなく、その先の予定も立てやすくなります。
普段使っているツールと連携する
新しいルールを追加しても、それが日々の行動に馴染むまでには少なからずコストがかかります。
そのため今回は、普段から無意識に近いレベルで使っているツールに、Backlogの期限情報を届ける形にしました。
私が業務開始時に必ず確認していて、かつ見落としにくいと感じているのは、
- Google カレンダー
- Google Chat
の2つです。
すでに日常業務の中で開いている場所に情報が届けば、「Backlogのダッシュボードを見に行く」という行動を意識的に増やさなくても、期限が自然と意識に上がりやすくなります。
1. Googleカレンダーとの標準連携
Backlogには、課題やマイルストーンを iCalendar(ICS)形式 で配信する標準機能が用意されています。
このURLをGoogleカレンダーへ登録することで、Backlogの課題期限をGoogleカレンダー上で確認できるようになります。
設定手順
- Backlogの 個人設定 > プライベートアドレスの設定 で「使用する」にチェックを入れて保存します。
-
ダッシュボード へ戻り、画面右側にある カレンダー取込 からURLを取得します。
→カレンダー取込
取得したURLをGoogleカレンダーへ登録するだけで、Backlogの課題期限が予定として表示されるようになります。
この標準機能を利用するだけでも、Backlogの課題期限を普段の予定とあわせて確認できるようになるため便利です。まだ利用していない方にはオススメです。
2. Google Chatへ毎朝通知する
さらに今回は、Google Apps Script(GAS)を利用して、Google Chatへ毎朝通知する仕組みを作成しました。
実際にGoogle Chatへ届いた通知は以下のようになります。
Google Chatに届いたBacklog期限通知
なぜGoogle Apps Script(GAS)を使ったのか
今回の仕組みでは、Google Apps Script(GAS)を利用しました。
理由は、Google ChatやGoogleカレンダーなど、普段使っているGoogle Workspace系のツールと相性がよく、時間主導型トリガーを使えば定期実行も簡単に設定できるためです。
また、サーバーを別途用意する必要がなく、今回のような「毎朝決まった時間にBacklog APIを確認して通知する」程度の処理であれば、GASだけで十分実現できます。
小さな業務改善ツールを素早く作るには、ちょうど良いと感じました。
システム構成
今回の構成はシンプルです。
Backlog APIで自分が担当している未完了課題を取得し、GAS側で期限ごとに分類します。
その結果をGoogle ChatのWebhookへ送信し、毎朝決まった時間に通知されるようにしています。
Backlog
│
│ Backlog APIで課題を取得
▼
Google Apps Script
│
│ 期限ごとに分類してWebhook送信
▼
Google Chat
通知内容
通知は重要度ごとに分類し、優先順位が一目で分かるようにしています。
-
🔥 期限超過
-
⚠️ 本日期限
-
⏰ 3日以内
-
📅 来週期限の課題
朝Google Chatを開くだけで、その日に確認すべき課題を自然と把握できます。
実装時に意識したポイント
ここでは、特に工夫したポイントを紹介します。
-
期限に応じた分類ロジック
-
スクリプトプロパティを活用した設定情報の分離
-
土日は通知しない
1. 期限に応じた分類ロジック
取得した課題は、現在日付との差分を計算し、
-
期限超過
-
本日期限
-
3日以内
-
来週期限
の4つに分類しています。
issues.forEach(issue => {
const dueDate = new Date(issue.dueDate);
dueDate.setHours(0,0,0,0);
const diffDays = Math.floor(
(dueDate - today) / (1000 * 60 * 60 * 24)
);
if(diffDays < 0){
result.overdue.push(issue);
}else if(diffDays === 0){
result.today.push(issue);
}else if(diffDays <= NOTICE_DAYS){
result.withinNoticeDays.push(issue);
}
// 来週の課題は別途判定
});
2. スクリプトプロパティを活用した設定情報の分離
今回のツールでは、BacklogのAPIキーやGoogle ChatのWebhook URLなどの設定情報をコードへ直接記述せず、GASのスクリプトプロパティから取得する構成にしました。
APIキーなどの機密情報をソースコードへ含めないことで、安全に管理できるようにしています。
また、環境ごとの差分をスクリプトプロパティへ切り出したことで、コードを変更することなく設定値だけで利用環境を切り替えられるため、結果として再利用しやすい構成になっています。
function getConfig() {
const props = PropertiesService.getScriptProperties();
return {
space: props.getProperty('BACKLOG_SPACE'),
apiKey: props.getProperty('BACKLOG_API_KEY'),
chatWebhookUrl: props.getProperty('CHAT_WEBHOOK_URL'),
};
}
設定手順
- Google Apps Script(GAS)のエディタ画面左側にある プロジェクトの設定 を開きます。
- 画面下部にある スクリプトプロパティ > スクリプトプロパティを追加 から、以下の3つの環境変数を登録します。
| キー | 内容 |
|---|---|
| BACKLOG_SPACE | Backlogのスペースドメイン(例:xxxx.backlog.com) |
| BACKLOG_API_KEY | Backlogで発行したAPIキー |
| CHAT_WEBHOOK_URL | Google Chatで発行したWebhook URL |
設定値だけ変更すればそのまま利用できるため、プロジェクトが変わってもコードを修正する必要がありません。
3. 土日は通知しない
Google Apps Scriptの時間主導トリガーでは、曜日指定が柔軟ではありません。
そこで、スクリプト側で曜日を判定し、土日は処理を終了するようにしました。
function runNotification() {
const day = new Date().getDay();
if(day === 6 || day === 7){
return;
}
// 通知処理
}
トリガーは毎日実行しつつ、休日だけ通知を抑止しています。
全体のコード
今回紹介したコード全体は以下になります。
詳細な実装が気になる方は展開してご覧ください。
全体のコードはこちら
/**
* Backlog期限通知Bot
*
* 自分が担当しているBacklog課題のうち、
* 期限超過・本日期限・3日以内・来週期限の課題を
* Google Chatへ通知する。
*/
const NOTICE_DAYS = 3;
/**
* 通知対象ステータス
* 1: 未対応
* 2: 処理中
* 3: 処理済み
* 4: 完了 は通知対象外
*/
const OPEN_STATUS_IDS = [1, 2, 3];
function getConfig() {
const props = PropertiesService.getScriptProperties();
return {
space: props.getProperty('BACKLOG_SPACE'),
apiKey: props.getProperty('BACKLOG_API_KEY'),
chatWebhookUrl: props.getProperty('CHAT_WEBHOOK_URL'),
};
}
function getMyUser(config) {
const url =
`https://${config.space}/api/v2/users/myself?apiKey=${encodeURIComponent(config.apiKey)}`;
const response = UrlFetchApp.fetch(url);
return JSON.parse(response.getContentText());
}
function getMyIssues(config, userId) {
const params = [
`apiKey=${encodeURIComponent(config.apiKey)}`,
`assigneeId[]=${userId}`,
...OPEN_STATUS_IDS.map(id => `statusId[]=${id}`),
`count=100`,
];
const url =
`https://${config.space}/api/v2/issues?${params.join("&")}`;
const response = UrlFetchApp.fetch(url);
return JSON.parse(response.getContentText());
}
function filterDueIssues(issues) {
return issues.filter(issue => issue.dueDate);
}
function classifyDueIssues(issues) {
const today = new Date();
today.setHours(0,0,0,0);
// 今週の月曜日
const thisMonday = new Date(today);
thisMonday.setDate(today.getDate() - ((today.getDay() + 6) % 7));
// 来週の月曜日
const nextMonday = new Date(thisMonday);
nextMonday.setDate(thisMonday.getDate() + 7);
// 来週の日曜日
const nextSunday = new Date(nextMonday);
nextSunday.setDate(nextMonday.getDate() + 6);
const result = {
overdue: [],
today: [],
withinNoticeDays: [],
nextWeek: []
};
issues.forEach(issue => {
const dueDate = new Date(issue.dueDate);
dueDate.setHours(0,0,0,0);
const diffDays = Math.floor(
(dueDate - today) / (1000 * 60 * 60 * 24)
);
if(diffDays < 0){
result.overdue.push(issue);
}else if(diffDays === 0){
result.today.push(issue);
}else if(diffDays <= NOTICE_DAYS){
result.withinNoticeDays.push(issue);
}
if(dueDate >= nextMonday && dueDate <= nextSunday){
result.nextWeek.push(issue);
}
});
return result;
}
function buildChatMessage(result, space){
const sections = [
{
icon: "🔥",
title: "期限超過",
issues: result.overdue
},
{
icon: "⚠️",
title: "本日期限",
issues: result.today
},
{
icon: "⏰️",
title: `${NOTICE_DAYS}日以内`,
issues: result.withinNoticeDays
},
{
icon: "📅",
title: "来週期限の課題",
issues: result.nextWeek
}
];
let message = "📣 Backlog期限通知\n\n";
sections.forEach(section => {
message += `${section.icon} ${section.title}\n`;
if(section.issues.length === 0){
message += "なし\n\n";
return;
}
section.issues.forEach(issue => {
message += `・${issue.issueKey}\n`;
message += ` ${issue.summary}\n`;
message += ` 期限:${issue.dueDate.substring(0,10)}\n`;
message += ` https://${space}/view/${issue.issueKey}\n\n`;
});
});
return message.replace(/\n+$/, '');
}
function postToGoogleChat(config, message){
UrlFetchApp.fetch(config.chatWebhookUrl,{
method : "post",
contentType : "application/json; charset=UTF-8",
payload : JSON.stringify({
text : message
})
});
}
function runNotification() {
const day = new Date().getDay();
// 土曜日(6)・日曜日(7)は終了
if (day === 6 || day === 7) {
return;
}
try {
const config = getConfig();
const me = getMyUser(config);
const issues = getMyIssues(config, me.id);
const dueIssues = filterDueIssues(issues);
const result = classifyDueIssues(dueIssues);
const message = buildChatMessage(result, config.space);
postToGoogleChat(config, message);
} catch (e) {
Logger.log("===== ERROR =====");
Logger.log(e);
throw e;
}
}
実際に使ってみて
実際に運用してみると、期限切れになってから気付くことがほとんどなくなりました。
これまでは、Backlogを開いて課題一覧を確認しないと期限が近い課題に気付きにくい場面がありました。今回の仕組みでは、普段使っているGoogle Chatに通知が届くため、業務開始時の流れの中で自然に確認できます。
また、期限超過や本日期限だけでなく、3日以内・来週期限の課題も確認できるため、直前になって慌てる前に準備しやすくなりました。
小さな通知ではありますが、「今抱えているタスクを把握する」ための入口として役立っています。
まとめ
今回は、Backlogの課題期限をGoogle Chatへ自動通知する仕組みを作成しました。
意識したのは、個人の注意力だけに頼るのではなく、普段使っているツールの中で自然に気付ける状態を作ることです。
業務では、複数案件の並行や優先順位の変更など、さまざまな要因で課題の期限を見落としてしまうことがあります。だからこそ、期限切れになってから気付くのではなく、事前に気付ける仕組みがあるだけで、タスク管理の負担はかなり軽くなります。
今回紹介したBotは小規模なプログラムですが、日々の業務を少しだけ楽にしてくれるツールになりました。
同じようにBacklogでタスク管理をしている方や、業務改善に興味がある方の参考になれば幸いです。


