体験レッスンの申込をGoogleフォームで受けている教室が、LINE公式アカウントも持っているなら、2つをつなぐと受付の手間が減ります。申込が入ったら受付のLINEに知らせ、レッスンの前日には申込者のLINEにリマインドを送る。この記事では、LINEとGoogleフォームのつなぎ方を3つに分けて、どれで何ができるかを先に整理し、GASからMessaging APIでメッセージを送るコード、回答者のLINEとフォームの回答を結びつける方法とその限界、送る通数と料金までを、サンプル料理教室の例で通しで書きます。LINE公式アカウントがあり、Messaging APIのチャネルを作ってある前提です。
この記事の内容
先に決めること
LINE Notifyはもう使えない
GASからLINEに通知を送る方法として、以前はLINE Notifyがよく使われていました。LINE Notifyは2025年3月31日でサービスを終えていて、今は使えません。古いブログのコードで notify-api.line.me を呼んでいるものは動きません。代わりに、LINE公式アカウントのMessaging APIでプッシュメッセージを送ります。LINE Notifyと違い、送った通数はLINE公式アカウントのプランの通数に数えられます。
誰に送るのか
LINEで送る相手は2種類あり、準備が違います。
- スタッフに送る:受付や代表が、自分の個人のLINEで教室の公式アカウントを友だちに入れる。または、スタッフのLINEグループに公式アカウントを入れる
- 回答者(お客様)に送る:申込者が公式アカウントを友だちに入れていて、その人のユーザーIDがフォームの回答と結びついている
スタッフへの通知は、LINEでなくGoogle Chatでも済みます。Chatなら通数を気にせずに済み、退職したスタッフの個人のLINEに通知が残り続けることもありません。サンプル料理教室では、受付2人が教室のスマホで公式アカウントのグループを見ている運用だったので、LINEで知らせることにしました。お客様への連絡はLINEでしかできないので、そちらはLINEで送ります。
送信をどこから行うか
LINEにメッセージを送る処理は、GASでもn8nでも書けます。LINEからの受信(Webhook)は、n8nで受けることを勧めます。GASのWebアプリはリクエストのヘッダーを読めないので、LINEが付けてくる署名(x-line-signature)を確かめられません。LINE Developersは署名の検証を勧めているので、受ける側はヘッダーを読めるn8nにします。送るだけならGASで十分です。
つなぎ方の一覧
| つなぎ方 | できること | 必要なもの | 費用 |
|---|---|---|---|
| リッチメニューからフォームを開く | LINEのトーク画面の下のメニューから、フォームの申込画面を開く | リッチメニューのURLのアクション | 通数は使わない |
| 回答をスタッフにLINEで知らせる | フォームが送信されたら、受付のグループやスタッフにプッシュメッセージを送る | Messaging APIのチャネルアクセストークン、GAS、スタッフのユーザーIDかグループID | 送った人数ぶんの通数 |
| 回答者にLINEで送る | 受付の完了、前日のリマインド、日程の変更を、申込者のLINEに送る | 上に加えて、回答者のユーザーIDをフォームの回答と結びつける仕組み | 送った人数ぶんの通数 |
| LINEのメッセージにLINEで返す | 「体験を申し込む」と送ってきた人に、その人用のフォームのURLを返す | Webhookを受けるn8n | 応答メッセージは通数に数えない |
1つ目は設定だけで今日からできます。2つ目は、GASを数十行書けばできます。3つ目と4つ目は組み合わせて使い、ユーザーIDを結びつけるところに工夫が要ります。
リッチメニューからフォームを開く
- LINE Official Account Managerにログインし、「トークルーム管理」→「リッチメニュー」を開く
- 「リッチメニューを作成」を押す
- タイトルと表示期間を入れる
- テンプレートを選び、「体験レッスンを申し込む」に使う枠を決める
- その枠のアクションのタイプで「リンク」を選び、Googleフォームの回答用のURLを貼る
- アクションラベルに
体験レッスンの申込と入れる(20文字まで) - 「保存」を押す

