- 動作確認日:2026-10-01
- n8nのバージョン:2.41.3(Docker、セルフホスト)
① 結論:このワークフローでできること
Googleスプレッドシートに並べた「名前・メールアドレス・用件」の一覧を読み、まだ対応していない行の分だけ、Gmailの下書きを作ります。作った行は、シートの状態を「作成済み」に書き換えます。
- メールは送りません。Gmailの「下書き」に入るだけなので、中身を確かめてから自分で送れます。
- 状態が「作成済み」の行は飛ばすので、何回実行しても、同じ人の下書きは1回しか作られません。
- 使う認証情報(Credentials)は、Google SheetsとGmailの2つです。
② 完成イメージ

シートの mail-list タブに、次の3行を用意しました(名前やアドレスは、テスト用の架空のものです。example.com は、例として使うために予約されたドメインです)。
| name | topic | status | draftCreatedAt | |
|---|---|---|---|---|
| 山田テスト | yamada@example.com | 資料請求 | (空欄) | (空欄) |
| 佐藤テスト | sato@example.com | 見積もり | (空欄) | (空欄) |
| 鈴木テスト | suzuki@example.com | 資料請求 | 作成済み | 2026-09-30 10:00 |
WebhookのURLにGETでアクセスすると、次の文章が返ってきました(実際の出力です)。
2件の下書きを作りました
実行したあとのシートは、次のとおりです。空欄だった2行だけが「作成済み」になり、日時が入りました。
| name | topic | status | draftCreatedAt | |
|---|---|---|---|---|
| 山田テスト | yamada@example.com | 資料請求 | 作成済み | 2026-10-01 1:49 |
| 佐藤テスト | sato@example.com | 見積もり | 作成済み | 2026-10-01 1:49 |
| 鈴木テスト | suzuki@example.com | 資料請求 | 作成済み | 2026-09-30 10:00 |
Gmailの下書きには、次のような内容が2通入りました。
宛先:yamada@example.com
件名:【資料請求】お問い合わせありがとうございます
山田テスト 様
お問い合わせありがとうございます。
「資料請求」について、担当者から改めてご連絡いたします。
よろしくお願いいたします。
実行したときの件数の流れは、次のとおりです。
| ノード | 1回目 | 2回目(全部作成済み) |
|---|---|---|
| Webhook | 1 | 1 |
| Read List | 3 | 3 |
| Not Yet | 3(true:2、false:1) | 3(true:0、false:3) |
| Create Draft | 2 | 動かない |
| Mark Done | 2 | 動かない |
| Update Status | 2 | 動かない |
| Respond to Webhook | 2(返事は1回だけ) | 動かない |
2回目は、下書きが1通も作られませんでした。同じ人に二重に下書きを作らないことを確認できました。
③ 使うノードと必要なもの
必要なもの
- n8n(今回はバージョン2.41.3をDockerで動かしました)
- Googleスプレッドシートと、
mail-listタブ(1行目にname、email、topic、status、draftCreatedAtの見出し) - 認証情報:Google Sheets OAuth2 API と Gmail OAuth2 API(どちらも、Google Cloudで作った同じOAuthクライアントを使えます)
使うノード
| ノード | 役割 | 設定したこと |
|---|---|---|
| Webhook | 動作確認用の起動ボタン | Path:test-sheets-gmail-drafts、Respond:Using 'Respond to Webhook' Node |
| Google Sheets(Read List) | 一覧を読む | Operation:Get Row(s)、Sheet:By Name mail-list |
| If(Not Yet) | まだの行だけ通す | status が 作成済み と等しくない(is not equal to) |
| Gmail(Create Draft) | 下書きを作る | Resource:Draft、Operation:Create、To Email:{{ $json.email }} |
| Edit Fields(Mark Done) | シートに書き戻す内容を作る | email、status(作成済み)、draftCreatedAt |
| Google Sheets(Update Status) | 状態を書き換える | Operation:Update Row、Column to match on:email |
| Respond to Webhook | 作った件数を返す | Respond With:Text、本文:{{ $input.all().length }}件の下書きを作りました |

Not Yetでは、status が「作成済み」でない行だけを、true側に通しています。空欄の行も、ここで通ります。

Create Draftでは、ResourceをMessageではなく Draft にしています。ここをMessageの「Send」にすると、本当に送信されてしまうので注意してください。宛先は、Optionsの「To Email」で指定します。
件名と本文には、シートの列をそのまま差し込んでいます。
- 件名:
【{{ $json.topic }}】お問い合わせありがとうございます - 本文:
{{ $json.name }} 様から始まる文章
Mark Doneでは、シートに書き戻す3つの項目を作っています。Gmailノードの出力には下書きのIDしか入っていないので、メールアドレスは $('Not Yet').item.json.email で、1つ前のノードから取り出しています。
| 項目名 | 値 |
|---|---|
email | {{ $('Not Yet').item.json.email }} |
status | 作成済み |
draftCreatedAt | {{ $now.setZone('Asia/Tokyo').toFormat('yyyy-MM-dd HH:mm') }} |

