問い合わせ対応でAIに返信下書きを作らせても、下書きの完成と送信可否は別の状態です。
この記事では、GoogleスプレッドシートとGoogle Apps Script(GAS)で、返信下書きのレビュー結果を記録し、送信前の確認漏れを一覧化する最小構成を紹介します。AI APIの呼び出しや顧客への自動送信は扱いません。AIは下書きと確認候補まで、人間が送信・差し戻し・保留を確定する構成です。
作るもの
REPLY_REVIEW_LOG シートに、1件の返信レビューを1行で保存します。
| 列 | 内容 |
|---|---|
inquiry_id |
問い合わせを戻すための識別子 |
draft |
AIが作った返信下書き |
source_refs |
参照したFAQや手順の識別子 |
facts_checked |
事実を確認したか |
recipient_checked |
宛先・対象者を確認したか |
promise_checked |
約束・期限を確認したか |
sensitive_data_present |
個人情報などが含まれるか |
decision |
send / revise / hold
|
reason |
判定理由 |
reviewer |
人間の確認者 |
reviewed_at |
確認日時 |
next_action |
次に行うこと |
status |
open / done
|
send は「AIが送信してよいと決めた」という意味にしません。確認項目を人間が埋め、担当者が送信する段階へ進めた、という記録に限定します。
シートを準備する
スプレッドシートに REPLY_REVIEW_LOG というシートを作り、1行目へ見出しを入力します。
inquiry_id,draft,source_refs,facts_checked,recipient_checked,promise_checked,sensitive_data_present,decision,reason,reviewer,reviewed_at,next_action,status
サンプルデータには実際の氏名、メールアドレス、契約番号、問い合わせ本文を入れないでください。動作確認には、次のような匿名化した行を使います。
inq-20260824-001,確認のうえ担当者からご連絡します。,faq-12|service-guide-03,TRUE,TRUE,FALSE,FALSE,revise,返信予定日の根拠がない,担当者A,,返信予定日を確認する,open
GASでレビュー結果を検証する
拡張機能 > Apps Script を開き、次のコードを貼り付けます。
const REVIEW_SHEET = 'REPLY_REVIEW_LOG';
const DECISIONS = new Set(['send', 'revise', 'hold']);
const REQUIRED_HEADERS = [
'inquiry_id', 'draft', 'source_refs', 'facts_checked',
'recipient_checked', 'promise_checked', 'sensitive_data_present',
'decision', 'reason', 'reviewer', 'reviewed_at', 'next_action', 'status'
];
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('返信レビュー')
.addItem('レビュー行を検証', 'validateReviewRows')
.addItem('要確認一覧を作成', 'createOpenReviewSheet')
.addToUi();
}
function validateReviewRows() {
const sheet = SpreadsheetApp.getActive().getSheetByName(REVIEW_SHEET);
if (!sheet) throw new Error(`${REVIEW_SHEET} シートがありません`);
const values = sheet.getDataRange().getValues();
if (values.length < 2) return;
const header = values[0].map(String);
const index = Object.fromEntries(header.map((name, i) => [name, i]));
for (const name of REQUIRED_HEADERS) {
if (index[name] === undefined) throw new Error(`列がありません: ${name}`);
}
const errors = [];
values.slice(1).forEach((row, offset) => {
const line = offset + 2;
if (row.every(value => value === '')) return;
const get = name => String(row[index[name]] ?? '').trim();
const decision = get('decision');
if (!get('inquiry_id')) errors.push(`${line}行目: inquiry_id が空です`);
if (!get('draft')) errors.push(`${line}行目: draft が空です`);
if (!DECISIONS.has(decision)) {
errors.push(`${line}行目: decision は send/revise/hold のいずれかです`);
return;
}
if (decision === 'send' &&
(!get('facts_checked') || !get('recipient_checked') || !get('promise_checked') ||
get('sensitive_data_present').toUpperCase() === 'TRUE' || !get('reviewer'))) {
errors.push(`${line}行目: send には確認項目・機微情報なし・確認者が必要です`);
}
if (decision !== 'send' && !get('reason')) {
errors.push(`${line}行目: revise/hold には reason が必要です`);
}
if (decision === 'hold' && !get('next_action')) {
errors.push(`${line}行目: hold には next_action が必要です`);
}
});
if (errors.length) throw new Error(errors.join('\n'));
SpreadsheetApp.getUi().alert('レビュー行の検証に成功しました。送信操作は担当者が行ってください。');
}
function createOpenReviewSheet() {
const source = SpreadsheetApp.getActive().getSheetByName(REVIEW_SHEET);
if (!source) throw new Error(`${REVIEW_SHEET} シートがありません`);
const values = source.getDataRange().getValues();
const header = values[0];
const statusIndex = header.indexOf('status');
const rows = values.slice(1).filter(row => String(row[statusIndex]).toLowerCase() !== 'done');
const book = SpreadsheetApp.getActive();
const old = book.getSheetByName('OPEN_REPLY_REVIEWS');
if (old) book.deleteSheet(old);
const target = book.insertSheet('OPEN_REPLY_REVIEWS');
target.getRange(1, 1, 1, header.length).setValues([header]);
if (rows.length) target.getRange(2, 1, rows.length, header.length).setValues(rows);
target.setFrozenRows(1);
}
このコードは send になった行を自動送信しません。検証、要確認一覧の生成、記録の一貫性確認だけを行います。
判定を分ける
実務では、次の3値を最初から固定すると、担当者ごとの「問題なさそう」が減ります。
-
send: 事実、宛先、約束、機微情報、確認者を確認できた。担当者が送信する -
revise: 直せる不足や表現の問題がある。下書きを直して再確認する -
hold: 契約、金額、苦情、個人情報、根拠不足、権限不足がある。送信せず責任者へ戻す
特に hold は失敗ではなく、戻し先を明確にするための状態です。reason と next_action がない保留は、次の担当者を再び迷わせます。
運用前チェックリスト
- AIに実顧客の機微情報を入力していない
-
FAQや手順の識別子を
source_refsに残せる - 送信可否をAIの出力だけで決めていない
-
sendに確認者と確認日時がある -
reviseとholdに理由がある -
holdに責任者や次の確認先がある -
送信済みになった後、担当者が
statusを更新する - 一定期間後に差し戻し理由をFAQ・フォーム・承認ルールへ戻す
まとめ
AI返信の導入で先に作るべきものは、自動送信の処理ではなく、送信前の判定を戻せるログです。GASで入力を検証し、send・revise・hold を分けるだけでも、確認基準と次の行動を共有できます。
この記事の構成は、AI API呼び出しや社外送信を含まない最小例です。契約、料金、個人情報、苦情などは必ず社内の責任者へ戻してください。Miraigentでは、AIに任せる範囲、人間確認、送らない情報、判断ログの戻し先を無料診断で整理しています。
