GASで問い合わせメールをAIで
緊急度判定し優先順位をつける方法

スプレッドシートに集めた問い合わせを、AI(Claude API)で0〜100の緊急度スコアに採点し、即日対応などの対応区分をつけて優先順位で並べ替える方法を、動くコードで解説します。高緊急度だけSlackに通知する運用まで紹介します。

|対象: GAS / スプレッドシート / Claude API

Table of Contents

問い合わせの「どれから対応するか」を自動化する

問い合わせが1日に何十件も届くと、届いた順に対応しているうちに、本当に急ぐ案件が埋もれてしまいます。 システム障害や納期の相談を後回しにしてしまうと、クレームや失注につながります。

メール本文を読んで緊急度を見極める作業は、AIが下書きを作るのに向いています。 スプレッドシートに集めた問い合わせをClaude APIに渡し、緊急度を0〜100のスコアで採点させれば、対応の順番付け(トリアージ=優先度の振り分け)を自動化できます。 「高・中・低」の3段階ではなく数値スコアにするのがポイントで、同じ「高」の中でも順番が一意に決まります。

対応漏れの防止

急ぎの問い合わせが受信順の後ろに埋もれるのを防げる

対応順の見える化

スコア順に並べれば、上から対応するだけで優先度を守れる

高緊急度の即共有

スコアが高い案件だけSlackに飛ばし、担当者がすぐ気づける

属人化の解消

判断基準をコードに書くことで、担当者ごとの優先順位のブレを抑える

APIキーを安全に保管する

まずClaude APIのキーを用意します。コードに直接書くと共有時に漏れる恐れがあるため、PropertiesService(スクリプトごとの設定値を安全に保管する仕組み)のスクリプトプロパティに保存します。 後で使うSlackのWebhook URLも、同じようにSLACK_WEBHOOK_URLとして保存しておきます。

// スクリプトプロパティにAPIキーを保存しておく(初回1回だけ実行)
function saveApiKey() {
  PropertiesService.getScriptProperties()
    .setProperty("CLAUDE_API_KEY", "sk-ant-xxxxxxxx");
}

function getApiKey() {
  const key = PropertiesService.getScriptProperties()
    .getProperty("CLAUDE_API_KEY");
  if (!key) throw new Error("APIキーが未設定です。saveApiKey を実行してください。");
  return key;
}

緊急度の判断基準を固定する

この自動化で最も大事なのが、採点の基準を先に固定することです。 基準を渡さないと、AIは毎回違う観点で採点してスコアがブレます。 自社で優先したい要素(障害・納期・クレームなど)を言葉にして、スコア帯ごとの目安と一緒に指示します。 スコアから即日対応などの対応区分ラベルを決める処理は、AIに任せずGAS側で計算すると安定します。

// 緊急度の判断基準を自社の方針に合わせて書く(ここを固定するのが最大のポイント)
const PRIORITY_RULES =
  "次の観点で緊急度を0〜100のスコアで採点してください。\n" +
  "- 90以上(即日対応): システム停止・サービス障害・重大なクレーム・法的な指摘・支払い/請求のトラブル\n" +
  "- 70〜89(当日中): 納期や日程に関わる相談・機能不具合・強い不満の表明・キャンセル検討\n" +
  "- 40〜69(通常対応): 一般的な質問・仕様の確認・見積依頼・軽微な要望\n" +
  "- 39以下(低): 資料請求・お礼のメッセージ・営業や宣伝・自動送信の通知\n" +
  "文面が丁寧でも内容が深刻なら高く、口調が強くても内容が軽微なら上げすぎないでください。";

// スコアから対応区分のラベルを決める(GAS側で判定する)
function toActionLabel(score) {
  if (score >= 90) return "即日対応";
  if (score >= 70) return "当日中";
  if (score >= 40) return "通常対応";
  return "低";
}

「文面が丁寧でも内容が深刻なら高く」といった指示を入れているのがポイントです。 問い合わせは口調と深刻さが一致しないことが多いため、内容で判断するよう明示すると精度が安定します。