Google Workspaceのフォームは、回答できる人を組織の中に絞った状態になっていることがあります。外のお客様に答えてもらうには、フォームを公開するときに「管理」から回答できるユーザーを開き、「一般的なアクセス」をリンクを知っている全員にします。また、「設定」の「回答」にある「回答を 1 回に制限する」はGoogleアカウントへのログインが要るので、LINEの中のブラウザで開くとログインの画面になってしまいます。お客様向けのフォームではオフにします。

この方法だけでは、誰が申し込んだかはフォームに書かれた名前でしか分かりません。LINEのユーザーIDは渡らないので、申込者にLINEで返事を送ることはできません。
回答をスタッフにLINEで知らせる
チャネルアクセストークンを用意する
- LINE Developersコンソールで教室のMessaging APIのチャネルを開く
- 「Messaging API設定」タブの一番下の「チャネルアクセストークン(長期)」で「発行」を押す
- 表示されたトークンをコピーする
このトークンがあれば誰でも教室の公式アカウントとしてメッセージを送れます。メールやチャットに貼らず、GASのスクリプトプロパティにだけ入れます。
送り先のIDを取る
スタッフのLINEグループに送る場合は、グループに公式アカウントを入れ、グループIDを取ります。
- LINE Developersコンソールの「Messaging API設定」タブで「グループトーク・複数人トークへの参加を許可する」を有効にする(既定では無効)
- 受付のLINEグループに、教室の公式アカウントを招待する
- グループで誰かが一言送る
- Webhookを受けているn8nの実行の履歴を開き、
events[0].source.groupIdの値(Cで始まる文字列)を控える
個人に送る場合は、スタッフ本人が公式アカウントを友だちに入れて一言送り、同じようにWebhookの source.userId(U で始まる33文字)を控えます。ユーザーIDは、プロバイダーが違うと同じ人でも別の値になります。別のプロバイダーのチャネルで取ったIDは使えません。
GASのコード
体験申込のフォームの回答先スプレッドシート「予約一覧」で、「拡張機能」→「Apps Script」を開き、次を貼ります。
const LINE_PUSH_URL = 'https://api.line.me/v2/bot/message/push';
function pushLine_(to, text) {
const props = PropertiesService.getScriptProperties();
const token = props.getProperty('LINE_CHANNEL_ACCESS_TOKEN');
const retryKey = Utilities.getUuid();
const options = {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: 'Bearer ' + token,
'X-Line-Retry-Key': retryKey
},
payload: JSON.stringify({
to: to,
messages: [{ type: 'text', text: text.slice(0, 5000) }]
}),
muteHttpExceptions: true
};
for (let i = 0; i < 3; i++) {
let code = 0;
let body = '';
try {
const res = UrlFetchApp.fetch(LINE_PUSH_URL, options);
code = res.getResponseCode();
body = res.getContentText();
} catch (err) {
body = String(err);
}
if (code === 200 || code === 409) return true;
if (code >= 400 && code < 500) {
console.error('LINE ' + code + ': ' + body);
return false;
}
Utilities.sleep(2000 * (i + 1));
}
console.error('LINE: 3回失敗しました');
return false;
}
function onTrialFormSubmit(e) {
const v = e.namedValues;
const text = [
'体験申込が入りました',
'お名前: ' + v['お名前'][0],
'希望日: ' + v['希望日'][0],
'コース: ' + v['希望のコース'][0],
'予約一覧の' + e.range.getRow() + '行目です'
].join('\n');
const groupId = PropertiesService.getScriptProperties().getProperty('LINE_STAFF_GROUP_ID');
pushLine_(groupId, text);
}
続けて設定します。
- 「プロジェクトの設定」(歯車)の「スクリプト プロパティ」に、
LINE_CHANNEL_ACCESS_TOKEN(値はYOUR_CHANNEL_ACCESS_TOKEN、発行したトークン)とLINE_STAFF_GROUP_ID(控えたグループID)を足す - 左の「トリガー」(時計)で「トリガーを追加」を押す
- 関数
onTrialFormSubmit、イベントのソース「スプレッドシートから」、イベントの種類「フォーム送信時」で保存する - 承認の画面で許可する
- フォームからテストで1件送り、受付のグループに通知が届くことを確かめる

