LINEに送った文章をClaudeに処理させる|GAS中継の実装コードと詰まりどころ4つ
LINEに送った文章をClaudeに処理させる|GAS中継の実装コードと詰まりどころ4つ
「LINEに送った文章を、AIが読み取って処理してくれたら」。思いついたことをLINEに送るだけでタスク表に整理されて登録される、問い合わせ文が要約されて担当者に届く——こうした仕組みは、実は追加のサーバーを立てずに作れます。
結論から書きます。LINEとClaudeをつなぐ最小構成は次の3段です。
- LINE公式アカウント(Messaging API)が、送られたメッセージをWebhookで通知する
- GAS(Google Apps Script)の doPost 関数がそれを受け取り、Claude APIに文章を渡す
- Claudeが返した結果をスプレッドシートに書き込み、必要ならLINEに返信する
中継サーバーの役割をGASのWebアプリが担うので、レンタルサーバーもVPSも不要です。かかる費用はClaude APIの利用料だけで、短い文章の処理なら1通あたり1円前後(使うモデルによってはその数分の一)に収まります。
弊社ではこの構成の土台になる「LINE→GAS→スプレッドシート」のタスク登録システムを2026年から本番運用しており、Claude側からGAS経由で同じシートに書き込む経路も稼働しています。全体像と設計思想は既存記事「Claude×LINE連携をGASで実装|送るだけでタスク自動登録」に書いたので、本記事はその実装編です。コードと、実際に運用して踏んだ「詰まりどころ」を中心にまとめます。
最小実装コード:doPost → Claude API → シート
GAS側のコードは3つの関数に分かれます。まずLINEからのWebhookを受けるdoPostです。
function doPost(e) {
const data = JSON.parse(e.postData.contents);
const event = data.events && data.events[0];
// テキストメッセージ以外(スタンプ・画像・検証リクエスト)は無視して200を返す
if (!event || event.type !== "message" || event.message.type !== "text") {
return ContentService.createTextOutput(JSON.stringify({status: "ignored"}));
}
const userText = event.message.text;
const parsed = callClaude(userText);
saveToSheet(parsed, userText);
replyToLine(event.replyToken, "登録しました:" + parsed.task);
return ContentService.createTextOutput(JSON.stringify({status: "ok"}));
}
次にClaude APIの呼び出しです。GASにはSDKがないので、UrlFetchAppでMessages APIを直接叩きます。APIキーはコードに書かず、スクリプトプロパティに保存して読み出します。
function callClaude(userText) {
const apiKey = PropertiesService.getScriptProperties().getProperty("ANTHROPIC_API_KEY");
const payload = {
model: "claude-opus-4-8",
max_tokens: 1000,
system: "ユーザーの文章からタスクを抽出し、JSONだけを返す。キーはtask, deadline, priority。読み取れない項目は空文字にする。",
messages: [{ role: "user", content: userText }]
};
const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": apiKey,
"anthropic-version": "2023-06-01"
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const body = JSON.parse(res.getContentText());
return JSON.parse(body.content[0].text);
}
最後にシート追記と返信です。
function saveToSheet(parsed, original) {
const sheet = SpreadsheetApp.openById("シートID").getSheetByName("タスク管理");
sheet.appendRow([new Date(), "LINE", parsed.task, parsed.deadline, parsed.priority, original]);
}
function replyToLine(replyToken, text) {
const token = PropertiesService.getScriptProperties().getProperty("LINE_CHANNEL_ACCESS_TOKEN");
UrlFetchApp.fetch("https://api.line.me/v2/bot/message/reply", {
method: "post",
contentType: "application/json",
headers: { "Authorization": "Bearer " + token },
payload: JSON.stringify({ replyToken: replyToken, messages: [{ type: "text", text: text }] })
});
}
これをWebアプリとしてデプロイし、発行された /exec のURLをLINE Developersの Webhook URL に設定すれば骨格は完成です。設計上の注意が1つあります。LINEの応答用トークン(replyToken)は短時間で失効し1回しか使えないため、Claudeの処理に時間がかかる構成では、返信を諦めてシート記録だけにするか、返信をpush送信に切り替える判断が必要になります。
なお、モデルは用途で選びます。上のコードでは高精度なclaude-opus-4-8を指定していますが、短文からのタスク抽出のような軽い処理なら、より安価な軽量モデルに差し替えても実用になります。API料金の詳しい試算は別記事に譲り、本記事は先に「動かなくなるポイント」を潰します。
詰まりどころ1:curlでテストするとdoPostが動かない
実装して最初にぶつかりやすいのがこれです。GASのWebアプリにcurlでPOSTしてテストすると、正しく書いたはずのdoPostが反応しません。
原因はGASのリダイレクト仕様です。GASのWebアプリはPOSTを受けると一度リダイレクトを返し、curlはリダイレクト先へのリクエストをPOSTからGETに変えてしまいます。結果、GAS側ではdoGetが呼ばれ、doPostは一度も実行されません。コードのバグではないので、コードをいくら見直しても直りません。
テストはPythonのurllib.requestなど、リダイレクト後もPOSTを維持するクライアントで行うのが正解です。弊社のClaude→GAS登録経路も、この理由で送信処理をPythonに統一しています。なお、LINEプラットフォームからの本番Webhookはこの問題の影響を受けないため、「curlでは動かないがLINEからは動く」という状態は正常です。切り分けを間違えないでください。
詰まりどころ2:コードを直しても本番に反映されない
GASのWebアプリは「デプロイした時点のバージョン」が固定で公開されます。エディタでコードを修正して保存しても、本番の /exec URL の挙動は変わりません。これを知らないと「直したはずのバグが直らない」状態で時間を溶かします。
正しい手順は、デプロイの管理画面から同じデプロイの「バージョンを更新」を行うことです。「新しいデプロイ」を作ると /exec のURL自体が変わってしまい、LINE Developers側のWebhook URLを貼り替えない限り、旧バージョンが動き続けます。URL貼り替え漏れは「修正が反映されない」の典型原因なので、運用ではURLが変わらないバージョン更新を標準にしておくと事故が減ります。
ここは弊社でも運用ルール化するまで手戻りが出やすかったポイントで、逆に言えば「同じURLのままバージョン更新」を徹底すれば、LINE側の設定には二度と触らずに済みます。
詰まりどころ3:返信が二重に来る(応答メッセージとの競合)
Webhook連携を有効にしたあと、LINE公式アカウント標準の「応答メッセージ」「あいさつメッセージ」をONのままにしていると、Bot側の返信と標準機能の自動返信が両方送られ、ユーザーには二重に届きます。
正しい設定は、応答メッセージ・あいさつメッセージをOFF、WebhookをONです。弊社が美容室グループ向けに本番運用している口コミ通知Bot(友だち追加時に登録案内を自動返信する構成)でも、この設定で二重返信を防いでいます。あいさつをBot側で返すなら標準あいさつは切る、標準機能を使うならBot側では返さない、と役割をどちらかに寄せるのが原則です。
応答モードまわりの優先関係で「そもそも反応しない」パターンにはまった場合は、切り分け手順を「公式LINEのキーワード応答が反応しない原因」にまとめています。
詰まりどころ4:push送信が400エラーで落ちるときの切り分け順
返信ではなくpush送信(Botから能動的に送る)を使う構成で、「トークンも正しい、ペイロードの形式も正しい、なのに400 Failed to send messages」という状況が起こり得ます。弊社でも物販企業のグループ通知構築で実際に遭遇しました。
このとき疑う順番を間違えると長引きます。実体験からの推奨順は次の通りです。
- まずチャネルアクセストークンの取り違え(別チャネルのトークンを使っていないか)
- 次にペイロード形式(messages配列・型)
- それでも直らなければ送信経路の競合を疑う。同じ公式アカウントのWebhookを別の拡張ツールが使っている構成や、グループへの参加がそのツール経由になっている場合、Messaging APIからの直接pushが通らないことがある
- 429が返る場合はエラーではなくプランの送信通数上限。実装ではなく契約プランの問題
特に3つ目は見落としやすいポイントです。宛先IDやトークンを何度も疑う前に、「この公式アカウントのWebhookを今、誰が握っているか」を確認する方が早く着地します。
どこまでClaudeに任せるかの判断基準
最後に設計の話です。実は弊社の本番タスク登録は、LINEからの入力を「内容・期限・優先度」の定型フォーマットで受けており、この範囲ならClaude APIを挟まなくても動きます。Claudeを中継させる価値が出るのは次の条件に当てはまるときです。
- 入力が自然文で、フォーマットを強制したくない(家族やスタッフにも使わせる場合など)
- 表記ゆれの吸収が必要(「来週金曜まで」「7/31」「月末」を同じ期限フィールドに落とす)
- 分類・要約・振り分けの判断が入る(問い合わせ内容から担当者を決める等)
逆に、入力者が自分だけで定型入力が苦にならないなら、GAS内の文字列処理で十分です。API利用料もレイテンシもゼロで済みます。
Claudeを挟む場合は、失敗時の逃げ道を必ず作ってください。AIの応答は稀に指定したJSONにならないことがあるため、パースに失敗したら原文をそのままシートの末尾列に記録して終わる設計にしておくと、メッセージを取りこぼしません。上のコードでも原文(original)を常に保存しているのはこのためです。
まとめ
LINEに送った文章をClaudeに処理させる構成は、GASのdoPostを中継にすればサーバーなしで実装できます。ただし実装よりも、curlテストが効かないリダイレクト仕様、デプロイのバージョン運用、応答メッセージとの競合、push送信の経路問題といった「GAS×LINE特有の詰まりどころ」の方が時間を奪います。この4つを先に知っておけば、骨格は半日で動くはずです。
graciautoでは、LINE公式アカウントとAI・スプレッドシートを組み合わせた業務自動化の設計・構築を支援しています。「うちの業務ならどこまで自動化できるか」といったご相談もお気軽にどうぞ。