問い合わせを渡してスコアと理由を受け取る

件名と本文をまとめてClaude APIに渡し、score(0〜100の緊急度)とreason(採点の理由)をJSONで受け取ります。 理由を一緒にもらうことで、なぜそのスコアなのかを人が確認でき、判定を信用してよいか判断できます。 AIの返答には前後に説明文が付くことがあるため、[から]までを切り出してからJSON.parseする点がポイントです。

// 問い合わせの配列を渡し、緊急度スコアと理由を受け取る
function scoreInquiries(items) {
  const prompt =
    "あなたはカスタマーサポートの責任者です。\n" +
    "次の問い合わせを読み、対応の緊急度を採点してください。\n" +
    PRIORITY_RULES + "\n" +
    "各件に score(0〜100の整数)と、その採点の reason(40字以内の日本語)を付けてください。\n" +
    "余計な説明は出力せず、JSON配列だけを返してください。\n" +
    "形式: [{\"index\":0,\"score\":85,\"reason\":\"納期遅延への苦情のため\"}]\n\n" +
    "問い合わせ(番号: 件名 / 本文):\n" +
    items.map((it, i) =>
      i + ": " + it.subject + " / " + it.body
    ).join("\n");

  const payload = {
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    messages: [{ role: "user", content: prompt }],
  };

  const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
    method: "post",
    contentType: "application/json",
    headers: {
      "x-api-key": getApiKey(),
      "anthropic-version": "2023-06-01",
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  });

  if (res.getResponseCode() !== 200) {
    throw new Error("API error: " + res.getContentText());
  }

  const text = JSON.parse(res.getContentText()).content[0].text;
  return JSON.parse(extractJson(text));
}

// レスポンスからJSON配列部分だけを取り出す(前後の余計な文字対策)
function extractJson(text) {
  const start = text.indexOf("[");
  const end = text.lastIndexOf("]");
  return text.slice(start, end + 1);
}

15件ずつのバッチ処理で一括採点

1件ずつAPIを呼ぶと通信回数が増え、実行時間もコストもかさみます。15件をまとめて1回のリクエストで送るバッチ処理にします。 スコア列(C列)がすでに埋まっている行はスキップするので、途中で止まっても再実行すれば続きから処理できます。 受け取ったスコアは0〜100の範囲に収め、対応区分ラベルはGAS側で決めます。

// A列=件名 B列=本文 C列=緊急度スコア D列=対応区分 E列=理由 を想定
function scoreInquiriesAll() {
  const sheet = SpreadsheetApp.getActive().getSheetByName("問い合わせ");
  const last = sheet.getLastRow();
  if (last < 2) return;

  const values = sheet.getRange(2, 1, last - 1, 5).getValues();
  const BATCH = 15;

  // スコア(C列)が未入力の行だけを対象にする
  const targets = [];
  values.forEach((row, i) => {
    const subject = String(row[0]).trim();
    const body = String(row[1]).trim();
    const done = String(row[2]).trim() !== "";
    if ((subject || body) && !done) {
      targets.push({
        rowIndex: i,
        subject: subject || "(件名なし)",
        body: body.slice(0, 800), // 長文はトークン節約のため先頭だけ渡す
      });
    }
  });

  for (let s = 0; s < targets.length; s += BATCH) {
    const chunk = targets.slice(s, s + BATCH);
    const result = scoreWithRetry(chunk);

    result.forEach((r) => {
      const target = chunk[r.index];
      if (!target) return;
      const score = Math.max(0, Math.min(100, Number(r.score) || 0));
      values[target.rowIndex][2] = score;
      values[target.rowIndex][3] = toActionLabel(score);
      values[target.rowIndex][4] = r.reason || "";
    });

    Utilities.sleep(500); // 連続呼び出しの間隔をあける
  }

  sheet.getRange(2, 1, values.length, 5).setValues(values);
}