X-Line-Retry-Key は、通信が途中で切れて同じ送信をやり直したときに、二重に届くのを防ぐ値です。キーは最初の送信から24時間有効で、同じキーで2回目を送ると、LINEは 409 を返して送りません。そのためコードでは 409 も送れたものとして扱っています。やり直すのは、500 番台のエラーと通信の失敗のときだけです。400 番台は送り方の間違いなので、何度送っても結果は変わりません。429 は送りすぎか、プランの通数の上限に当たったときに返ります。

ブロックされている相手や友だちでない相手に送っても、LINEは 200 を返しますが、実際には届きません。届いたかどうかはAPIの返事では分からないので、通知が来ないと言われたら、まず相手が友だちのままかを確かめます。
回答者のLINEとフォームの回答を結びつける
申込者にLINEでリマインドを送るには、その人のユーザーIDが要ります。ところが、Googleフォームには回答者のLINEのユーザーIDを知る手段がありません。ここが、LINEとフォームをつなぐときにいちばん工夫の要るところです。
やり方:LINEで送ってきた人に、その人用のフォームのURLを返す
サンプル料理教室では、次の流れにしました。
- リッチメニューの「体験レッスンを申し込む」の枠のアクションのタイプを、「リンク」ではなく「テキスト」にし、
体験を申し込むと送らせる - n8nがWebhookでそのメッセージを受け、
source.userIdを取り出す - n8nが、フォームの「LINE連携ID」の質問にユーザーIDを入れた事前入力のURLを作り、応答メッセージで返す
- 申込者はそのURLからフォームに答える。回答の「LINE連携ID」の列にユーザーIDが入る
事前入力のURLは、フォームの編集画面で作ります。
- フォームの編集画面の右上の「その他」(3つの点)から「フォームに事前入力する」を選ぶ
- 「LINE連携ID」の欄に
TESTと入れて「リンクを取得」を押す - コピーしたURLの中の
entry.123456789=TESTの数字を控える
n8nでは、応答メッセージの本文に次の式でURLを差し込みます。
https://docs.google.com/forms/d/e/YOUR_FORM_ID/viewform?usp=pp_url&entry.123456789={{ $json.body.events[0].source.userId }}
応答メッセージは、プランの通数に数えられません。この方法なら、フォームを開いてもらうところまでは通数を使いません。

