講座の感想、ちゃんと共有できてますか
講座やセミナーを運営していると、受講生からの感想やアンケート回答が集まる。Googleフォームで。
集まるところまではいい。問題はその先だ。
「フォームの回答をスプレッドシートで確認して、いい感想をコピーして、Chatworkのグループチャットに貼って……」
最初の2週間はやる。3週目から面倒になる。1ヶ月後にはスプレッドシートに未読の回答が30件溜まっている。
この「管理者がコピペで共有する」運用、ほぼ確実に破綻する。人間がハブになる仕組みは、人間が忙しくなった瞬間に止まるから。
やりたいことはシンプル
受講生がGoogleフォームに感想を送ったら、Chatworkのグループチャットに自動で投稿される。それだけ。
管理者は何もしない。感想が送信された瞬間に、チームメンバー全員の目に入る。
「あの受講生、こんなこと感じてたんだ」が勝手に共有される。学びの温度がリアルタイムで伝わる。
これ、GAS(Google Apps Script)で10分あれば作れる。しかも5年以上、現場で使い続けている仕組みだ。壊れない。
2つのパターンがある
Googleフォーム × GASの連携には、2つのアプローチがある。
| パターン | 仕組み | 向いているケース |
|---|---|---|
| パターンA: フォーム直結 | フォームの送信イベントを直接受け取る | フォーム1つに対して1つの通知。シンプルに使いたいとき |
| パターンB: スプレッドシート経由 | フォームの回答が書き込まれたスプレッドシートのイベントを拾う | 複数フォームの回答が1シートに集まる場合や、回答データを加工してから通知したいとき |
どちらもコピペで動く。順番に見ていく。
共通部分: Chatwork API送信関数
まず、どちらのパターンでも使う「Chatworkにメッセージを送る関数」から。
// ============================================================
// 設定値
// ============================================================
var CONFIG = {
CHATWORK_API_TOKEN: 'YOUR_CHATWORK_API_TOKEN',
ROOM_ID: 'YOUR_ROOM_ID',
CHATWORK_API_URL: 'https://api.chatwork.com/v2'
};
// ============================================================
// Chatwork API: メッセージ送信
// ============================================================
function postToChatwork(roomId, message) {
var url = CONFIG.CHATWORK_API_URL + '/rooms/' + roomId + '/messages';
var options = {
method: 'post',
headers: {
'X-ChatWorkToken': CONFIG.CHATWORK_API_TOKEN
},
payload: {
body: message
},
muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(url, options);
var code = response.getResponseCode();
if (code !== 200) {
throw new Error('Chatwork API エラー (HTTP ' + code + '): ' + response.getContentText());
}
return JSON.parse(response.getContentText());
}
CHATWORK_API_TOKEN は Chatworkの「サービス連携」画面から発行する。ROOM_ID はブラウザでルームを開いたときのURLに含まれる数字。
パターンA: フォーム直結(onFormSubmit)
Googleフォームの送信イベントを直接キャッチする。一番素直なやり方。
function onFormSubmit(e) {
try {
var response = e.response;
var itemResponses = response.getItemResponses();
var timestamp = response.getTimestamp();
var lines = [];
for (var i = 0; i < itemResponses.length; i++) {
var item = itemResponses[i];
var question = item.getItem().getTitle();
var answer = item.getResponse();
if (Array.isArray(answer)) {
answer = answer.join(', ');
}
lines.push(question + ': ' + (answer || '(未回答)'));
}
var formTitle = FormApp.getActiveForm()
? FormApp.getActiveForm().getTitle()
: 'Googleフォーム';
var body = '[info][title]' + formTitle + ' - 回答通知[/title]'
+ '受信日時: ' + Utilities.formatDate(timestamp, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss') + '\n'
+ '\n'
+ lines.join('\n')
+ '[/info]';
postToChatwork(CONFIG.ROOM_ID, body);
Logger.log('Chatwork通知完了: ' + formTitle);
} catch (error) {
Logger.log('エラー発生: ' + error.message);
try {
postToChatwork(CONFIG.ROOM_ID,
'[info][title]フォーム通知エラー[/title]' + error.message + '[/info]'
);
} catch (e2) {
Logger.log('エラー通知も失敗: ' + e2.message);
}
}
}
ポイントは e.response でフォームの回答オブジェクトを直接触れること。質問名と回答がペアで取れるので、整形が楽。チェックボックス(複数選択)の回答は配列で返ってくるから、Array.isArray で判定してカンマ区切りにしている。
エラー時にChatworkへ通知を飛ばしているのは、GASのログを毎日見に行く人間はいないから。壊れたことに気づかないまま数週間放置、というのが一番怖い。
パターンB: スプレッドシート経由(onFormSubmitFromSheet)
フォームの回答がスプレッドシートに書き込まれるタイミングでトリガーを発火させる。
function onFormSubmitFromSheet(e) {
try {
var range = e.range;
var sheet = range.getSheet();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var values = range.getValues()[0];
var lines = [];
for (var i = 0; i < headers.length; i++) {
if (headers[i] && i < values.length) {
var val = values[i];
if (val instanceof Date) {
val = Utilities.formatDate(val, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
}
lines.push(headers[i] + ': ' + (val !== '' ? val : '(未回答)'));
}
}
var sheetName = sheet.getName();
var body = '[info][title]フォーム回答通知 (' + sheetName + ')[/title]'
+ lines.join('\n')
+ '[/info]';
postToChatwork(CONFIG.ROOM_ID, body);
Logger.log('Chatwork通知完了(シート版)');
} catch (error) {
Logger.log('エラー発生: ' + error.message);
}
}
日付型の値が Date オブジェクトのまま返ってくる罠がある。instanceof Date で判定してフォーマットしないと、Mon Mar 31 2026 09:00:00 GMT+0900 みたいな読めない文字列がChatworkに流れる。
どちらを選ぶか
正直、迷ったらパターンAでいい。
パターンBが必要になるのは、「フォームの回答をスプレッドシートで加工してから通知したい」とか「複数のフォームの回答を1つのシートにまとめている」といった、やや特殊なケース。
セットアップ手順(10分で終わる)
1. スクリプトエディタを開く
フォームに紐づくスプレッドシートを開いて、メニューから「拡張機能」→「Apps Script」。
2. コードを貼り付ける
上のコード(CONFIG + postToChatwork + 使いたいパターンの関数)をまるごと貼り付ける。
3. 設定値を書き換える
var CONFIG = {
CHATWORK_API_TOKEN: 'ここに自分のAPIトークン',
ROOM_ID: 'ここに投稿先のルームID',
CHATWORK_API_URL: 'https://api.chatwork.com/v2'
};
4. テストする(トリガー設定の前に疎通確認)
トリガーを設定する前に、APIトークンとルームIDが正しいかテストする。
function testNotification() {
var testMessage = '[info][title]フォーム回答通知 - テスト[/title]'
+ '受信日時: ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss') + '\n'
+ '\n'
+ 'お名前: テスト太郎\n'
+ 'メールアドレス: test@example.com\n'
+ 'お問い合わせ内容: テスト送信です'
+ '[/info]';
postToChatwork(CONFIG.ROOM_ID, testMessage);
Logger.log('テスト通知送信完了');
}
5. トリガーを設定する
テストが成功したら、フォーム送信時に自動で実行されるようにトリガーを設定する。
スクリプトエディタの左メニュー「トリガー」→「トリガーを追加」。
| 項目 | パターンA | パターンB |
|---|---|---|
| 関数 | onFormSubmit |
onFormSubmitFromSheet |
| イベントソース | フォームから | スプレッドシートから |
| イベント | フォーム送信時 | フォーム送信時 |
5年使って分かったこと
この仕組みを講座運営で5年以上使い続けている。
壊れない。 GASもChatwork APIも枯れた技術だ。APIの仕様変更でスクリプトが止まったことは一度もない。
感想の共有スピードが運営の質を変える。 受講生が感想を送った30秒後にはチーム全員が読んでいる。メールで集めてExcelにまとめて月次レポート、という世界とは別次元のスピード感。
「見られている」が受講生のモチベーションになる。 感想を送ったら運営側から即座にリアクションが返ってくる。フォームの回答率も目に見えて改善した。
ハマりポイント
権限の承認を忘れる。 初回実行時にGoogleの権限承認画面が出る。「このアプリは確認されていません」の警告に怯まず、「詳細」→「安全でないページに移動」で進む。
トリガーの重複。 トリガーを何度も追加すると、1回のフォーム送信で通知が2回3回飛ぶ。トリガー一覧を確認して、古いものは削除する。
Chatwork APIのレートリミット。 5分間に100リクエストまで。講座の感想程度なら引っかかることはまずない。
まとめ
Googleフォーム → GAS → Chatwork。たったこれだけの連携で、「感想を集めて共有する」という地味だけど大事な作業が完全に自動化される。
講座運営、コミュニティ運営、社内研修。受講生やメンバーの声をチームに届けたいなら、10分で作れるこの仕組みを試してほしい。
Chatworkシリーズ
- #1 なぜ2026年にまだChatworkを使い倒しているのか
- #2 chatwork-client-gas、ぶっちゃけいるの?
- #3 ルームの参加者データだけで、組織の人間関係マップを作った
- #4 「Chatworkに確定連絡が来たら請求書を送る」をGASで自動化する
- #5 Chatwork MCPを繋いだら、17ルームの未読が10秒で片付いた
- #6 MCP vs GAS — Chatwork自動化の「正解」はどっちか
- #7 コンタクト承認をn8nで自動化しようとしたら、3つの罠にハマった
- #8 ChatworkにAIチームを住まわせたら、勝手に会話が始まった
- #9 Chatwork 8ルームの全メッセージからFAQ429件を自動抽出した
- #10 Webhook署名検証を入れたら全メッセージが消えた
- #11 過去メッセージを全件取得しようとしたら、APIの「100件の壁」にハマった
- #12 Chatwork APIの「既読」は自分で制御できる
- #13 Chatwork APIのファイル機能、使ったことある?
- #14 n8nで全ルーム巡回
- #15 タスク機能をAPIで使い倒す
- #16 MCPを2アカウント同時接続したら、仕事用と事務局用が1画面で回った
- #17 【世界初かもしれない】ChatworkでClaude Code Channelsを実装してみた
- #18 Chatwork × Dify × GASで問い合わせ回答を自動提案する
- #19 RelationMapを夜間バッチで毎日自動更新する
- #20 17記事書いて見えた、Chatwork APIエコシステムに足りないもの
- #21 Googleフォームの回答をChatworkに自動投稿するGAS(この記事)
- #22 Chatworkの会話を毎日AIが要約してくれる仕組みをn8nで作った話
- #23 chatwork-cliを入れたら、シェルからChatworkが操作できて世界が変わった
- #24 ChatworkのWebhookをn8nで受けるなら、HMAC署名検証は必ずやれ
Chatworkシリーズ
- #1 なぜ2026年にまだChatworkを使い倒しているのか
- #2 chatwork-client-gas、ぶっちゃけいるの?
- #3 ルームの参加者データだけで、組織の人間関係マップを作った
- #4 「Chatworkに確定連絡が来たら請求書を送る」をGASで自動化する
- #5 Chatwork MCPを繋いだら、17ルームの未読が10秒で片付いた
- #6 MCP vs GAS — Chatwork自動化の「正解」はどっちか
- #7 コンタクト承認をn8nで自動化しようとしたら、3つの罠にハマった
- #8 ChatworkにAIチームを住まわせたら、勝手に会話が始まった
- #9 Chatwork 8ルームの全メッセージからFAQ429件を自動抽出した
- #10 Webhook署名検証を入れたら全メッセージが消えた
- #11 過去メッセージを全件取得しようとしたら、APIの「100件の壁」にハマった
- #12 Chatwork APIの「既読」は自分で制御できる
- #13 Chatwork APIのファイル機能、使ったことある?
- #14 n8nで全ルーム巡回
- #15 タスク機能をAPIで使い倒す
- #16 MCPを2アカウント同時接続したら、仕事用と事務局用が1画面で回った
- #17 【世界初かもしれない】ChatworkでClaude Code Channelsを実装してみた
- #18 Chatwork × Dify × GASで問い合わせ回答を自動提案する
- #19 RelationMapを夜間バッチで毎日自動更新する
- #20 17記事書いて見えた、Chatwork APIエコシステムに足りないもの
- #21 Googleフォームの回答をChatworkに自動投稿するGAS(この記事)
- #22 Chatworkの会話を毎日AIが要約してくれる仕組みをn8nで作った話
- #23 chatwork-cliを入れたら、シェルからChatworkが操作できて世界が変わった
- #24 ChatworkのWebhookをn8nで受けるなら、HMAC署名検証は必ずやれ
- #25 Chatwork × GAS × Claude Codeで会員制講座の運用を自動化した