一時的なAPIエラーに備えて、失敗したら少し待って再試行するリトライも挟みます。待ち時間を試行のたびに伸ばす(指数バックオフ)と、混雑時に成功しやすくなります。

// 一時的なエラー時に最大3回まで再試行する
function scoreWithRetry(items, maxRetry) {
  const limit = maxRetry || 3;
  for (let attempt = 1; attempt <= limit; attempt++) {
    try {
      return scoreInquiries(items);
    } catch (e) {
      Logger.log("試行" + attempt + "回目で失敗: " + e.message);
      if (attempt === limit) throw e;
      Utilities.sleep(1000 * attempt); // 待ち時間を伸ばして再試行
    }
  }
}

高緊急度の問い合わせだけSlackに通知する

スコアが高い問い合わせは、スプレッドシートを見に行く前にすぐ気づきたいものです。 一定スコア以上(この例では70以上)の行だけをSlackに通知します。 通知済みフラグ(F列)を立てて、同じ問い合わせを何度も通知しないようにします。

// スコアが高い問い合わせだけSlackに通知する
function notifyUrgentInquiries() {
  const url = PropertiesService.getScriptProperties()
    .getProperty("SLACK_WEBHOOK_URL");
  if (!url) return;

  const sheet = SpreadsheetApp.getActive().getSheetByName("問い合わせ");
  const last = sheet.getLastRow();
  if (last < 2) return;

  // A列=件名 C列=スコア E列=理由 F列=通知済みフラグ
  const values = sheet.getRange(2, 1, last - 1, 6).getValues();
  const THRESHOLD = 70; // このスコア以上を通知対象にする

  values.forEach((row, i) => {
    const subject = String(row[0]).trim();
    const score = Number(row[2]);
    const reason = String(row[4]).trim();
    const notified = String(row[5]).trim() !== "";
    if (score >= THRESHOLD && !notified) {
      const text =
        "🚨 緊急度 " + score + " の問い合わせ\n" +
        "件名: " + subject + "\n" +
        "理由: " + reason;
      UrlFetchApp.fetch(url, {
        method: "post",
        contentType: "application/json",
        payload: JSON.stringify({ text: text }),
        muteHttpExceptions: true,
      });
      sheet.getRange(i + 2, 6).setValue("通知済み"); // 二重通知を防ぐ
    }
  });
}

しきい値は運用しながら調整してください。厳しくすると通知が減り、緩めると通知が増えます。 WebhookのURLはAPIキーと同じくスクリプトプロパティに保管します。

スコアの高い順に並べ替える

採点が終わったら、シートをスコアの高い順に並べ替えます。 上から順に対応していけば、自然に優先度を守れます。 1行目の見出しを動かさないよう、2行目以降だけを対象にRange.sort()で並べ替えます。

// 未対応の問い合わせをスコアの高い順に並べ替える
function sortByPriority() {
  const sheet = SpreadsheetApp.getActive().getSheetByName("問い合わせ");
  const last = sheet.getLastRow();
  if (last < 2) return;

  // 1行目の見出しは動かさず、2行目以降だけを並べ替える
  const range = sheet.getRange(2, 1, last - 1, sheet.getLastColumn());
  range.sort({ column: 3, ascending: false }); // C列=スコアの降順
}

対応が終わった問い合わせを別シートに移す運用にすると、未対応分だけが上位に残り、より見やすくなります。

定期トリガーで自動実行する

新しく届いた問い合わせを自動で採点・通知・並べ替えするには、時間主導トリガー(時刻や間隔で自動実行する仕組み)を使います。everyMinutesで15分おきに実行できます。採点・通知・並べ替えをまとめて動かします。

// 15分おきに採点・通知・並べ替えを自動実行する
function createPriorityTrigger() {
  ScriptApp.newTrigger("runPriorityFlow")
    .timeBased()
    .everyMinutes(15)
    .create();
}