この方法の限界
便利ですが、限界があります。知ったうえで使います。
- 事前入力は、質問の欄にあらかじめ値を入れておくしくみで、その質問は回答者の画面に出る。ヘルプには、その値を隠したり固定したりする設定の記載が無いので、回答者が書き換えられる前提で扱う
- URLを人に転送されると、転送された人の回答に、元の人のユーザーIDが入る
- そのため、回答のユーザーIDが本人のものだという保証は無い。リマインドや受付の連絡のように、間違って届いても大きな害が無い用途に限る
- 個人情報や支払いに関わる連絡を、この結びつけだけを頼りに送らない
本人だと確かめる必要があるなら、LIFF(LINEの中で開くWebアプリ)で自前のフォームを作り、LINEログインのIDトークンをサーバーで検証する方法になります。LIFFの liff.getProfile() でユーザーIDは取れますが、LINE Developersはその値をそのままサーバーに送らず、IDトークンを使うよう求めています。ここまでやるならGoogleフォームではなく、LINEの拡張ツールの予約・フォームの機能を使ったほうが早く済みます。
結びつけを確かめる
回答のLINE連携IDが空の人や、ユーザーIDの形(U と32桁の英数字)でない人もいます。予約一覧に =REGEXMATCH(F2,"^U[0-9a-f]{32}$") の列を足しておき、FALSE の人には受付が電話かメールで連絡します。
前日のリマインドをLINEで送る
予約一覧には、体験日・お名前・LINE連携ID・リマインド送信の列があるものとします。毎日18時に、翌日が体験日の人に送ります。
function sendTrialReminders() {
const sheet = SpreadsheetApp.getActive().getSheetByName('予約一覧');
const values = sheet.getDataRange().getValues();
const head = values[0];
const col = {
date: head.indexOf('体験日'),
name: head.indexOf('お名前'),
lineId: head.indexOf('LINE連携ID'),
status: head.indexOf('対応状況'),
sent: head.indexOf('リマインド送信')
};
const tz = 'Asia/Tokyo';
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const target = Utilities.formatDate(tomorrow, tz, 'yyyy-MM-dd');
for (let i = 1; i < values.length; i++) {
const row = values[i];
if (!(row[col.date] instanceof Date)) continue;
if (Utilities.formatDate(row[col.date], tz, 'yyyy-MM-dd') !== target) continue;
if (row[col.status] === 'キャンセル') continue;
if (row[col.sent]) continue;
if (!/^U[0-9a-f]{32}$/.test(row[col.lineId])) continue;
const text = row[col.name] + '様\n明日 ' +
Utilities.formatDate(row[col.date], tz, 'M月d日 H:mm') +
' から体験レッスンです。\nエプロンとハンドタオルをお持ちください。\n' +
'ご都合が悪くなった場合は、このトークでお知らせください。\nサンプル料理教室';
if (pushLine_(row[col.lineId], text)) {
sheet.getRange(i + 1, col.sent + 1).setValue(new Date());
}
}
}
pushLine_ は前の節と同じ関数を使います。トリガーは関数 sendTrialReminders、イベントのソース「時間主導型」、「日付ベースのタイマー」、「午後6時〜7時」で作ります。送ったら「リマインド送信」の列に日時を入れるので、同じ日に2回動いても二重には送りません。

