AutomationFlowsSlack & Telegram › Daily AI Pitch Menu Outline via Claude

Daily AI Pitch Menu Outline via Claude

Original n8n title: Pitch Menu (outline)

02 — Pitch menu (outline). Uses googleSheets, httpRequest, telegram. Event-driven trigger; 9 nodes.

Event trigger★★★★☆ complexity9 nodesGoogle SheetsHTTP RequestTelegram
Slack & Telegram Trigger: Event Nodes: 9 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "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, '&amp;')\n  .replace(/</g, '&lt;')\n  .replace(/>/g, '&gt;');\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.

Pro

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 →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

checkProcess(old). Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.

Google Sheets, HTTP Request, Telegram +3
Slack & Telegram

checkProcess. Uses googleSheets, httpRequest, telegram, @n-octo-n/n8n-nodes-json-database. Event-driven trigger; 40 nodes.

Google Sheets, HTTP Request, Telegram +3
Slack & Telegram

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

Google Drive Trigger, Google Drive, Google Docs +3
Slack & Telegram

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

HTTP Request, Execute Workflow Trigger, Google Sheets +2
Slack & Telegram

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

Telegram Trigger, HTTP Request, Google Calendar +3