Update Statusでは、照合する列(Column to match on)を email にしました。メールアドレスが同じ行の status と draftCreatedAt を書き換えます。
④ JSON
n8nの公開API(POST /api/v1/workflows)でこのJSONからワークフローを作成し、有効化してから、上の結果を確認しました。
使うときは、次の箇所を自分の環境に合わせて書き換えてください。
YOUR_SPREADSHEET_ID:スプレッドシートのID(URLの/d/と/editの間の文字列)。2か所あります。YOUR_CREDENTIAL_ID:n8nに登録したGoogle Sheetsの認証情報のID。2か所あります。YOUR_GMAIL_CREDENTIAL_ID:n8nに登録したGmailの認証情報のID
読み込んだあとに、ノードの画面で認証情報を選び直しても大丈夫です。
{
"name": "test-sheets-gmail-drafts",
"nodes": [
{ "parameters": { "path": "test-sheets-gmail-drafts", "responseMode": "responseNode", "options": {} },
"id": "aa100000-0001-4000-8000-000000000001", "name": "Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 0], "webhookId": "aa100000-aaaa-4000-8000-000000000001" },
{ "parameters": { "documentId": { "__rl": true, "value": "YOUR_SPREADSHEET_ID", "mode": "id" },
"sheetName": { "__rl": true, "value": "mail-list", "mode": "name" }, "options": {} },
"id": "aa100000-0002-4000-8000-000000000002", "name": "Read List", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [220, 0],
"credentials": { "googleSheetsOAuth2Api": { "id": "YOUR_CREDENTIAL_ID", "name": "Google Sheets account" } } },
{ "parameters": {
"conditions": {
"options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose", "version": 2 },
"conditions": [
{ "id": "s1", "leftValue": "={{ $json.status }}", "rightValue": "作成済み", "operator": { "type": "string", "operation": "notEquals" } }
],
"combinator": "and"
},
"looseTypeValidation": true,
"options": {} },
"id": "aa100000-0003-4000-8000-000000000003", "name": "Not Yet", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [440, 0] },
{ "parameters": {
"resource": "draft",
"subject": "=【{{ $json.topic }}】お問い合わせありがとうございます",
"emailType": "text",
"message": "={{ $json.name }} 様\n\nお問い合わせありがとうございます。\n「{{ $json.topic }}」について、担当者から改めてご連絡いたします。\n\nよろしくお願いいたします。",
"options": { "sendTo": "={{ $json.email }}" } },
"id": "aa100000-0004-4000-8000-000000000004", "name": "Create Draft", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [660, -100],
"webhookId": "aa100000-bbbb-4000-8000-000000000004",
"credentials": { "gmailOAuth2": { "id": "YOUR_GMAIL_CREDENTIAL_ID", "name": "Gmail account" } } },
{ "parameters": {
"assignments": { "assignments": [
{ "id": "u1", "name": "email", "value": "={{ $('Not Yet').item.json.email }}", "type": "string" },
{ "id": "u2", "name": "status", "value": "作成済み", "type": "string" },
{ "id": "u3", "name": "draftCreatedAt", "value": "={{ $now.setZone('Asia/Tokyo').toFormat('yyyy-MM-dd HH:mm') }}", "type": "string" }
] },
"options": {} },
"id": "aa100000-0005-4000-8000-000000000005", "name": "Mark Done", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [880, -100] },
{ "parameters": {
"operation": "update",
"documentId": { "__rl": true, "value": "YOUR_SPREADSHEET_ID", "mode": "id" },
"sheetName": { "__rl": true, "value": "mail-list", "mode": "name" },
"columns": { "mappingMode": "autoMapInputData", "value": {}, "matchingColumns": ["email"], "schema": [] },
"options": {} },
"id": "aa100000-0006-4000-8000-000000000006", "name": "Update Status", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [1100, -100],
"credentials": { "googleSheetsOAuth2Api": { "id": "YOUR_CREDENTIAL_ID", "name": "Google Sheets account" } } },
{ "parameters": { "respondWith": "text", "responseBody": "={{ $input.all().length }}件の下書きを作りました", "options": {} },
"id": "aa100000-0007-4000-8000-000000000007", "name": "Respond to Webhook", "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.1, "position": [1320, -100] }
],
"connections": {
"Webhook": { "main": [[{ "node": "Read List", "type": "main", "index": 0 }]] },
"Read List": { "main": [[{ "node": "Not Yet", "type": "main", "index": 0 }]] },
"Not Yet": { "main": [[{ "node": "Create Draft", "type": "main", "index": 0 }], []] },
"Create Draft": { "main": [[{ "node": "Mark Done", "type": "main", "index": 0 }]] },
"Mark Done": { "main": [[{ "node": "Update Status", "type": "main", "index": 0 }]] },
"Update Status": { "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
⑤ つまずきポイントと対処
このワークフロー自体は、1回目の実行でエラーなく動きました。ここでは、確認の途中で気づいたことを書きます。
対象の行がないとき、応答が空になる
- 起きたこと:全部の行が「作成済み」の状態で実行すると、
200は返ってきたものの、本文は空でした。 - 原因:Not Yetのtrue側に1件も流れないため、最後のRespond to Webhookまで処理が届きません。そのため、n8nは本文なしで応答を返しました。
- 対処:今回は、「本文が空なら、作る下書きがなかった」と判断することにしました。「0件でした」と返したい場合は、Not Yetの前に件数を数えるノードを入れる必要があります。
ResourceをMessageにすると送信されてしまう
- 気をつけること:GmailノードのResourceが
Messageのままだと、OperationのSendで実際にメールが送られます。 - 対処:下書きにとどめたいときは、Resourceを
Draft、OperationをCreateにします。テストのときは、宛先にexample.comのような、実在しないドメインを使うと安心です。
⑥ 応用アイデア
どれも、まだ試していないアイデアです。
- Webhookを「Schedule Trigger」に替えて、毎朝その日の分の下書きをまとめて作る
- 用件(topic)ごとに、件名や本文を変える(Switchノードで分ける)
- 下書きを確かめたうえで、Resourceを
MessageのSendに変えて、自動で送る - フォームの回答(Webhook × Google Sheetsのワークフロー)と組み合わせて、問い合わせが来たら返信の下書きまで作る