体験日の列は、日付と時刻の両方が入った日時にしておきます。フォームの「日付」の質問で「時刻を含める」をオンにすると、日時で入ります。
送る通数と料金
Messaging APIのプッシュメッセージは、LINE公式アカウントのプランの通数に数えられます。数え方は「送った回数×送った人数」です。グループに送ったときは、グループの人数ぶん数えられます。1回のプッシュで吹き出しを5つまで送れ、吹き出しの数は通数に影響しません。ブロックしている人など、届かなかった相手は数えません。
| プラン(2026年10月時点・税別) | 月額 | 無料で送れる通数 | 追加で送る |
|---|---|---|---|
| コミュニケーションプラン | 0円 | 200通 | できない |
| ライトプラン | 5,000円 | 5,000通 | できない |
| スタンダードプラン | 15,000円 | 30,000通 | できる(単価は送る数で変わる。2026年10月1日に改定) |
応答メッセージ、あいさつメッセージ、LINEチャットでの手動のやりとりは通数に数えません。
サンプル料理教室の1か月を数えると、次のようになります。
| 送るもの | 件数 | 通数 |
|---|---|---|
| 体験申込の受付への通知(グループ3人) | 申込40件 | 120通 |
| 体験の前日リマインド | 40件 | 40通 |
| フォームのURLを返す応答メッセージ | 40件 | 0通 |
| 合計 | 160通 |
コミュニケーションプランの200通に収まりますが、会員のレッスンのリマインドまで送るとすぐに超えます。会員60人に月4回送るだけで240通です。超えそうなら、受付への通知をGoogle Chatに移して120通を浮かせるか、ライトプランにします。キャンペーンの一斉配信も同じ通数から引かれるので、配信の予定がある月は特に気をつけます。月の途中で上限に当たると、429 が返ってリマインドが止まります。
応用
日程の変更をLINEで受け付ける
リマインドに「ご都合が悪くなった場合は、このトークでお知らせください」と書いておくと、返事がLINEのチャットに届きます。n8nでWebhookを受け、source.userId で予約一覧の行を探し、対応状況を「変更希望」にして受付のChatに知らせます。返事そのものは受付がLINE Official Account Managerのチャットで手で返します。この返事は通数に数えません。
カレンダーとあわせる
体験日が決まったら、レッスンのカレンダーにも予定を入れておくと、講師が当日の人数を見られます。予約一覧からカレンダーへの登録はGASで書けます。リマインドの文面の時刻を、カレンダーの予定から取るようにしておけば、時刻を変えたときに文面との食い違いが起きません。
できないことと、その代わり
| できないこと | 代わりの手 |
|---|---|
| LINE Notifyで通知する | 2025年3月31日に終了。Messaging APIのプッシュメッセージで送る |
| Googleフォームで回答者のLINEのユーザーIDを自動で取る | LINEで送ってきた人に、ユーザーIDを事前入力したURLを返す |
| 事前入力したユーザーIDを隠す・書き換えられなくする | 本人確認が要るならLIFFとIDトークンで自前のフォームにするか、拡張ツールを使う |
| 友だちでない人、ブロックした人に送る | 送れない。電話かメールで連絡する |
| GASでLINEのWebhookの署名を確かめる | GASのWebアプリはヘッダーを読めない。受信はn8nで受ける |
| マルチキャストでグループに送る | グループにはプッシュでグループIDを指定して送る |
| 無料のプランで通数を気にせず送る | 200通を超えるならChatに寄せるか、ライトプランにする |
| フォームの確認画面にLINEの友だち追加を自動で出し分ける | 確認メッセージに友だち追加のURLを固定で書いておく |
よく詰まるところ
| 症状 | 原因 | 直し方 |
|---|---|---|
| 昔のコードで通知が来なくなった | LINE Notifyが2025年3月31日に終了した | Messaging APIのプッシュメッセージに書き換える |
401 が返る | チャネルアクセストークンの貼り間違い、または再発行して古いトークンを使っている | スクリプトプロパティのトークンを今のものに入れ直す |
200 が返るのに届かない | 相手が友だちでない、ブロックしている、またはIDが別のプロバイダーのもの | 相手に友だち追加を確かめてもらう。IDを同じチャネルのWebhookで取り直す |
400 が返る | to が空、または本文が5,000文字を超えている | 予約一覧のLINE連携IDの列と、本文の長さを確かめる |
429 が返り、月の後半に止まる | プランの無料の通数を使い切った | LINE Official Account Managerで今月の通数を確かめ、プランを上げるか通知をChatへ移す |
| グループに公式アカウントを招待できない | 「グループトーク・複数人トークへの参加を許可する」が無効 | 有効にしてから、もう一度招待してもらう |
| LINEの中でフォームを開くとログイン画面になる | 「回答を 1 回に制限する」がオン、または回答できる人が組織の中に絞られている | 「設定」の「回答」で「回答を 1 回に制限する」をオフにし、公開の「管理」で一般的なアクセスを広げる |
| フォームのLINE連携IDが空の回答がある | 事前入力のURLを通らず、リッチメニュー以外からフォームを開いた | 予約一覧で空の人を見つけ、電話かメールで連絡する |
| リマインドが2回届いた | リマインド送信の列を見ずに送っている、またはトリガーを2つ作った | 「トリガー」の画面で重複を消す。列に日時が入っている人は飛ばす |
| リマインドの時刻が9時間ずれている | スクリプトのタイムゾーンが日本でない | 「プロジェクトの設定」でタイムゾーンを東京にする |
Exception: 承認が必要です で止まる | トリガーを作った後にコードで新しいサービスを使い、再承認していない | エディタで一度手動で実行して許可する |