こんにちは、アイレットMSPチームの沢田です。

私は新しく加入したメンバーの教育・オンボーディングを担当するグループに所属しています。そのなかで地味に大変だったのが「アカウント申請(※)」の作業でした。今回は、その申請作業を Google Apps Script(GAS) で自動化してみたので、その仕組みをご紹介します。

※ ここで少しだけ、私たちの「アカウント申請」業務について補足します。新しいメンバーが入ると、業務で使う各種ツール・サービスのアカウントを用意する必要があります。ツールやアカウントごとに管理者(申請先)が異なるため、申請は一括では済まず、管理者ごとに個別に申請しなければなりません(これまで30件近くを、すべて手作業で対応していました)。私たちのチームでは、この申請を「Backlog(プロジェクト管理ツール)での申請課題起票」や「Slackでの関係者連絡」といった形で行っています。

何が大変だったか

この前提のうえで、これまでのアカウント申請には、こんな課題がありました。

  • 依頼の方法が「Backlogでの課題起票」や「Slack連絡」などに分かれていて、「この依頼は前回どっちだったっけ?」と、都度過去の依頼例を遡って確認する必要があった
  • 申請先が、各案件+社内インフラ向けの依頼で合わせて 30件弱 ある
  • 起票先ごとにプロジェクトも課題種別も担当者もバラバラ
  • 案件ごとに申請文(テンプレート)が違う
  • 起票後はSlackで本人とチームにメンションで連携したい
  • 申請の抜け漏れは許されない

毎回手作業で起票していると時間がかかるうえ、「あの申請、出し忘れてた」「これはどっちの方法で依頼するんだっけ」が起きやすい状況でした。

そこで、スプレッドシートに対象メンバーを追加するだけで、必要な課題が一括で起票され、Slack通知まで飛ぶ仕組みを作りました。コード自体は、生成AI(Gemini)に相談しながら書いています。

作ったもの(ざっくり概要)

スプレッドシートの MemberMaster シートに対象者(氏名・メール・SlackユーザーID)を追加して実行すると、こんなことが自動で起こります。

  1. 申請状況を管理する「親課題」を1件作成
  2. 案件ごとに、課題を一括起票(自社の管理プロジェクトの場合は親課題の子課題として、別プロジェクトの案件は単独課題として)
  3. 案件ごとのテンプレートに、対象者の氏名・メール・SlackIDを差し込んで本文を自動生成
  4. 完了後、本人+チームにSlackでメンション通知
  5. 実行履歴をログシートに記録

「申請の抜け漏れ防止」と「起票作業の省力化」が、いっぺんに解決できました。

仕組み(全体の流れ)

使っている技術は Googleスプレッドシート + GAS + Backlog API + Slack(Incoming Webhook) です。

スプレッドシートの構成

データはすべてスプレッドシート側で管理し、GASは「読みに行くだけ」にしています。案件やテンプレを足したいときも、コードを触らずシートを編集すればOKにしています。

シート名 役割
MemberMaster 申請対象メンバー(氏名・メール・SlackID)。一番下の行が対象者になる
ProjectMaster 起票する案件の一覧。案件名・起票先プロジェクトID・課題種別ID・担当者・通知先を管理
TemplateMaster 案件ごとの課題本文テンプレート。{名前} などを差し込む
NotificationMaster Slack通知のCcに入れるメンバーのID一覧
RequestLog 実行履歴の記録用

GASコードの要点(抜粋)

全文は長いので、ポイントになる部分だけ抜粋します。
※プロジェクトIDや通知先IDなどの固有値は伏せています(実物はスクリプト内/スプレッドシートで管理)。

① 機密情報はコードに直書きしない

APIキーやWebhook URLはコードに書かず、スクリプトプロパティから読み込むようにしました。

const props = PropertiesService.getScriptProperties();
const BACKLOG_API_KEY  = props.getProperty("BACKLOG_API_KEY");
const SLACK_WEBHOOK_URL = props.getProperty("SLACK_WEBHOOK_URL");

② 親課題を作成し、その課題IDを子課題に紐づける

まず申請状況を管理する「親課題」を1件作成し、返ってきた課題IDを受け取ります。その後の子課題ループで、親と同じプロジェクトだけ子課題としてぶら下げ、別プロジェクトの案件は単独課題として起票します。

// 親課題を作成し、その課題ID・URLを受け取る
const pRes = UrlFetchApp.fetch(backlogApiUrl, {
  method: "post",
  payload: buildFormUrlEncoded(parentPayload)
});
const pIssue = JSON.parse(pRes.getContentText());
const parentIssueId = pIssue.id.toString();          // ★親ID
const pUrl = `https://${SPACE_NAME}.backlog.jp/view/${pIssue.issueKey}`;

// 子課題ループの中での紐づけ判定
if (targetProjectId === parentProjectId) {
  // 親と同じプロジェクトなら「子課題」として親にぶら下げる
  childPayload["parentIssueId"] = parentIssueId;
}
// 別プロジェクトは parentIssueId を付けず、単独課題として起票

