This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "02 \u2014 Pitch menu (outline)",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
0,
0
],
"id": "11111111-7777-7777-7777-111111111111",
"name": "Manual Trigger"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 30 7 * * *"
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.3,
"position": [
0,
200
],
"id": "11111111-7777-7777-7777-222222222222",
"name": "Schedule Trigger (daily 07:30)"
},
{
"parameters": {
"authentication": "serviceAccount",
"documentId": {
"__rl": true,
"value": "157MDKV96QneeVdVlWqnC2kjjQrIKVsljJFFmrrwvRl0",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "content-plan",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/157MDKV96QneeVdVlWqnC2kjjQrIKVsljJFFmrrwvRl0/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
240,
100
],
"id": "22222222-7777-7777-7777-333333333333",
"name": "Get All Topics",
"credentials": {
"googleApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Pick top 3 oldest draft+approved topics that haven't been published.\n// Drafts are eligible because the menu is exactly the picking step \u2014\n// reviewer flips one to 'approved' after seeing the menu, others stay draft.\nconst rows = $input.all().map(r => r.json);\nconst eligible = rows.filter(r =>\n ['draft', 'approved'].includes(r.status) &&\n !r.published_url\n);\n\nif (eligible.length === 0) {\n // No work \u2014 return empty so workflow stops gracefully\n return [];\n}\n\nconst sorted = eligible.sort(\n (a, b) => (a.created_at || '').localeCompare(b.created_at || '')\n);\nconst pick = sorted.slice(0, 3);\n\nif (pick.length < 3) {\n // Pad with whatever we have rather than fail \u2014 reviewer can pick from\n // fewer if the queue is small\n}\n\nconst batch_id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n\nreturn [{\n json: {\n batch_id,\n topics: pick.map(p => ({\n id: p.id,\n title: p.title,\n cluster: p.cluster,\n category: p.category,\n target_keyword: p.target_keyword,\n search_intent: p.search_intent,\n length: p.length,\n })),\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
480,
100
],
"id": "33333333-7777-7777-7777-444444444444",
"name": "Pick Top 3 Topics"
},
{
"parameters": {
"jsCode": "// Fetch outline system prompt from repo, build Claude API request\nconst promptRes = await this.helpers.httpRequest({\n method: 'GET',\n url: 'https://raw.githubusercontent.com/puffy-pet/puffy-automation/main/prompts/article-outline.md',\n});\nconst systemPrompt = typeof promptRes === 'string'\n ? promptRes\n : (promptRes.body || JSON.stringify(promptRes));\n\nconst input = $input.first().json;\n\nreturn [{\n json: {\n batch_id: input.batch_id,\n topics: input.topics,\n body: {\n model: 'claude-sonnet-4-6',\n max_tokens: 2000,\n temperature: 0.5,\n system: systemPrompt,\n messages: [{\n role: 'user',\n content: JSON.stringify({ topics: input.topics }, null, 2)\n }]\n }\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
720,
100
],
"id": "44444444-7777-7777-7777-555555555555",
"name": "Build Outline Payload"
},
{
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "anthropicApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.body) }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
960,
100
],
"id": "55555555-7777-7777-7777-666666666666",
"name": "Claude API (outlines)",
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Parse Claude's outline JSON, validate shape\nconst raw = $json.content[0].text;\nlet cleaned = raw.trim();\nif (cleaned.startsWith('```')) {\n cleaned = cleaned.replace(/^```(?:json)?\\s*\\n?/, '').replace(/\\n?```\\s*$/, '');\n}\n\nlet parsed;\ntry {\n parsed = JSON.parse(cleaned);\n} catch (e) {\n throw new Error(`Claude did not return valid outline JSON: ${cleaned.slice(0, 500)}`);\n}\n\nif (!Array.isArray(parsed.outlines) || parsed.outlines.length === 0) {\n throw new Error('Expected non-empty outlines array');\n}\n\nfor (const o of parsed.outlines) {\n if (!o.topic_id || !o.title || !o.hook || !Array.isArray(o.h2_list)) {\n throw new Error(`Outline missing required fields: ${JSON.stringify(o)}`);\n }\n if (/!/.test(o.title)) {\n throw new Error(`Outline title contains exclamation: ${o.title}`);\n }\n}\n\nconst ctx = $('Build Outline Payload').first().json;\n\nreturn [{\n json: {\n batch_id: ctx.batch_id,\n outlines: parsed.outlines,\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1200,
100
],
"id": "66666666-7777-7777-7777-777777777777",
"name": "Parse Outlines"
},
{
"parameters": {
"jsCode": "// Format the menu of 3 outlines into a Telegram HTML message.\nconst { batch_id, outlines } = $input.first().json;\n\nconst escapeHtml = (s) => String(s)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n\nconst emojis = ['1\ufe0f\u20e3', '2\ufe0f\u20e3', '3\ufe0f\u20e3', '4\ufe0f\u20e3', '5\ufe0f\u20e3'];\n\nconst sections = outlines.map((o, i) => {\n const h2s = (o.h2_list || []).map(s => `\u00b7 ${escapeHtml(s)}`).join('\\n');\n return `${emojis[i]} <code>${escapeHtml(o.topic_id)}</code>\\n<b>${escapeHtml(o.title)}</b>\\n<i>${escapeHtml(o.hook)}</i>\\n${h2s}`;\n}).join('\\n\\n\u2014\u2014\u2014\\n\\n');\n\nconst text = `\ud83d\udccb <b>\u0412\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043d\u0430 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u2014 \u0432\u044b\u0431\u0435\u0440\u0438 \u0447\u0442\u043e \u043f\u0438\u0441\u0430\u0442\u044c</b>\\n\\nClaude \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 3 \u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f. \u041e\u0442\u043a\u0440\u043e\u0439 <code>content-plan</code> tab \u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u044c <b>approved</b> \u0442\u043e\u043b\u044c\u043a\u043e \u0432 \u0442\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435, \u0447\u0442\u043e \u0437\u0430\u0446\u0435\u043f\u0438\u043b\u0430. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0441\u0442\u0430\u0432\u044c <b>draft</b> \u0438\u043b\u0438 \u043f\u043e\u0441\u0442\u0430\u0432\u044c <b>skipped</b>.\\n\\n${sections}\\n\\n<i>\u0417\u0430\u0432\u0442\u0440\u0430 \u0432 8:00 02a \u0432\u043e\u0437\u044c\u043c\u0451\u0442 \u0442\u0435\u043c\u0443 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0442\u044b \u043f\u043e\u043c\u0435\u0442\u0438\u043b approved \u2192 \u043d\u0430\u043f\u0438\u0448\u0435\u0442 \u0441\u0442\u0430\u0442\u044c\u044e \u2192 \u043f\u0440\u0438\u0448\u043b\u0451\u0442 \u0441\u044e\u0434\u0430 \u043d\u0430 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 /approve.</i>\\n\\n<i>batch: <code>${escapeHtml(batch_id)}</code></i>`;\n\nreturn [{ json: { text, batch_id } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1440,
100
],
"id": "77777777-7777-7777-7777-888888888888",
"name": "Format Pitch Menu"
},
{
"parameters": {
"chatId": "-5184755657",
"text": "={{ $json.text }}",
"additionalFields": {
"appendAttribution": false,
"parse_mode": "HTML",
"disable_web_page_preview": true
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1680,
100
],
"id": "88888888-7777-7777-7777-999999999999",
"name": "Send Pitch Menu",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Get All Topics",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger (daily 07:30)": {
"main": [
[
{
"node": "Get All Topics",
"type": "main",
"index": 0
}
]
]
},
"Get All Topics": {
"main": [
[
{
"node": "Pick Top 3 Topics",
"type": "main",
"index": 0
}
]
]
},
"Pick Top 3 Topics": {
"main": [
[
{
"node": "Build Outline Payload",
"type": "main",
"index": 0
}
]
]
},
"Build Outline Payload": {
"main": [
[
{
"node": "Claude API (outlines)",
"type": "main",
"index": 0
}
]
]
},
"Claude API (outlines)": {
"main": [
[
{
"node": "Parse Outlines",
"type": "main",
"index": 0
}
]
]
},
"Parse Outlines": {
"main": [
[
{
"node": "Format Pitch Menu",
"type": "main",
"index": 0
}
]
]
},
"Format Pitch Menu": {
"main": [
[
{
"node": "Send Pitch Menu",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "00000000-0000-0000-0000-000000000030",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "02-pitch-menu-puffy",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApigoogleApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
02 — Pitch menu (outline). Uses googleSheets, httpRequest, telegram. Event-driven trigger; 9 nodes.
Source: https://github.com/puffy-pet/puffy-automation/blob/ccb9955f1d770baf49926b45384fefb23b4b4bc7/workflows/02-pitch-menu.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.
checkProcess. Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.
This template monitors Google Drive folder for new files, extracts text from PDFs, images, text files, CSVs, and Google Docs., reads images with meta/llama-3.2-11b-vision-instruct, structures the resu
This workflow provides a complete solution for handling Telegram Stars payments, invoicing and refunds using n8n. It automates the process of sending invoices, managing pre-checkout approvals, recordi
clients kept booking meetings during my prayer times. i'd either miss a prayer or scramble to reschedule. the problem wasn't the clients — it was that my calendar had no blocked windows for salah. i n