function runPriorityFlow() {
  scoreInquiriesAll();     // 未採点の問い合わせを採点
  notifyUrgentInquiries(); // 高緊急度だけSlack通知
  sortByPriority();        // スコア順に並べ替え
}

採点済み行はスキップされるので、短い間隔で動かしても新しい問い合わせだけが処理されます。 問い合わせをスプレッドシートに集める部分は、Googleフォームやメール転送と組み合わせると自動化できます。

実務での注意点

1. 最終判断は人がする前提にする

AIは文面から緊急度を推測しますが、取引の重要度や過去の経緯までは分かりません。 スコアはあくまで対応順の目安として使い、理由を確認して明らかにおかしい判定は人が直してください。

2. 判断基準を自社仕様に育てる

スコアが実感とずれるときは、コードのPRIORITY_RULESの文言を見直します。優先したい要素を具体的に書き足すほど、判定が自社の感覚に近づきます。

3. 個人情報の外部送信ルールを確認する

問い合わせには氏名や連絡先が含まれることがあります。外部APIに送る前に、社内規程やプライバシーポリシーで外部送信が許されるかを確認してください。 必要なら、氏名やメールアドレスを除いた本文だけを渡す工夫も有効です。

4. 実行時間の上限に注意する

GASには1回あたり6分の実行時間制限があります。件数が多い場合は、採点済み行をスキップする仕組みと分割実行を組み合わせると安定します。

まとめ

問い合わせの緊急度判定は、採点基準を固定してClaude APIに渡せば下書きを自動化できます。 0〜100のスコアと理由を受け取れば、スコア順に並べるだけで対応の順番が決まり、対応漏れを防げます。 15件ずつのバッチ処理、判定済み行のスキップ、リトライ、高緊急度のSlack通知、定期トリガーを組み合わせれば、問い合わせ対応のトリアージを実務レベルで支えられます。 最終判断は人がする、という前提だけ守って活用してください。

よくある質問

「高・中・低」の3段階だと同じ「高」の中の順番が分からず、結局どれから対応すべきか迷います。0〜100のスコアで受け取れば、スコアの高い順に並べるだけで対応の順番が一意に決まります。この記事のように理由と推奨対応区分(即日対応・当日中・通常対応など)も一緒に受け取れば、数字の根拠も確認できます。判断基準をプロンプトで固定するのがブレを防ぐポイントです。

下書きとして使い、最終判断は人がする前提にしてください。AIはメール本文の言葉づかいから緊急度を推測しますが、取引の重要度や過去の経緯までは分かりません。この記事では判定理由も一緒に受け取り、スコアが高い問い合わせだけSlackやメールですぐ気づけるようにしています。並べ替えはあくまで対応の目安として使い、明らかにおかしい判定は人が直してください。

自社の対応方針を言葉にしてプロンプトに書き込みます。たとえば「システム停止・納期遅延・クレームは高スコア」「一般的な質問・資料請求は低スコア」のように、自社で優先したい要素を具体的に指示します。基準を固定しないと、AIが毎回違う観点で採点してスコアがブレます。運用しながら、実際の判定結果を見て基準の文言を調整してください。

できます。1件ずつAPIを呼ぶと通信回数が増えるため、この記事のように15件ずつまとめて1回のリクエストで送るバッチ処理にします。判定済み行をスキップする仕組みと併用すれば、GASの6分制限で途中終了しても、再実行で続きから処理できます。件数が非常に多い場合は分割実行と定期トリガーを組み合わせてください。

直接書くのは避けてください。スクリプトを共有した際にキーが漏れる恐れがあります。PropertiesService のスクリプトプロパティに保存し、コードからは PropertiesService.getScriptProperties().getProperty() で読み込む方法が安全です。

問い合わせ対応の
自動化を相談する。

緊急度判定以外にも、問い合わせの自動分類・返信下書き・Slack通知・集計など、問い合わせ対応を効率化するGAS×AI開発をご相談いただけます。

AI×GAS自動化を見る