③ 案件ごとにループして一括起票+件名の出し分け

ProjectMaster を1行ずつ読み、案件ごとに課題を起票します。案件によって件名のルールが違うので、そこだけ分岐させています。

// 基本の件名
let childSummary = `【アカウント申請】${projectName}`;
// 特定のプロジェクトは、案件名をそのまま件名にする
if (targetProjectId === "<対象プロジェクトのID>") {
  childSummary = projectName;
}
// 一部の案件だけは特別な件名にする
if (projectName === "<対象案件名>") {
  childSummary = "<個別の件名>";
}

④ テンプレートに対象者情報を差し込む

TemplateMaster の本文に置いてある {名前} などを、実際の値に置換しています。

childDescription = template
  .replace(/{名前}/g, memberName)
  .replace(/{メールアドレス}/g, memberEmail)
  .replace(/{SlackユーザーID}/g, slackUserId);

⑤ 配列パラメータをBacklogが受け取れる形に変換する

通知先(notifiedUserId[])のような「複数の値」をBacklog APIに渡すには、「同じキーを複数回並べる」形式に変換する必要があります。これを担うのがこの関数です。これが無いと複数の通知先を正しく渡せないようでした。

// 配列値(notifiedUserId[]など)は「同じキーを複数回並べる」形式に変換する
const buildFormUrlEncoded = (payload) => {
  const parts = [];
  for (const key in payload) {
    if (Array.isArray(payload[key])) {
      payload[key].forEach(val =>
        parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(val)));
    } else {
      parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(payload[key]));
    }
  }
  return parts.join('&');
};

⑥ 1件失敗しても止まらないようにする

案件ごとの起票は try/catch で囲み、1件エラーになっても全体を止めず、次の案件に進むようにしています。

try {
  UrlFetchApp.fetch(backlogApiUrl, { method: "post", payload: buildFormUrlEncoded(childPayload) });
} catch (e) {
  console.error(`案件 [${projectName}] の起票に失敗: ${e.message}`);
}

⑦ Slack通知(本人+Cc)

NotificationMaster から集めたIDでCcを組み立て、本人をメンションして通知します。

const slackPayload = {
  text: `<@${slackUserId}> Cc:${teamMentions}\n\n` +
        `アカウント発行申請の各案件の起票が完了しました!\n` +
        `全体管理用 親課題: ${pUrl}`
};
UrlFetchApp.fetch(SLACK_WEBHOOK_URL, {
  method: "post",
  contentType: "application/json",
  payload: JSON.stringify(slackPayload)
});

苦労した点・工夫したところ

案件ごとに件名・本文がバラバラ問題
案件ごとに件名ルールが違ったり、本文テンプレが全然違ったりするのが厄介でした。これはシート(ProjectMaster / TemplateMaster)側に逃がして、コードを汚さずに対応できるようにしています。

起票先ごとに課題種別も担当者も違う問題
起票先のプロジェクトごとに課題種別やBacklog上の担当者が異なるため、課題種別IDやユーザーIDを1つずつ手動で抽出して、シートに登録していきました。これが地味に大変でした(今となっては、この抽出作業自体ももっと自動化できるのでは?と模索中です)。

テンプレートの作り込み
TemplateMaster のセルを1つずつ作成するにあたり、種別ごとのテンプレを手動で抽出しました。組織変更などでテンプレの記載内容が変わることもあるため、古い内容のまま使っていないかのチェックも必要でした。

導入効果

手作業だった頃と比べて、かかる時間が大きく変わりました。

作業時間
Before(手作業) コア業務を片手間に、1日あたり2〜3時間の起票・連絡対応を、5営業日ほどかけて実施
After(一括起票) 対象者の氏名・メールアドレス・SlackユーザーIDさえ分かれば、約10秒で起票+Slack通知まで完了

これまでは、コア業務を片手間にこなしながら「1日2〜3時間 × 5営業日」程度かかる、なかなかにつらい作業でした。それが、必要な情報をシートに入れて実行するだけ、約10秒で30弱の案件の起票とSlack通知が終わるようになりました。

工数削減はもちろんですが、「どちらの方法で依頼するんだっけ」と過去を遡る必要がなくなり、抜け漏れの不安も減ったのが大きいです。

まとめ

手作業で30件弱を起票していたのが、シートに1行足して実行するだけで終わるようになりました。抜け漏れの不安も減りました。

コード自体は生成AI(Gemini)に相談しながら作ったので、ゼロから全部自力で書いたわけではありません。それでも「何をやりたいか」を整理して伝えれば、ここまでの仕組みが作れてしまうので、生成AIはやっぱり強力だなと感じています。

今後はアカウントの新規発行だけでなく、人事異動によるアカウント棚卸しや、アカウント削除の際もこの運用に載せられるようにしていくことが目標です。

最後まで読んでいただき、ありがとうございました。