マキモノ
Google Workspace無料✅ 公式検証済みv1.0.0 / 更新

既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する

人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。

出品者: kim@orgiast.jp📖 読込 約2,888トークン (約4円)💰 コスパ 61倍
トークン節約メーター98%節約
ゼロからAIに作らせた場合約18万トークン
このMDを読ませた場合約4,200トークン

約17.6万トークンの節約 (API料金換算で約260円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

「既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約17.6万トークン(API料金換算で約260円)・98%のトークンを節約できます。

カテゴリ
Google Workspace
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約18万トークン
この巻物使用時
約4,200トークン
節約量
約17.6万トークン (約260円)
更新日
2026-10-01

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/google-gas-read-back/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

既存の Google フォームへ GAS から自動送信し、回答シートで read-back 検証する

「人が手入力している Google フォーム」を業務システムから自動送信して、手作業を消す。 フォームを作り直さないので、受け取る側(フォーム回答を見ている部署)の運用は一切変わらないのが利点。

想定: 社内の別部署が運用しているフォーム(発注・予約・申請など)に、自分たちのアプリから自動で投げたい。


1. フォームの entry ID を取り出す

フォームが公開(リンクを知っている全員が回答可)なら、認証なしで HTML を取れる。

curl -sL -o form.html 'https://docs.google.com/forms/d/e/<FORM_ID>/viewform'

FB_PUBLIC_LOAD_DATA_ に全質問の定義が入っている。これを解析する:

import fs from 'fs';
const h = fs.readFileSync('form.html', 'utf8');
const m = h.match(/FB_PUBLIC_LOAD_DATA_ = (\[[\s\S]*?\]);/);
const d = JSON.parse(m[1]);
console.log('collectEmail:', JSON.stringify(d[1] && d[1][10]));
for (const it of (d[1][1] || [])) {
  const title = it[1], type = it[3], sub = it[4] || [];
  for (const s of sub) {
    const id = s[0], req = s[2], opts = (s[1] || []).map(o => o[0]);
    console.log(`entry.${id}\ttype=${type}\treq=${req}\t${JSON.stringify(title)}\t${JSON.stringify(opts)}`);
  }
}

type は 0=短答 / 1=段落 / 2=ラジオ / 3=プルダウン / 9=日付。 req=1 が必須項目。選択肢は文字列を1文字も変えずに送ること(かぎ括弧や全角記号もそのまま)。

落とし穴: メールアドレスは entry.* ではない

「メールアドレス」の質問が一覧に出てこないのに、フォーム画面には入力欄がある場合、 それは質問項目ではなくメール収集機能。パラメータ名は固定で emailAddress を使う。

  • 「回答者が入力」モードなら、任意のアドレスを渡せる
  • 「自動収集(確認済み)」モードだと、送信したアカウントのアドレスになり上書きできない。 この場合、本人以外のアドレスを入れる運用は自動化できないので、設計を変える(フォーム側に質問項目を足してもらう等)

日付の形式

entry.<id>=YYYY-MM-DD で通る。古い資料にある entry.<id>_year / _month / _day の分割形式は不要。 ただし実際に1回テスト送信して回答シートで確認すること。フォームの設定で変わり得る。


2. GAS から送信する

function submitForm_(values) {
  const res = UrlFetchApp.fetch(
    'https://docs.google.com/forms/d/e/<FORM_ID>/formResponse',
    { method: 'post', payload: values, muteHttpExceptions: true }
  );
  return res.getResponseCode(); // 200 が成功
}

payload はフラットなオブジェクト({'entry.123': '値', emailAddress: '...'})でよい。


3. 回答シートの read-back 検証 — ここに罠が2つある

「送ったつもり」で終わらせず、回答シートに行が入ったことを確認する。ただし次の2点で確実に失敗する。

罠1: ファイル名とタブ名は別物

回答先スプレッドシートのファイル名が「○○申込フォーム(回答)」でも、 タブ名が同じとは限らない。しかも1つのブックに別業務のタブが同居していることがある (目次・質問シート・集計など)。タブ名で探すと失敗するか、最悪、別業務のタブを読み書きする。

→ gid で特定する。

function responseSheet_(ssId, gid) {
  const sheets = SpreadsheetApp.openById(ssId).getSheets();
  for (const s of sheets) if (s.getSheetId() === gid) return s;
  // 見つからない時は「無い」で終わらせず、全タブ名と gid を添えて失敗させる
  const list = sheets.map(s => s.getName() + '(gid=' + s.getSheetId() + ')').join(', ');
  throw new Error('回答タブが見つかりません。候補: ' + list);
}

gid はブラウザで該当タブを開いた時の URL 末尾 #gid=... から取る。設定値として外出しし、既定値を持たせる。

さらに、特定したタブのヘッダー行がフォームの質問と噛み合うかを検証してから使う。 gid が変わっていた場合に別タブを書き換える事故を防げる。

罠2: getLastRow() が実データの最終行より大きい値を返す

回答シートの末尾にテンプレート由来の数式が入った行が並んでいると、 getLastRow() も getDataRange() も数式がある行までを返す。

実測例: 実際の回答は32行目までなのに getLastRow() が 131 を返した。 「送信前の最終行より後ろを探す」実装にしていたため、**探索ループが一度も回らず、常に「未反映」**になった。

→ タイムスタンプ列(通常1列目)の、値が入っている最後の行で判定する。

function lastDataRow_(sheet) {
  const n = sheet.getLastRow();
  if (n < 2) return 1;
  const col = sheet.getRange(1, 1, n, 1).getValues(); // 1列だけ読む
  for (let i = col.length - 1; i >= 1; i--) {
    if (String(col[i][0] || '').trim() !== '') return i + 1;
  }
  return 1;
}

4. 反映は遅い。同期で待つ設計にしない

フォーム POST からシートに行が現れるまで、実測で約52秒かかった。保証された値ではなく、 もっと遅いこともある。

ここで「30秒待って見つからなければ失敗」という同期実装にすると、 成功した送信が毎回失敗として記録される。待ち時間を延ばしても、反映時間が保証されていない以上は再発する。

→ POST が 200 なら「送信済み・未確認」として状態を保存し、戻り値は成功にする。 確認は後追いのトリガ(10分おき等)で行い、見つかったら「確認済み」に更新する。 一定回数(例: 6回=約1時間)確認できなければ、そこで初めて失敗として通知する。

状態は最低3つに分ける。ここを2値にすると事故る。

状態意味再送してよいか
未送信POST していない(設定で止めた等)してよい
送信済み・未確認POST は 200。シート未確認絶対にしない
確認済みシートに行を確認しない

「未送信で止まった」と「送ったが結果が分からない」を区別できないと、 前者が永久に再開できない(手動復旧が必要)か、後者で二重送信する。 外部への申請や発注では二重送信が実害になるので、迷ったら送らない側に倒す。


5. 本番を汚さないための3段モード

いきなり本番送信を有効にしない。設定値ひとつで3段に分ける。

モード挙動
off(既定)外部送信を一切しない
test実フォームへ送るが、識別子と注記を付ける
live本番

off は引数で上書きできない絶対の停止条件にする。 関数の opts.mode で off を破れる実装にすると、 「止めたはずなのに送られた」が起きる。上書きを許すのは test ↔ live の間だけ。

test で守ること:

  • 案件名などの識別子に __TEST__<一意なUUID> を付け、受け取る側が一目で分かるようにする
  • 実在の第三者のメールアドレスを入れない(専用のテスト用アドレスか、実行アカウント)
  • 備考欄の先頭に「これはテスト送信です。対応不要です。」を入れる
  • チャット通知など他システムへの書き込みは一切しない(テストで関係者に通知が飛ぶのが一番迷惑)

live への切替には確認トークンを必須にする:

function setMode(mode, opts) {
  if (['off', 'test', 'live'].indexOf(mode) < 0) throw new Error('不正なモード: ' + mode);
  if (mode === 'live' && (!opts || opts.confirm !== 'ENABLE-LIVE-SENDING')) {
    throw new Error('live にするには confirm が必要です');
  }
  // 書き込み後に必ず読み戻して確認する
}

6. 検証の順序

  1. モックのテストを書く(送信経路が off で一切発火しないことを含める)
  2. test で1件だけ送る
  3. 回答シートを直接読み、送った値と入った値を1項目ずつ突き合わせる。特に日付
  4. 同じ対象をもう一度送って、行が増えないこと(二重送信防止)を確認する
  5. 複数パターン(地域別の分岐など計算ロジックが変わるもの)で2〜3件試す
  6. live に切り替える

モックのテストが何百件通っても、実データで初めて出るバグがある。 実際この手順で、テスト762件が全通過した状態から「タブ名が違う」「getLastRow() が実データを超える」 の2件が実送信で初めて露見した。どちらもモックでは再現しない種類のもの。


7. 運用前に確認すること

  • 回答先スプレッドシートの所有者を確認する。個人アカウント所有のまま共有で使われていることがあり、 その場合は共有が切れた瞬間に自動化も手作業も同時に止まる。組織アカウントへの移管を先に提案する
  • 送信が失敗した時に誰にどう通知するかを決める。通知は重複抑止を入れる (後追い確認のたびに失敗通知が飛ぶと、通知が意味を失う)
  • フォームの質問が変更されると entry ID や選択肢文字列が変わる。定期的に entry ID を取り直して突き合わせる か、送信前にヘッダー照合で検知する

よくある質問

+「既存のGoogleフォームへGASから自動送信し、回答シートでread-back検証する」とは何ですか?

人が手入力しているフォームを業務システムから自動送信して手作業を消す。entry IDの取り出し、メール収集欄の罠、回答タブはgidで特定、getLastRow()が実データを超える罠、反映52秒に耐える非同期確認、off/test/liveの3段ゲートまで。

+どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約18万トークンかかりますが、この巻物を使えば約4,200トークンで済みます。差し引き約17.6万トークン(API料金換算で約260円)・98%の節約です。

+どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

+どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

+商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア