# templates

name: Auto-Generate Meeting Attendee Research with GPT-4o, Google Calendar, and Gmail
author_name: Adam Janes
author_username: adamjanes
nodes_used: ["n8n-nodes-base.httpRequest","n8n-nodes-base.gmail"]
categories: []
created_at: 2025-04-30T03:54:36.015Z
updated_at: 2025-04-30T03:54:36.015Z
url: https://n8n.io/workflows/3796
scraped_at: 2025-07-06 21:16:30
description:
  How it works:

  Whenever a new event is scheduled on your Google Calendar, this workflow generates a Meeting Briefing email, giving an overview of each person on the call and the company they work for.

  It makes use of the web search tool on the OpenAI Responses API to make lookups.

  The workflow triggers when a new event is added to the calendar, loops over each attendee, generating reports on each person and their company, collates the results, and sends the briefing as an email.

  Set up steps:

  Add your credentials for Google Calendar (for viewing events) and Gmail (to send the email)
  Add your OpenAI credentials as a Header Auth on the Company Search and Person Search nodes.
  	Name: Authorization
  	Value: Bearer {{ YOUR_API_KEY }}
  Edit the "Edit Fields" node with the email that you want to send the briefing to, and a short bit of context about yourself.

workflow_json:
  {"meta":{"instanceId":"45e293393b5dd8437fb351e5b1ef5511ef67e6e0826a1c10b9b68be850b67593"},"nodes":[{"id":"7976731d-692d-45f8-b986-3f82d998dfa0","name":"Research Company","type":"n8n-nodes-base.httpRequest","position":[600,780],"parameters":{"url":"https://api.openai.com/v1/responses","options":{},"requestMethod":"POST","authentication":"headerAuth","jsonParameters":true,"bodyParametersJson":"={{\n  JSON.stringify({\n    model: \"gpt-4o\",\n    tools: [{ type: \"web_search_preview\" }],\n    input: $json.prompt\n  })\n}}","queryParametersJson":"{}","headerParametersJson":"{}"},"credentials":{"httpHeaderAuth":{"id":"rhDo5pdVQQsBgcVZ","name":"Header Auth account 2"}},"typeVersion":1},{"id":"2f123bde-a5a0-4828-81e8-b875ac27d081","name":"Research Person","type":"n8n-nodes-base.httpRequest","position":[940,960],"parameters":{"url":"https://api.openai.com/v1/responses","options":{},"requestMethod":"POST","authentication":"headerAuth","jsonParameters":true,"bodyParametersJson":"={{\n  JSON.stringify({\n    model: \"gpt-4o\",\n    tools: [{ type: \"web_search_preview\" }],\n    input: $json.prompt\n  })\n}}","queryParametersJson":"{}","headerParametersJson":"{}"},"credentials":{"httpHeaderAuth":{"id":"rhDo5pdVQQsBgcVZ","name":"Header Auth account 2"}},"typeVersion":1},{"id":"07131cea-4600-479f-9048-3e1ec26dac25","name":"Google Calendar Trigger","type":"n8n-nodes-base.googleCalendarTrigger","position":[-1000,940],"parameters":{"options":{},"pollTimes":{"item":[{"mode":"everyMinute"}]},"triggerOn":"eventCreated","calendarId":{"__rl":true,"mode":"list","value":"youremail@example.com","cachedResultName":"Your Name Here"}},"credentials":{"googleCalendarOAuth2Api":{"id":"gpYtW24uwPf0eJEq","name":"Google Calendar account"}},"typeVersion":1},{"id":"fece4fec-b5e5-43ee-8bb2-64093729137a","name":"Filter Out Myself","type":"n8n-nodes-base.filter","position":[-320,940],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"a45fab6b-2017-4740-a7a2-dfc90bc2eafb","operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $json.self }}","rightValue":""}]}},"typeVersion":2.2},{"id":"c25cf9a0-99b9-4e52-8852-0824ff53982c","name":"Split Out Attendees","type":"n8n-nodes-base.splitOut","position":[-480,940],"parameters":{"options":{},"fieldToSplitOut":"=attendees"},"typeVersion":1},{"id":"e7709b40-db55-4b4f-8953-218b96d38d73","name":"For Each Attendee","type":"n8n-nodes-base.splitInBatches","position":[-40,940],"parameters":{"options":{}},"typeVersion":3},{"id":"5db7b2b5-078e-4b3a-b8b6-d12903127a93","name":"Is Company Email?","type":"n8n-nodes-base.if","position":[260,960],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"2e0ad575-3652-4981-ad78-e76d95880448","operator":{"type":"string","operation":"notRegex"},"leftValue":"={{ $('For Each Attendee').item.json.email }}","rightValue":"@(gmail\\.com|hotmail\\.com|yahoo\\.com|outlook\\.com|icloud\\.com|aol\\.com|live\\.com|msn\\.com|protonmail\\.com|me\\.com|mail\\.com|gmx\\.com|yandex\\.com)"}]}},"typeVersion":2.2},{"id":"14e226d4-7f42-4da3-b941-9c69facbbbf6","name":"Combine All Research","type":"n8n-nodes-base.aggregate","position":[260,260],"parameters":{"options":{},"aggregate":"aggregateAllItemData"},"typeVersion":1},{"id":"599fb5b6-8426-4edf-bae8-34ad69aa68e9","name":"Collect Fields","type":"n8n-nodes-base.set","position":[1100,960],"parameters":{"options":{},"assignments":{"assignments":[{"id":"f4b7dbc5-8f43-4cb7-aa59-508822625152","name":"person","type":"string","value":"={{ $json.output[1].content[0].text }}"},{"id":"28988743-7e98-41c3-a564-0e507f8a69af","name":"company","type":"string","value":"={{ $('For Each Attendee').item.json.email.match(/@(gmail\\.com|hotmail\\.com|yahoo\\.com|outlook\\.com|icloud\\.com|aol\\.com|live\\.com|msn\\.com|protonmail\\.com|me\\.com|mail\\.com|gmx\\.com|yandex\\.com)/) ? 'No company information found.' : $('Research Company').item.json.output[1].content[0].text }}"},{"id":"ed7cc918-4b08-4de8-a21e-7410cfe6b6cb","name":"email","type":"string","value":"={{ $('For Each Attendee').item.json.email }}"}]}},"typeVersion":3.4},{"id":"d226f2f5-9671-49b7-bd3d-eea8896aee87","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-1040,620],"parameters":{"color":7,"width":880,"height":700,"content":"## 1. New Google Calendar Event Detected\n\nOur workflow is triggered when a new calendar event comes in. \n\nThe event gives us access to a list of attendees which we can loop over in the next step. We need to filter out ourselves if we are in the meeting too!"},"typeVersion":1},{"id":"89881dac-69cb-42fd-995c-bc459eab28a5","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[200,620],"parameters":{"color":7,"width":1120,"height":700,"content":"## 2. Research Attendee + Company\n\nAPI calls are made to the OpenAI Responses API using the new web search preview endpoint. This allows us to search the web for any mentions of each attendee. If the email address is a company email, we also make a search request to find out about the company. We use some context about ourself (in the \"Set Context\" node) so that the LLM can make an educated guess if there are many people with the same name."},"typeVersion":1},{"id":"2a7f467e-cd0c-45f3-bbcd-9b37746b74ef","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[200,0],"parameters":{"color":7,"width":1120,"height":580,"content":"## 3. Generate + Send Report\n\nFinally, we combine all the data from the meeting attendees into a report. The report gets written in Markdown, converted into HTML, and the send via the Gmail API."},"typeVersion":1},{"id":"d04cf49a-d1fa-4019-9a98-01ec64bd6a37","name":"Write HTML","type":"n8n-nodes-base.markdown","position":[440,260],"parameters":{"mode":"markdownToHtml","options":{"tables":true},"markdown":"=### Meeting Briefing\n\n{{ \n\n$json.data.reduce((acc, entry, index) => acc + (`\n\n### Person ${index + 1} (${entry.email}):\n\n${entry.person}\n\n### Person ${index + 1} Company:\n\n${entry.company}\n\n---`)\n\n, '').trim().replace(/---$/, '')\n\n}}"},"typeVersion":1},{"id":"ac2a56db-2d80-4412-8985-a29577db5bcb","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-840,1100],"parameters":{"width":310,"height":200,"content":"## Edit Here\nEdit a few variables here to get started:\n- **context**: Some information about you to help the web search return the right people. \n- **email**: The email that you want to send the report to."},"typeVersion":1},{"id":"d32e4220-78fa-4581-abd3-ceff4e95641a","name":"Edit Fields","type":"n8n-nodes-base.set","position":[-740,940],"parameters":{"options":{},"assignments":{"assignments":[{"id":"ad442334-0219-4297-91c3-03575920d9b9","name":"context","type":"string","value":"I am working in web development, based in Singapore/Australia, and I work with startups"},{"id":"46cff036-7624-4682-8a22-966a5c46c7b5","name":"email","type":"string","value":"youremail@example.com"},{"id":"c9b83d56-8b24-4767-bc83-0eb0b5f62986","name":"attendees","type":"array","value":"={{ $json.attendees }}"}]}},"typeVersion":3.4},{"id":"600667b6-aae3-4a9e-a71c-a0819921a823","name":"Send Report","type":"n8n-nodes-base.gmail","position":[600,260],"webhookId":"86c63a4a-64e7-41e5-b657-c80b59dce562","parameters":{"sendTo":"={{ $('Edit Fields').item.json.email }}","message":"={{ $json.data }}","options":{"appendAttribution":false},"subject":"=Meeting Briefing: {{ $('Google Calendar Trigger').item.json.summary }} ({{ new Date($('Google Calendar Trigger').item.json.start.dateTime).format(\"dd/MM/yyyy\") }})"},"credentials":{"gmailOAuth2":{"id":"aXTuNMJaYuKFOKTa","name":"Gmail account"}},"typeVersion":2.1},{"id":"863c58b1-3b88-4b25-9191-31c77c2911cd","name":"Person Prompt","type":"n8n-nodes-base.set","position":[780,960],"parameters":{"options":{},"assignments":{"assignments":[{"id":"7096cd1e-179c-4230-b869-73f7cb1a9ff9","name":"prompt","type":"string","value":"=I have a call scheduled with {{ $('For Each Attendee').item.json.email }} Please find out as much as you can about the owner of this email address. \n\n- What do they do? \n- What are their interests? \n- What might I not know about them?\n\n{{ $('For Each Attendee').item.json.email.match(/@(gmail\\.com|hotmail\\.com|yahoo\\.com|outlook\\.com|icloud\\.com|aol\\.com|live\\.com|msn\\.com|protonmail\\.com|me\\.com|mail\\.com|gmx\\.com|yandex\\.com)/) ? '' : `Make sure to crawl their company website (http:/$('For Each Attendee').item.json.email.split('@')[1]}) to see if there's anything there.` }} \n\nFor context: {{ $('Edit Fields').item.json.email }}. If there is any ambiguity, use this information to find the most likely person to be meeting with me.\n\nDon't tailor your answer to this context - stay objective about the person only. Make your answer less than 100 words."}]}},"typeVersion":3.4},{"id":"dbc54bdb-1b50-44ae-a3d2-b4ab33d1ecc3","name":"Company Prompt","type":"n8n-nodes-base.set","position":[440,780],"parameters":{"options":{},"assignments":{"assignments":[{"id":"9d1121f3-a5a6-4f73-8726-0a84cad94e77","name":"prompt","type":"string","value":"=Check out the website http://{{ $('For Each Attendee').item.json.email.split(\"@\")[1] }}). \n\n- What does this company do? \n- What problem do they solve? \n- What is their business model? \n\nFor context about me: {{ $('Edit Fields').item.json.context }}.\n\nDon't mention anything about this context in your answer - stay objective about the company. Make your answer less than 100 words. \n\nIf you are unable to find a company at this URL, just write 'Company Not Found'."}]}},"typeVersion":3.4}],"pinData":{},"connections":{"Write HTML":{"main":[[{"node":"Send Report","type":"main","index":0}]]},"Edit Fields":{"main":[[{"node":"Split Out Attendees","type":"main","index":0}]]},"Person Prompt":{"main":[[{"node":"Research Person","type":"main","index":0}]]},"Collect Fields":{"main":[[{"node":"For Each Attendee","type":"main","index":0}]]},"Company Prompt":{"main":[[{"node":"Research Company","type":"main","index":0}]]},"Research Person":{"main":[[{"node":"Collect Fields","type":"main","index":0}]]},"Research Company":{"main":[[{"node":"Person Prompt","type":"main","index":0}]]},"Filter Out Myself":{"main":[[{"node":"For Each Attendee","type":"main","index":0}]]},"For Each Attendee":{"main":[[{"node":"Combine All Research","type":"main","index":0}],[{"node":"Is Company Email?","type":"main","index":0}]]},"Is Company Email?":{"main":[[{"node":"Company Prompt","type":"main","index":0}],[{"node":"Person Prompt","type":"main","index":0}]]},"Split Out Attendees":{"main":[[{"node":"Filter Out Myself","type":"main","index":0}]]},"Combine All Research":{"main":[[{"node":"Write HTML","type":"main","index":0}]]},"Google Calendar Trigger":{"main":[[{"node":"Edit Fields","type":"main","index":0}]]}}}


name: Create a Session-Based Telegram Chatbot with GPT-4o-mini and Google Sheets
author_name: Davide
author_username: n3witalia
categories: []
created_at: 2025-04-30T08:31:26.629Z
updated_at: 2025-04-30T08:31:26.629Z
url: https://n8n.io/workflows/3798
scraped_at: 2025-07-06 21:16:30
description:
  How It Works  
  This workflow creates an AI-powered Telegram chatbot with session management, allowing users to:  
  Start new conversations** (/new).  
  Check current sessions** (/current).  
  Resume past sessions** (/resume).  
  Get summaries** (/summary).  
  Ask questions** (/question).  

  Key Components:  
  Session Management**:  
    Uses Google Sheets to track active/expired sessions (storing SESSION IDs and STATE).  
    /new creates a session; /resume reactivates past ones.  
  AI Processing**:  
    OpenAI GPT-4 generates responses with contextual memory (via Simple Memory node).  
    Summarization: Condenses past conversations when requested.  
  Data Logging**:  
    All interactions (prompts/responses) are saved to Google Sheets for audit and retrieval.  
  User Interaction**:  
    Telegram commands trigger specific actions (e.g., /question [query] fetches answers from session history).  

  Main Advantages

  1. Multi-session Handling  
  Each user can create, manage, and switch between multiple sessions independently, perfect for organizing different conversations without confusion.

  2. Persistent Memory  
  Conversations are stored in Google Sheets, ensuring that chat history and session states are preserved even if the server or n8n instance restarts.

  3. Commands for Full Control  
  With intuitive commands like /new, /current, /resume, /summary, and /question, users can manage sessions easily without needing a web interface.

  4. Smart Summarization and Q&A  
  Thanks to OpenAI models, the workflow can summarize entire conversations or answer specific questions about past discussions, saving time and improving the chatbot’s usability.

  5. Easy Setup and Scalability  
  By using Google Sheets instead of a database, the workflow is easy to clone, modify, and deploy — ideal for quick prototyping or lightweight production uses.

  6. Modular and Extensible  
  Each logical block (new session, get current session, resume, summarize, ask question) is modular, making it easy to extend the workflow with additional features like analytics, personalized greetings, or integrations with CRM systems.

  Setup Steps  

  Prerequisites:  
  Telegram Bot Token**: Create via BotFather.  
  Google Sheets**:  
    Duplicate this template.  
    Two sheets: Session (active/inactive sessions) and Database (chat logs).  
  OpenAI API Key**: For GPT-4 responses.  

  Configuration:  
  Telegram Integration:  
     Add your bot token to the Telegram Trigger and Telegram Send nodes.  

  Google Sheets Setup:  
     Authenticate the Google Sheets nodes with OAuth.  
     Ensure sheet names (Session, Database) and column mappings match the template.  

  OpenAI & Memory:  
     Add your API key to the OpenAI Chat Model nodes.  
     Adjust contextWindowLength in the Simple Memory node for conversation history length.  

  Testing:  
     Use Telegram commands to test:  
       /new: Starts a session.  
       /question [query]: Tests AI responses.  
       /summary: Checks summarization.  

  Deployment:  
     Activate the workflow; the bot will respond to Telegram messages in real-time.  

  Need help customizing?  
  Contact me for consulting and support or add me on Linkedin. 

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.telegram","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.chainSummarization","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.memoryBufferWindow"]

workflow_json:
  {"id":"A7dRnMf9WybO8O02","meta":{"instanceId":"a4bfc93e975ca233ac45ed7c9227d84cf5a2329310525917adaf3312e10d5462","templateCredsSetupCompleted":true},"name":"Telegram ChatBot with multiple sessions","tags":[],"nodes":[{"id":"d3104851-90ec-4f0c-ab4d-aee5a6faf81b","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[640,1180],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"4zwP0MSr8zkNvvV9","name":"OpenAi account"}},"typeVersion":1.2},{"id":"e25050a1-e49b-4b46-8452-ce7b3388c7f3","name":"Simple Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[780,1180],"parameters":{"sessionKey":"={{ $('Get session').item.json.SESSION }}","sessionIdType":"customKey","contextWindowLength":100},"typeVersion":1.3},{"id":"d50951ae-e9a2-492c-8ff5-a039ca6975e4","name":"Get message","type":"n8n-nodes-base.telegramTrigger","position":[-520,-60],"webhookId":"8d87dcf7-1608-4255-a1f1-03b700a42f0e","parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"1bc9aa0f-8af8-47b8-b7d7-6bfd9280b65f","name":"Command or text?","type":"n8n-nodes-base.switch","position":[-20,-60],"parameters":{"rules":{"values":[{"outputKey":"New session","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"955f3c39-0732-4a63-b3f7-70ab0753a68a","operator":{"type":"string","operation":"startsWith"},"leftValue":"={{ $('Get message').item.json.message.text }}","rightValue":"/new"}]},"renameOutput":true},{"outputKey":"Current session","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"790f64bc-f3cc-4ef4-9d43-80853742fee6","operator":{"type":"string","operation":"startsWith"},"leftValue":"={{ $('Get message').item.json.message.text }}","rightValue":"/current"}]},"renameOutput":true},{"outputKey":"Resume session","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"39f35352-aee4-4a16-b82f-7f58ab8120f0","operator":{"type":"string","operation":"startsWith"},"leftValue":"={{ $('Get message').item.json.message.text }}","rightValue":"/resume"}]},"renameOutput":true},{"outputKey":"Summary ","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"270d71bf-8ecf-46e7-a601-cc5d1dc58e72","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $('Get message').item.json.message.text }}","rightValue":"/summary"}]},"renameOutput":true},{"outputKey":"Question","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"55fe98fa-e39a-41b3-983f-359a8e730f21","operator":{"type":"string","operation":"startsWith"},"leftValue":"={{ $('Get message').item.json.message.text }}","rightValue":"/question"}]},"renameOutput":true}]},"options":{"fallbackOutput":"extra"}},"typeVersion":3.2},{"id":"4a405332-99a8-4ec9-84d5-89f9f5f1610f","name":"Get session","type":"n8n-nodes-base.googleSheets","position":[-260,-60],"parameters":{"options":{"returnFirstMatch":true},"filtersUI":{"values":[{"lookupValue":"current","lookupColumn":"STATE"}]},"sheetName":{"__rl":true,"mode":"list","value":207843712,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=207843712","cachedResultName":"Session"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"860fa8d5-6c9b-476b-857b-9c47a023d72f","name":"Disable previous session","type":"n8n-nodes-base.googleSheets","position":[640,-760],"parameters":{"columns":{"value":{"STATE":"expire","SESSION":"={{ $('Get session').item.json.SESSION }}"},"schema":[{"id":"SESSION","type":"string","display":true,"removed":false,"required":false,"displayName":"SESSION","defaultMatch":false,"canBeUsedToMatch":true},{"id":"STATE","type":"string","display":true,"removed":false,"required":false,"displayName":"STATE","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["SESSION"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":207843712,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=207843712","cachedResultName":"Session"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"6ff1cf54-5406-4c86-a77e-8424208d4728","name":"Set new session","type":"n8n-nodes-base.googleSheets","position":[960,-760],"parameters":{"columns":{"value":{"STATE":"current","SESSION":"={{ $('Get message').item.json.update_id }}"},"schema":[{"id":"SESSION","type":"string","display":true,"required":false,"displayName":"SESSION","defaultMatch":false,"canBeUsedToMatch":true},{"id":"STATE","type":"string","display":true,"removed":false,"required":false,"displayName":"STATE","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"list","value":207843712,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=207843712","cachedResultName":"Session"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"be9c755a-0c52-453c-b448-98975d4c6e87","name":"Session activated","type":"n8n-nodes-base.telegram","position":[1240,-760],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"New session activated","chatId":"={{ $('Get message').item.json.message.from.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"31478ec5-c1de-43db-ac9d-9b34f68e951c","name":"Send response","type":"n8n-nodes-base.telegram","position":[1100,980],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"={{ $json.output }}","chatId":"={{ $('Get message').item.json.message.from.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"be35b9a6-2914-409f-bc9c-6199ccb9e4ed","name":"Update database","type":"n8n-nodes-base.googleSheets","position":[1420,980],"parameters":{"columns":{"value":{"DATE":"={{$now}}","PROMPT":"={{ $('Get message').item.json.message.text }}","SESSION":"={{ $('Get session').item.json.SESSION }}","RESPONSE":"={{ $('Telegram Chatbot').item.json.output }}"},"schema":[{"id":"SESSION","type":"string","display":true,"required":false,"displayName":"SESSION","defaultMatch":false,"canBeUsedToMatch":true},{"id":"DATE","type":"string","display":true,"required":false,"displayName":"DATE","defaultMatch":false,"canBeUsedToMatch":true},{"id":"PROMPT","type":"string","display":true,"required":false,"displayName":"PROMPT","defaultMatch":false,"canBeUsedToMatch":true},{"id":"RESPONSE","type":"string","display":true,"required":false,"displayName":"RESPONSE","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=0","cachedResultName":"Database"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"dacc79e0-56ff-47d6-b09f-16ebfb31cbfa","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-540,-540],"parameters":{"color":3,"width":620,"height":360,"content":"# Telegram ChatBot with multiple sessions\n\nThis workflow creates an **AI-powered Telegram chatbot** with **session management**, allowing users to:  \n- **Start new conversations** (`/new`).  \n- **Check current sessions** (`/current`).  \n- **Resume past sessions** (`/resume`).  \n- **Get summaries** (`/summary`).  \n- **Ask questions** (`/question`). \n\n- Clone [this sheet](https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=sharing)"},"typeVersion":1},{"id":"55751f99-ee2e-429b-99cd-fef30eb56fb8","name":"Summarization Chain","type":"@n8n/n8n-nodes-langchain.chainSummarization","position":[1040,200],"parameters":{"options":{"summarizationMethodAndPrompts":{"values":{"prompt":"=Write a concise summary of the following:\n\n\n\"{{ $json.fullText }}\"\n\n\nCONCISE SUMMARY:","combineMapPrompt":"=Write a concise summary of the following:\n\n\n\"{{ $json.fullText }}\"\n\n\nCONCISE SUMMARY:"}}}},"typeVersion":2},{"id":"c7f0d136-4a77-4bc8-b2ef-b03fa41ad1ff","name":"OpenAI Chat Model1","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[1020,380],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"4zwP0MSr8zkNvvV9","name":"OpenAi account"}},"typeVersion":1.2},{"id":"a455cf00-2f72-4377-ac2a-d65ab8c30e31","name":"OpenAI Chat Model2","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[1660,800],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"4zwP0MSr8zkNvvV9","name":"OpenAi account"}},"typeVersion":1.2},{"id":"e55082e4-4e65-4fbb-8850-d3b64093eab6","name":"Basic LLM Chain","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[1680,600],"parameters":{"text":"=Question:\n{{ $json.question }}","messages":{"messageValues":[{"message":"=You have to answer the questions that are asked by analyzing the following text:\n\n{{ $json.fullText }}"}]},"promptType":"define"},"typeVersion":1.6},{"id":"1e670eaa-fd9f-4e79-b2a8-bd43240455db","name":"Get message1","type":"n8n-nodes-base.set","position":[600,-100],"parameters":{"options":{},"assignments":{"assignments":[{"id":"56bf47bc-e84c-4cee-8aa0-927f3d2e31c5","name":"text","type":"string","value":"={{ $('Get message').item.json.message.text }}"}]}},"typeVersion":3.4},{"id":"65771b1e-47fc-439d-a1ea-9901bac047d8","name":"Set to expire","type":"n8n-nodes-base.googleSheets","position":[1040,-100],"parameters":{"columns":{"value":{"STATE":"expire","SESSION":"={{ $('Get session').item.json.SESSION }}"},"schema":[{"id":"SESSION","type":"string","display":true,"removed":false,"required":false,"displayName":"SESSION","defaultMatch":false,"canBeUsedToMatch":true},{"id":"STATE","type":"string","display":true,"removed":false,"required":false,"displayName":"STATE","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["SESSION"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":207843712,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=207843712","cachedResultName":"Session"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"d18268f6-8792-4a66-aa32-35b596e7fcad","name":"Exist?","type":"n8n-nodes-base.if","position":[1540,-100],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"1710e38c-80d1-49ba-9814-2b2c6b3c3b8d","operator":{"type":"object","operation":"empty","singleValue":true},"leftValue":"={{ $json }}","rightValue":""}]}},"typeVersion":2.2},{"id":"6eeb7e4d-8af0-4d71-981d-66a39f4870a3","name":"OK","type":"n8n-nodes-base.telegram","position":[1880,-40],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"=The current session is {{ $json.SESSION }}","chatId":"={{ $('Get message').item.json.message.from.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"1a6b9065-154f-4c43-9406-110636884f3d","name":"KO","type":"n8n-nodes-base.telegram","position":[1880,-200],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"=This session doesn't exist","chatId":"={{ $('Get message').item.json.message.from.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"a2463ecc-fc82-401d-bf1b-6104f4b379e6","name":"Trim resume","type":"n8n-nodes-base.code","position":[820,-100],"parameters":{"jsCode":"for (const item of $input.all()) {\n  const text = item.json.text || '';\n  const match = text.match(/\\/resume\\s+(.*)/);\n\n  if (match) {\n    item.json.resume = match[1].trim();\n  } else {\n    item.json.resume = null; \n  }\n}\n\nreturn $input.all();"},"typeVersion":2},{"id":"c24a0c1d-290f-4e0c-8da9-98de14a65e2b","name":"Get session1","type":"n8n-nodes-base.googleSheets","position":[600,200],"parameters":{"options":{},"filtersUI":{"values":[{"lookupValue":"={{ $('Get session').item.json.SESSION }}","lookupColumn":"SESSION"}]},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=0","cachedResultName":"Database"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"84cc4b12-ab2b-4509-a2e3-1c4d952904c5","name":"Prompt + Resume","type":"n8n-nodes-base.code","position":[820,200],"parameters":{"jsCode":"let fullText = '';\n\nfor (const item of $input.all()) {\n  const prompt = item.json.PROMPT || '';\n  const response = item.json.RESPONSE || '';\n  fullText += `PROMPT: ${prompt}\\nRESPONSE: ${response}\\n`;\n}\nconst chat_id=$('Get message').first().json.message.from.id\n\nreturn [{ json: { fullText, chat_id } }];\n"},"typeVersion":2},{"id":"3ae34388-95a0-41d7-8f20-7fa74817dab8","name":"Send summary","type":"n8n-nodes-base.telegram","position":[1420,200],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"={{ $json.response.text }}","chatId":"={{ $('Prompt + Resume').item.json.chat_id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"d38eb058-29a3-4500-b6de-f700b8f501e9","name":"Get message2","type":"n8n-nodes-base.set","position":[620,600],"parameters":{"options":{},"assignments":{"assignments":[{"id":"56bf47bc-e84c-4cee-8aa0-927f3d2e31c5","name":"text","type":"string","value":"={{ $('Get message').item.json.message.text }}"}]}},"typeVersion":3.4},{"id":"ee6713b5-6264-43e8-86a4-f9584049f05b","name":"Trim question","type":"n8n-nodes-base.code","position":[860,600],"parameters":{"jsCode":"for (const item of $input.all()) {\n  const text = item.json.text || '';\n  const match = text.match(/\\/question\\s+(.*)/);\n\n  if (match) {\n    item.json.question = match[1].trim();\n  } else {\n    item.json.question = null; // oppure \"\" se preferisci\n  }\n}\n\nreturn $input.all();"},"typeVersion":2},{"id":"65482069-1c41-4977-8f18-75f192866fdd","name":"Set new current session","type":"n8n-nodes-base.googleSheets","position":[1280,-100],"parameters":{"columns":{"value":{"STATE":"current","SESSION":"={{ $('Trim resume').item.json.resume }}"},"schema":[{"id":"SESSION","type":"string","display":true,"removed":false,"required":false,"displayName":"SESSION","defaultMatch":false,"canBeUsedToMatch":true},{"id":"STATE","type":"string","display":true,"removed":false,"required":false,"displayName":"STATE","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["SESSION"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":207843712,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=207843712","cachedResultName":"Session"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"notesInFlow":false,"typeVersion":4.5,"alwaysOutputData":true},{"id":"91ef2595-acca-4efd-8331-3450fa466d33","name":"Response + Text","type":"n8n-nodes-base.googleSheets","position":[1140,600],"parameters":{"options":{},"filtersUI":{"values":[{"lookupValue":"={{ $('Get session').item.json.SESSION }}","lookupColumn":"SESSION"}]},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit#gid=0","cachedResultName":"Database"},"documentId":{"__rl":true,"mode":"list","value":"1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1MCJLAqKP0Y7Qr68ZYoSSBeEVyKI1QgAAZnlEiyqkzXo/edit?usp=drivesdk","cachedResultName":"Chatbot with session"}},"credentials":{"googleSheetsOAuth2Api":{"id":"JYR6a64Qecd6t8Hb","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"2fbaf240-c01e-48dc-90bf-cc655eb7cd4d","name":"fullText","type":"n8n-nodes-base.code","position":[1440,600],"parameters":{"jsCode":"let fullText = '';\n\nfor (const item of $input.all()) {\n  const prompt = item.json.PROMPT || '';\n  const response = item.json.RESPONSE || '';\n  fullText += `PROMPT: ${prompt}\\nRESPONSE: ${response}\\n`;\n}\nconst chat_id=$('Get message').first().json.message.from.id;\nconst question=$('Trim question').first().json.question;\n\nreturn [{ json: { fullText, chat_id, question } }];\n"},"typeVersion":2},{"id":"30e29b4f-da86-4cee-b875-7985b5d82b7e","name":"Send answer","type":"n8n-nodes-base.telegram","position":[2080,600],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"={{ $json.text }}","chatId":"={{ $('fullText').item.json.chat_id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"41e2b5b3-25a2-411c-929a-ad4c22f45cd1","name":"Send current session","type":"n8n-nodes-base.telegram","position":[620,-440],"webhookId":"fa5e7641-068c-40cb-b490-a6809e74c629","parameters":{"text":"=The current session is {{ $('Get session').item.json.SESSION }}","chatId":"={{ $('Get message').item.json.message.from.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"rQ5q95W7uKesMDx4","name":"Telegram account Fastewb"}},"typeVersion":1.2},{"id":"e30bb12b-8e46-4fe4-81b7-72ce036d431f","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[640,-880],"parameters":{"width":580,"height":80,"content":"## NEW SESSION\n"},"typeVersion":1},{"id":"7e23ee2d-b4ee-4be8-969b-c713039dacdf","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[620,-560],"parameters":{"width":580,"height":80,"content":"## GET CURRENT SESSION\n"},"typeVersion":1},{"id":"5b10cf55-3730-4e0c-8db7-49c82e9b1350","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[600,-220],"parameters":{"width":580,"height":80,"content":"## RESUME SESSION\n"},"typeVersion":1},{"id":"f0932a2c-ebf2-4179-972a-073e5edfa50d","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[600,100],"parameters":{"width":580,"height":80,"content":"## GET SUMMARY\n"},"typeVersion":1},{"id":"9d9fdca4-870b-4ef4-9415-2fb750eb6466","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[620,500],"parameters":{"width":580,"height":80,"content":"## SEND QUESTION\n"},"typeVersion":1},{"id":"aa55d92a-ec31-42c3-8b17-fa9d81d99c31","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[640,860],"parameters":{"width":580,"height":80,"content":"## CHATBOT\n"},"typeVersion":1},{"id":"fa4dd49c-881b-41d2-84b4-82cbd3e62d0e","name":"Telegram Chatbot","type":"@n8n/n8n-nodes-langchain.agent","position":[640,980],"parameters":{"text":"={{ $('Get message').item.json.message.text }}","options":{"systemMessage":"=Sei un assitente virtuale:\n\nData e ora corrente: {{ $now }}"},"promptType":"define"},"typeVersion":1.9}],"active":false,"pinData":{},"settings":{"timezone":"Europe/Rome","callerPolicy":"workflowsFromSameOwner","executionOrder":"v1","executionTimeout":-1},"versionId":"c6d418c0-aec9-498a-b5c7-318a3e4efe0b","connections":{"Exist?":{"main":[[{"node":"KO","type":"main","index":0}],[{"node":"OK","type":"main","index":0}]]},"fullText":{"main":[[{"node":"Basic LLM Chain","type":"main","index":0}]]},"Get message":{"main":[[{"node":"Get session","type":"main","index":0}]]},"Get session":{"main":[[{"node":"Command or text?","type":"main","index":0}]]},"Trim resume":{"main":[[{"node":"Set to expire","type":"main","index":0}]]},"Get message1":{"main":[[{"node":"Trim resume","type":"main","index":0}]]},"Get message2":{"main":[[{"node":"Trim question","type":"main","index":0}]]},"Get session1":{"main":[[{"node":"Prompt + Resume","type":"main","index":0}]]},"Send response":{"main":[[{"node":"Update database","type":"main","index":0}]]},"Set to expire":{"main":[[{"node":"Set new current session","type":"main","index":0}]]},"Simple Memory":{"ai_memory":[[{"node":"Telegram Chatbot","type":"ai_memory","index":0}]]},"Trim question":{"main":[[{"node":"Response + Text","type":"main","index":0}]]},"Basic LLM Chain":{"main":[[{"node":"Send answer","type":"main","index":0}]]},"Prompt + Resume":{"main":[[{"node":"Summarization Chain","type":"main","index":0}]]},"Response + Text":{"main":[[{"node":"fullText","type":"main","index":0}]]},"Set new session":{"main":[[{"node":"Session activated","type":"main","index":0}]]},"Command or text?":{"main":[[{"node":"Disable previous session","type":"main","index":0}],[{"node":"Send current session","type":"main","index":0}],[{"node":"Get message1","type":"main","index":0}],[{"node":"Get session1","type":"main","index":0}],[{"node":"Get message2","type":"main","index":0}],[{"node":"Telegram Chatbot","type":"main","index":0}]]},"Telegram Chatbot":{"main":[[{"node":"Send response","type":"main","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"Telegram Chatbot","type":"ai_languageModel","index":0}]]},"OpenAI Chat Model1":{"ai_languageModel":[[{"node":"Summarization Chain","type":"ai_languageModel","index":0}]]},"OpenAI Chat Model2":{"ai_languageModel":[[{"node":"Basic LLM Chain","type":"ai_languageModel","index":0}]]},"Summarization Chain":{"main":[[{"node":"Send summary","type":"main","index":0}]]},"Set new current session":{"main":[[{"node":"Exist?","type":"main","index":0}]]},"Disable previous session":{"main":[[{"node":"Set new session","type":"main","index":0}]]}}}


name: Automated AI Content Creation & Instagram Publishing from Google Sheets
author_name: Onur
author_username: onurpolat05
categories: []
created_at: 2025-05-03T08:06:31.593Z
updated_at: 2025-05-03T08:06:31.593Z
url: https://n8n.io/workflows/3840
scraped_at: 2025-07-06 21:16:30
description:
  Automated AI Content Creation & Instagram Publishing from Google Sheets

  This n8n workflow automates the creation and publishing of social media content directly to Instagram, using ideas stored in a Google Sheet. It leverages AI (Google Gemini and Replicate Flux) to generate concepts, image prompts, captions, and the final image, turning your content plan into reality with minimal manual intervention.

  Think of this as the execution engine for your content strategy. It assumes you have a separate process (whether manual entry, another workflow, or a different tool) for populating the Google Sheet with initial post ideas (including Topic, Audience, Voice, and Platform). This workflow takes those ideas and handles the rest, from AI generation to final publication.

  What does this workflow do?

  This workflow streamlines the content execution process by:

  Automatically fetching** unprocessed content ideas from a designated Google Sheet based on a schedule.
  Using Google Gemini to generate a platform-specific content concept (specifically for a 'Single Image' format).
  Generating two distinct AI image prompt options based on the concept using Gemini.
  Writing an engaging, platform-tailored caption (including hashtags) using Gemini, based on the first prompt option.
  Generating a visual image using the first prompt option via the Replicate API (using the Flux model).
  Publishing* the generated image and caption directly to a connected *Instagram Business account**.
  Updating the status** in the Google Sheet to mark the idea as completed, preventing reprocessing.

  Who is this for?

  Social Media Managers & Agencies:** Automate the execution of your content calendar stored in Google Sheets.
  Marketing Teams:** Streamline content production from planned ideas and ensure consistent posting schedules.
  Content Creators & Solopreneurs:** Save significant time by automating the generation and publishing process based on your pre-defined ideas.
  Anyone** using Google Sheets to plan social media content and wanting to automate the creative generation and posting steps with AI.

  Benefits

  Full Automation:** From fetching planned ideas to Instagram publishing, automate the entire content execution pipeline.
  AI-Powered Generation:** Leverage Google Gemini for creative concepts, prompts, and captions, and Replicate for image generation based on your initial topic.
  Content Calendar Execution:** Directly turn your Google Sheet plan into published posts.
  Time Savings:** Drastically reduce the manual effort involved in creating visuals and text for each planned post.
  Consistency:** Maintain a regular posting schedule by automatically processing your queue of ideas.
  Platform-Specific Content:** AI prompts are designed to tailor concepts, prompts, and captions for the platform specified in your sheet (e.g., Instagram or LinkedIn).

  How it Works

  Scheduled Trigger: The workflow starts automatically based on the schedule you set (e.g., every hour, daily).
  Fetch Idea: Reads the next row from your Google Sheet where the 'Status' column indicates it's pending (e.g., '0'). It only fetches one idea per run.
  Prepare Inputs: Extracts Topic, Audience, Voice, and Platform from the sheet data.
  AI Concept Generation (Gemini): Creates a single content concept suitable for a 'Single Image' post on the target platform.
  AI Prompt Generation (Gemini): Develops two detailed, distinct image prompt options based on the concept.
  AI Caption Generation (Gemini): Writes a caption tailored to the platform, using the first image prompt and other context.
  Image Generation (Replicate): Sends the first prompt to the Replicate API (Flux model) to generate the image.
  Prepare for Instagram: Formats the generated image URL and caption.
  Publish to Instagram: Uses the Facebook Graph API in three steps:
      Creates a media container by uploading the image URL and caption.
      Waits for Instagram to process the container.
      Publishes the processed container to your feed.
  Update Sheet: Changes the 'Status' in the Google Sheet for the processed row (e.g., to '1') to mark it as complete.

  n8n Nodes Used

  Schedule Trigger
  Google Sheets (Read & Update operations)
  Set (Multiple instances for data preparation)
  Langchain Chain - LLM (Multiple instances for Gemini calls)
  Langchain Chat Model - Google Gemini (Multiple instances)
  Langchain Output Parser - Structured (Multiple instances)
  HTTP Request (for Replicate API call)
  Wait
  Facebook Graph API (Multiple instances for Instagram publishing steps)

  Prerequisites

  Active n8n instance (Cloud or Self-Hosted).
  Google Account** with access to Google Sheets.
  Google Sheets API Credentials (OAuth2):** Configured in n8n.
  A Google Sheet** structured with columns like Topic, Audience, Voice, Platform, Status (or similar). Ensure your 'pending' and 'completed' statuses are defined (e.g., '0' and '1').
  Google Cloud Project** with the Vertex AI API enabled.
  Google Gemini API Credentials:** Configured in n8n (usually via Google Vertex AI credentials).
  Replicate Account** and API Token.
  Replicate API Credentials (Header Auth):** Configured in n8n.
  Facebook Developer Account**.
  Instagram Business Account** connected to a Facebook Page.
  Facebook App** with necessary permissions: instagram_basic, instagram_content_publish, pages_read_engagement, pages_show_list.
  Facebook Graph API Credentials (OAuth2):** Configured in n8n with the required permissions.

  Setup

  Import the workflow JSON into your n8n instance.
  Configure Schedule Trigger: Set the desired frequency (e.g., every 30 minutes, every 4 hours) for checking new ideas in the sheet.
  Configure Google Sheets Nodes:
      Select your Google Sheets OAuth2 credentials for both Google Sheets nodes.
      In 1. Get Next Post Idea..., enter your Spreadsheet ID and Sheet Name. Verify the Status filter matches your 'pending' value (e.g., 0).
      In 7. Update Post Status..., enter the same Spreadsheet ID and Sheet Name. Ensure the Matching Columns (e.g., Topic) and the Status value to update match your 'completed' value (e.g., 1).
  Configure Google Gemini Nodes: Select your configured Google Vertex AI / Gemini credentials in all Google Gemini Chat Model nodes.
  Configure Replicate Node (4. Generate Image...): Select your Replicate Header Auth credentials. The workflow uses black-forest-labs/flux-1.1-pro-ultra by default; you can change this if needed.
  Configure Facebook Graph API Nodes (6a, 6c):
      Select your Facebook Graph API OAuth2 credentials.
      Crucially, update the Instagram Account ID in the Node parameter of both Facebook Graph API nodes (6a and 6c). The template uses a placeholder (17841473009917118); replace this with your actual Instagram Business Account ID.
  Adjust Wait Node (6b): The default wait time might be sufficient, but if you encounter errors during publishing (especially with larger images/videos in the future), you might need to increase the wait duration.
  Activate the workflow.
  Populate your Google Sheet: Ensure you have rows with your content ideas and the correct 'pending' status (e.g., '0'). The workflow will pick them up on its next scheduled run.

  This workflow transforms your Google Sheet content plan into a fully automated AI-powered Instagram publishing engine. Start automating your social media presence today!

   

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","n8n-nodes-base.facebookGraphApi","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.outputParserStructured","@n8n/n8n-nodes-langchain.lmChatGoogleGemini"]

workflow_json:
  {"meta":{"instanceId":"02e782574ebb30fbddb2c3fd832c946466d718819d25f6fe4b920124ff3fc2c1","templateCredsSetupCompleted":true},"nodes":[{"id":"c53bc70a-2e85-465a-b977-73cfd268ba41","name":"Scheduled Start: Check for New Posts","type":"n8n-nodes-base.scheduleTrigger","position":[-1240,-380],"parameters":{"rule":{"interval":[{}]}},"typeVersion":1.2},{"id":"aa73f2af-f311-465b-8270-b027b17eec0d","name":"1. Get Next Post Idea from Sheet","type":"n8n-nodes-base.googleSheets","position":[-920,-380],"parameters":{"options":{"returnFirstMatch":true},"filtersUI":{"values":[{"lookupValue":"0","lookupColumn":"Status"}]},"sheetName":{"__rl":true,"mode":"list","value":1510137257,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k/edit#gid=1510137257","cachedResultName":"Postİ"},"documentId":{"__rl":true,"mode":"list","value":"1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k/edit?usp=drivesdk","cachedResultName":"Medium Post Automation"}},"credentials":{"googleSheetsOAuth2Api":{"id":"VV5AyFvgYkc4TfC7","name":"Onur Drive "}},"typeVersion":4.5},{"id":"42e129d0-5e4f-4226-a86f-b7fd04c8c497","name":"2. Prepare Input Variables (Topic, Audience, etc.)","type":"n8n-nodes-base.set","position":[-620,-380],"parameters":{"options":{},"assignments":{"assignments":[{"id":"aa3b9a02-ac6a-4d7f-937f-a0e6e566a0c8","name":"Topic","type":"string","value":"={{ $json.Topic }}"},{"id":"e48783e8-5f6b-4c54-bf4f-c004414dc510","name":"TargetAudience","type":"string","value":"={{ $json.Audience }}"},{"id":"c499a954-b4c6-4702-ab86-3656aa2b1783","name":"BrandVoice","type":"string","value":"={{ $json.Voice }}"},{"id":"210f7103-4d6b-42e9-9168-fd99dff94b5a","name":"Platform","type":"string","value":"={{ $json.Platform }}"}]}},"typeVersion":3.4},{"id":"5036db9b-1a1b-48d7-bfaa-1839ba5331b5","name":"3a. Generate Content Concept (Gemini)","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-160,-380],"parameters":{"text":"=","messages":{"messageValues":[{"message":"=<prompt>     <role>         You are a **highly imaginative Social Media Strategist** specializing in generating **unique, platform-aware content CONCEPTS** for **Instagram and LinkedIn**. You think beyond basic formats and consider audience engagement.     </role>      <task>         Based *only* on the `Topic`, `Target Audience`, `Brand Voice`, AND **target `Platform` ('Instagram' or 'LinkedIn')**, generate **exactly 1 creative content CONCEPT**. Focus on the **core message, angle, or hook**. The suggested format **MUST be \"Single Image\"**.         1.  **Platform Optimization:** **Explicitly tailor** the *type* and *angle* of the concept to the specified `Platform`. Consider typical user expectations and content formats:             * **Instagram:** Often more visual, storytelling, personal, community-focused, impactful single images.             * **LinkedIn:** Can utilize impactful single images to convey data points, key takeaways, or thought-provoking visuals supporting a concise message.         2.  **Originality:** Avoid common tropes (like basic quotes) unless the input strongly suggests it. Explore diverse angles: striking visual representations of data, metaphorical imagery, thought-provoking questions presented visually, behind-the-scenes moments captured in a compelling image, key message highlighted graphically.         3.  **Format Suggestion:** The format **MUST be \"Single Image\"**. The **CONCEPT is primary, the format is fixed**.      </task>      <input_context>         <param name=\"Topic\">{{ $json.Topic }}</param>         <param name=\"TargetAudience\">{{ $json.TargetAudience }}</param>         <param name=\"BrandVoice\">{{ $json.BrandVoice }}</param>         <param name=\"Platform\">{{ $json.Platform }}</param>     </input_context>      <output_instructions>         Your response MUST be a single, valid JSON object containing exactly one key: `ideas`.         The value of `ideas` MUST be an array containing exactly 1 object.         The object in the array MUST have two keys: `concept` (string: the descriptive concept text) and `suggested_format` (string: **MUST be \"Single Image\"**).         Example: `{\"ideas\": [{\"concept\": \"Concept text...\", \"suggested_format\": \"Single Image\"}]}`         Do NOT include any other text, explanations, or formatting outside the JSON structure.     </output_instructions> </prompt>"}]},"promptType":"define","hasOutputParser":true},"typeVersion":1.5},{"id":"5a882770-53af-466c-a297-856b088eb2b9","name":"(LLM Model for Concept)","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[-160,-220],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash-001"},"credentials":{"googlePalmApi":{"id":"hAoasnlx3jlRA3Kf","name":"BCP Gemini"}},"typeVersion":1},{"id":"a4132a05-def7-4c4f-ad1d-092533f3b9f2","name":"(Parse Concept JSON)","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[0,-220],"parameters":{"schemaType":"manual","inputSchema":"{\n  \"type\": \"object\",\n  \"properties\": {\n    \"ideas\": {\n      \"type\": \"array\",\n      \"description\": \"An array containing exactly 1 content concept.\",\n      \"minItems\": 1,\n      \"maxItems\": 1,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"concept\": {\n            \"type\": \"string\",\n            \"description\": \"The detailed text describing the content concept for a Single Image.\"\n          },\n          \"suggested_format\": {\n            \"type\": \"string\",\n            \"description\": \"The post format, which MUST be 'Single Image'.\",\n            \"const\": \"Single Image\"\n          }\n        },\n        \"required\": [\n          \"concept\",\n          \"suggested_format\"\n        ]\n      }\n    }\n  },\n  \"required\": [\n    \"ideas\"\n  ]\n}"},"typeVersion":1.2},{"id":"46338aad-5f7d-464f-a7fe-e9ae00aacad5","name":"3b. Generate Image Prompt Options (Gemini)","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[580,-380],"parameters":{"text":"=","messages":{"messageValues":[{"message":"=<prompt>\n    <role>\n        You are an **Expert Instagram/LinkedIn Content Strategist and AI Image Prompt Engineer**. You excel at elaborating concepts based on user feedback and crafting distinct, detailed, and visually consistent prompt options tailored for the target platform.\n    </role>\n\n    <task>\n        1.  **Analyze** the `Chosen Idea`, `User Visual Input` (if provided and relevant), and **target `Platform`** to determine the optimal post format (in this case, assumed to be Single Image based on the output) and elaborate this into a practical `expanded_post_concept`. **Justify format choice based on concept AND platform norms.**\n        2.  **Incorporate** the user's visual direction (if any) into the concept description. If no specific visual input was given, propose a clear visual direction that aligns with the concept and platform.\n        3.  Generate **TWO DISTINCT OPTIONS** for the image prompts based on the `expanded_post_concept`. **Tailor the visual style and content nuances** described in the prompts to the target `Platform`. (E.g., LinkedIn visuals might be cleaner, more data-oriented; Instagram more lifestyle or emotive).\n        4.  **Ensure Distinction:** The two options should offer meaningful variety (e.g., style, composition, focus) while remaining true to the core concept.\n        5.  **Detail:** Prompts should be highly detailed, suitable for advanced AI image generators (include subject, action, setting, style, mood, composition, lighting, color palette keywords).\n    </task>\n\n    <input_context>\n        <param name=\"ChosenIdea\">{{ $json.output.ideas[0].concept }}</param>\n        <param name=\"OriginalTopic\"> {{ $('2. Prepare Input Variables (Topic, Audience, etc.)').item.json.Topic }}</param>\n        <param name=\"TargetAudience\"> {{ $('2. Prepare Input Variables (Topic, Audience, etc.)').item.json.TargetAudience }}</param>\n        <param name=\"BrandVoice\"> {{ $('2. Prepare Input Variables (Topic, Audience, etc.)').item.json.BrandVoice }}</param>\n        <param name=\"Platform\"> {{ $('2. Prepare Input Variables (Topic, Audience, etc.)').item.json.Platform }}</param>\n        </input_context>\n\n    <output_instructions>\n        Your response MUST be a single, valid JSON object containing exactly two keys: `expanded_post_concept` and `prompt_options`.\n        - `expanded_post_concept` (string): The elaborated visual concept, stating Single Image format and incorporating user input/platform considerations.\n        - `prompt_options` (array): MUST contain exactly TWO objects.\n            - Each object represents one prompt option and MUST have two keys:\n                - `option_description` (string): Briefly describe the distinct angle/style of this option (e.g., \"Option 1: Hyperrealistic...\").\n                - `prompts` (array of strings): Contains ONE string representing the detailed prompt for the single image.\n       \n         \n        Do NOT include any text outside this JSON structure. Do NOT generate captions here.\n    </output_instructions>\n</prompt>"}]},"promptType":"define","hasOutputParser":true},"typeVersion":1.5},{"id":"950b7c2f-cebc-41ae-aad8-38b139c1b2ea","name":"(LLM Model for Prompts)","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[580,-220],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash-001"},"credentials":{"googlePalmApi":{"id":"hAoasnlx3jlRA3Kf","name":"BCP Gemini"}},"typeVersion":1},{"id":"bfda2858-3a3a-4997-b30c-ce64429bd255","name":"(Parse Prompts JSON)","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[740,-220],"parameters":{"schemaType":"manual","inputSchema":"{\n  \"type\": \"object\",\n  \"properties\": {\n    \"expanded_post_concept\": {\n      \"type\": \"string\",\n      \"description\": \"The elaborated visual concept, stating Single Image format and incorporating user input/platform considerations.\"\n    },\n    \"prompt_options\": {\n      \"type\": \"array\",\n      \"description\": \"An array containing exactly TWO prompt options for the single image concept.\",\n      \"minItems\": 2,\n      \"maxItems\": 2,\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"option_description\": {\n            \"type\": \"string\",\n            \"description\": \"Briefly describes the distinct angle/style of this option (e.g., 'Option 1: Hyperrealistic...').\"\n          },\n          \"prompts\": {\n            \"type\": \"array\",\n            \"description\": \"Contains ONE detailed prompt string for the single image.\",\n            \"minItems\": 1,\n            \"maxItems\": 1,\n            \"items\": {\n              \"type\": \"string\",\n              \"description\": \"A detailed image generation prompt.\"\n            }\n          }\n        },\n        \"required\": [\n          \"option_description\",\n          \"prompts\"\n        ]\n      }\n    }\n  },\n  \"required\": [\n    \"expanded_post_concept\",\n    \"prompt_options\"\n  ]\n}"},"typeVersion":1.2},{"id":"0151450f-fca2-4628-a268-f90186a79b9e","name":"3c. Generate Post Caption (Gemini)","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[1540,-380],"parameters":{"text":"=","messages":{"messageValues":[{"message":"=<prompt>\n    <role>\n        You are an AI Instagram/LinkedIn **Caption Writer**. You specialize in crafting concise, engaging, and contextually relevant captions based on a generated image (represented by its prompt) and the overall content strategy, specifically tailored for the target platform.\n    </role>\n\n    <task>\n        Write a short, effective social media caption **specifically tailored for the target `Platform` ('Instagram' or 'LinkedIn')**.\n        * The caption must complement the image described by `ImagePrompt` and align with all context parameters (`ChosenIdea`, `OriginalTopic`, `TargetAudience`, `BrandVoice`).\n        * **Platform Style:** Adapt tone, length, calls-to-action, and hashtag usage:\n            * **Instagram:** Can be more conversational, use more emojis, ask engaging questions, often benefits from slightly longer, more storytelling captions if relevant. Use a mix of popular and niche hashtags (3-7 recommended).\n            * **LinkedIn:** Generally more professional, concise, focused on insights or value proposition. Calls-to-action often relate to reading more, commenting with professional opinions, or business objectives. Use targeted, professional hashtags (1-3 recommended).\n        * Include 1-5 relevant, platform-appropriate hashtags.\n    </task>\n\n    <input_context>\n        <param name=\"ImagePrompt\">{{ $json.output.prompt_options[0].prompts[0] }}</param>\n        <param name=\"ChosenIdea\">{{ $('3a. Generate Content Concept (Gemini)').item.json.output.ideas[0].concept }} </param>\n        <param name=\"OriginalTopic\">{{ $('1. Get Next Post Idea from Sheet').item.json.Topic }} </param>\n        <param name=\"TargetAudience\">{{ $('1. Get Next Post Idea from Sheet').item.json.Audience }}</param>\n        <param name=\"BrandVoice\">{{ $('1. Get Next Post Idea from Sheet').item.json.Voice }} </param>\n        <param name=\"Platform\">{{ $('1. Get Next Post Idea from Sheet').item.json.Platform }} </param>\n    </input_context>\n\n    <output_instructions>\n        Your response MUST be a single, valid JSON object containing exactly one key: `Caption`.\n        The value of `Caption` MUST be a string containing the generated caption text, including hashtags.\n        Example: `{\"Caption\": \"Caption text tailored for LinkedIn goes here. #ProfessionalDevelopment #IndustryInsights\"}`\n        Do NOT include any other text, explanations, or formatting outside the JSON structure.\n    </output_instructions>\n</prompt>"}]},"promptType":"define","hasOutputParser":true},"typeVersion":1.5},{"id":"d4667681-a5aa-4b63-802c-2adf8c111bb9","name":"(LLM Model for Caption)","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[1560,-220],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash"},"credentials":{"googlePalmApi":{"id":"hAoasnlx3jlRA3Kf","name":"BCP Gemini"}},"typeVersion":1},{"id":"59da1f6c-ee55-4095-8c95-d60d1177c142","name":"(Parse Caption JSON)","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[1700,-220],"parameters":{"jsonSchemaExample":"{\n\t\"Caption\": \"Thee future of call centers is here!\"\n}"},"typeVersion":1.2},{"id":"a8d38be7-85b5-4a39-bd39-7f765699d471","name":"4. Generate Image using Prompt 1 (Replicate Flux)","type":"n8n-nodes-base.httpRequest","position":[2360,-380],"parameters":{"url":"https://api.replicate.com/v1/models/black-forest-labs/flux-1.1-pro-ultra/predictions","method":"POST","options":{},"jsonBody":"={\n  \"input\": {\n    \"raw\": false,\n    \"prompt\": \"{{ $('3b. Generate Image Prompt Options (Gemini)').item.json.output.prompt_options[0].prompts[0] }}\",\n    \"aspect_ratio\": \"1:1\",\n    \"output_format\": \"jpg\",\n    \"safety_tolerance\": 6\n  }\n}","sendBody":true,"sendHeaders":true,"specifyBody":"json","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","headerParameters":{"parameters":[{"name":"Prefer","value":"wait"}]}},"credentials":{"httpHeaderAuth":{"id":"iEtPzDTSlZBfz1RP","name":"Replicate"}},"typeVersion":4.2},{"id":"cc0f0035-0d63-4988-8ec8-89260261960a","name":"5. Prepare Data for Instagram API","type":"n8n-nodes-base.set","position":[2880,-380],"parameters":{"options":{},"assignments":{"assignments":[{"id":"8a4260ba-3bde-4444-8f42-8a8abd51ff0c","name":"ImageURL","type":"string","value":"={{ $json.output }}"},{"id":"1953ae03-6a86-4847-8686-5a928637be1d","name":"Caption","type":"string","value":"={{ $('3c. Generate Post Caption (Gemini)').item.json.output.Caption }}"}]}},"typeVersion":3.4},{"id":"f010c17f-1af3-4d04-a69c-8b03c70fc8f0","name":"6a. Create Instagram Media Container","type":"n8n-nodes-base.facebookGraphApi","position":[3180,-380],"parameters":{"edge":"media","node":"17841473009917118","options":{"queryParameters":{"parameter":[{"name":"caption","value":"={{ $json.Caption }}"},{"name":"image_url","value":"={{ $json.ImageURL }}"}]}},"graphApiVersion":"v22.0","httpRequestMethod":"POST"},"credentials":{"facebookGraphApi":{"id":"vF0cZWKe6sYiEaHo","name":"n8n Bot Access"}},"typeVersion":1},{"id":"c69f1856-efa2-48a5-9e41-71ab67bcaeca","name":"6b. Wait for Container Processing","type":"n8n-nodes-base.wait","position":[3480,-380],"webhookId":"1b14c8bf-151a-4054-8215-093dd5b6cbcc","parameters":{},"typeVersion":1.1},{"id":"516b61f0-825e-468b-8eb7-80f757171465","name":"6c. Publish Post to Instagram","type":"n8n-nodes-base.facebookGraphApi","position":[3780,-380],"parameters":{"edge":"media_publish","node":"17841473009917118","options":{"queryParameters":{"parameter":[{"name":"creation_id","value":"={{ $json.id }}"}]}},"graphApiVersion":"v22.0","httpRequestMethod":"POST"},"credentials":{"facebookGraphApi":{"id":"vF0cZWKe6sYiEaHo","name":"n8n Bot Access"}},"typeVersion":1},{"id":"1a94c8ba-7b59-4337-ae07-5c0b2b8349dd","name":"7. Update Post Status in Sheet","type":"n8n-nodes-base.googleSheets","position":[4440,-380],"parameters":{"columns":{"value":{"Topic":"={{ $('1. Get Next Post Idea from Sheet').item.json.Topic }}","Status":"1"},"schema":[{"id":"Topic","type":"string","display":true,"removed":false,"required":false,"displayName":"Topic","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Audience","type":"string","display":true,"removed":true,"required":false,"displayName":"Audience","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Voice","type":"string","display":true,"removed":true,"required":false,"displayName":"Voice","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Platform","type":"string","display":true,"removed":true,"required":false,"displayName":"Platform","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Status","type":"string","display":true,"required":false,"displayName":"Status","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["Topic"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":1510137257,"cachedResultUrl":"https://docs.google.com/spreadsheets/d/1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k/edit#gid=1510137257","cachedResultName":"Postİ"},"documentId":{"__rl":true,"mode":"list","value":"1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1hG2NMi-4fMa7D5qGonCN8bsYVya4L2TOB_8mI4XK-9k/edit?usp=drivesdk","cachedResultName":"Medium Post Automation"}},"credentials":{"googleSheetsOAuth2Api":{"id":"VV5AyFvgYkc4TfC7","name":"Onur Drive "}},"typeVersion":4.5},{"id":"68651653-07fb-435a-a82d-b8ee911b4161","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-240,-820],"parameters":{"width":460,"height":740,"content":"# 01. Content Concept Generation\n\n**Purpose:** This step uses Google Gemini to generate **one unique content concept** tailored for the specified platform (Instagram/LinkedIn), based on the input topic, audience, and brand voice. The format is fixed to \"Single Image\".\n\n**Input (from Node '2. Prepare Input Variables'):**\n*   `Topic` (string)\n*   `TargetAudience` (string)\n*   `BrandVoice` (string)\n*   `Platform` (string: 'Instagram' )\n\n**Output (JSON):**\n*   `{\"ideas\": [{\"concept\": \"Generated concept text...\", \"suggested_format\": \"Single Image\"}]}`"},"typeVersion":1},{"id":"c463e2b6-6b2b-498d-8672-a0a9b15c06b8","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[2220,-820],"parameters":{"color":4,"width":380,"height":740,"content":"# 03b. Image Generation\n\n**Purpose:** Generates the actual image using the **first detailed prompt** created in step 3b. It sends this prompt to the Replicate API, specifically using the 'Flux 1.1 Pro Ultra' model.\n\n**Input (from Node '3b. Generate Image Prompt Options'):**\n*   `prompt` (string: The *first* prompt string from `prompt_options[0].prompts[0]`)\n\n**Output (from Replicate API):**\n*   JSON containing the `output` URL of the generated image (e.g., `{\"output\": \"https://replicate.delivery/...\"}`)"},"typeVersion":1},{"id":"4636ef23-11e1-45dd-94c1-5774ad612811","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[400,-820],"parameters":{"color":2,"width":740,"height":740,"content":"# 02. Image Prompt Elaboration & Options\n\n**Purpose:** Takes the generated content concept and expands on it to create **two distinct, detailed image generation prompts**. These prompts are optimized for the target platform and suitable for AI image generators like Replicate Flux.\n\n**Input (from Nodes '2. Prepare Input Variables' & '3a. Generate Content Concept'):**\n*   `ChosenIdea` (string: Concept from step 3a)\n*   `OriginalTopic` (string)\n*   `TargetAudience` (string)\n*   `BrandVoice` (string)\n*   `Platform` (string: 'Instagram')\n\n**Output (JSON):**\n*   `{\"expanded_post_concept\": \"Elaborated concept description...\", \"prompt_options\": [{\"option_description\": \"Option 1: ...\", \"prompts\": [\"Detailed prompt 1...\"]}, {\"option_description\": \"Option 2: ...\", \"prompts\": [\"Detailed prompt 2...\"]}]}`\n"},"typeVersion":1},{"id":"1996f6d0-42bc-4b23-a1af-59c4bb1cb3ee","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[1380,-820],"parameters":{"color":3,"width":620,"height":740,"content":"# 03a. Caption Generation\n\n**Purpose:** Uses Google Gemini to write a short, engaging social media caption **specifically tailored for the target platform**. The caption complements the image (represented by the first generated prompt) and aligns with the overall content strategy. Includes relevant hashtags.\n\n**Input (from Nodes '1. Get Next Post Idea', '3a. Generate Content Concept', '3b. Generate Image Prompt Options'):**\n*   `ImagePrompt` (string: The *first* prompt from step 3b)\n*   `ChosenIdea` (string: Concept from step 3a)\n*   `OriginalTopic` (string)\n*   `TargetAudience` (string)\n*   `BrandVoice` (string)\n*   `Platform` (string: 'Instagram' or 'LinkedIn')\n\n**Output (JSON):**\n*   `{\"Caption\": \"Generated caption text with #hashtags...\"}`"},"typeVersion":1},{"id":"25e2ae24-c0c5-46fd-8c81-39a9fcafe472","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[2800,-820],"parameters":{"color":5,"width":1160,"height":740,"content":"# 04. Instagram Publishing\n\n**Purpose:** This block takes the final image URL and caption, prepares them for the Instagram Graph API, uploads the media to create a container, waits for Instagram to process it, and finally publishes the container as a post to the connected Instagram account.\n\n**Input (from Nodes '3c. Generate Post Caption (Gemini)' & '4. Generate Image using Prompt 1 (Replicate Flux)'):**\n*   `ImageURL` (string: URL of the generated image from Replicate)\n*   `Caption` (string: Generated post text with hashtags from Gemini)\n\n**Process:**\n1.  **Format Data (`5. Prepare Data...`):** Organizes the ImageURL and Caption into the required structure.\n2.  **Create Media Container (`6a. Create...`):** Sends the `image_url` and `caption` to the Instagram Graph API (`media` edge) to initiate the upload. Receives a container `id`.\n3.  **Wait for Processing (`6b. Wait...`):** Pauses the workflow to allow Instagram's servers time to process the uploaded media. *Note: Wait time might need adjustment depending on media size and API responsiveness.*\n4.  **Publish Media (`6c. Publish...`):** Sends the container `id` (as `creation_id`) to the Instagram Graph API (`media_publish` edge) to make the post live.\n\n**Output:** The content is published as a new post on the target Instagram profile. The final node (`6c. Publish Post...`) returns the `id` of the successfully published media object on Instagram."},"typeVersion":1},{"id":"d7082400-def2-40eb-9dbc-3e15d24c8f8f","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[4640,-480],"parameters":{"color":6,"width":380,"height":300,"content":"# 05. Finalize: Update Sheet Status\n\n**Purpose:** Marks the processed post idea as completed in the Google Sheet.\n\n**Action:** Finds the corresponding row in the sheet (using the 'Topic' to match) and updates its 'Status' column to '1'. This prevents the same idea from being processed again by the workflow in future runs."},"typeVersion":1},{"id":"4e5433ff-5d04-424a-8165-6fd85cca1ad0","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[-100,-300],"parameters":{},"typeVersion":1},{"id":"71bf2b4d-eaa1-423b-b798-8c0f737f4371","name":"Sticky Note7","type":"n8n-nodes-base.stickyNote","position":[-1760,-440],"parameters":{"width":420,"height":240,"content":"# 00. Scheduled Start & Input Preparation\n\n**Purpose:** Initiates the workflow automatically based on the user-defined schedule. Fetches the next available post idea (Status=0) from Google Sheets and prepares the necessary input variables (`Topic`, `Audience`, `Voice`, `Platform`) for the content generation steps."},"typeVersion":1}],"pinData":{"3c. Generate Post Caption (Gemini)":[{"output":{"Caption":"The future of call centers is here, and it's all about human + AI collaboration ✨. We believe AI should enhance, not replace, the human touch, leading to better customer experiences and happier agents. Ready to see how AI can transform your customer interactions? 🤔 \n\n#AIinCX #CallCenterAI #CustomerExperience #ArtificialIntelligence #TechInnovation #FutureofWork #HumanAI"}}],"3a. Generate Content Concept (Gemini)":[{"output":{"ideas":[{"concept":"Visually striking image of a human hand interacting with a glowing AI interface. Caption highlights how AI enhances, not replaces, human connection in call centers, leading to better customer experiences. Focus on the seamless collaboration between humans and AI.","suggested_format":"Single Image"}]}}],"3b. Generate Image Prompt Options (Gemini)":[{"output":{"prompt_options":[{"prompts":["Close-up shot of a diverse human hand gently touching a shimmering, holographic AI interface that displays abstract data visualizations. The background is blurred, showing a modern, bright call center environment. Soft, diffused lighting with hints of neon blue and teal. Ethereal, optimistic mood. Style: Digital art, futuristic, ethereal. Color palette: Pastel blues, teals, and whites with subtle glowing accents. Composition: Focus on the hand and interface, using shallow depth of field."],"option_description":"Option 1: Ethereal and futuristic. Focus on soft, diffused lighting and glowing elements to create a dreamlike, optimistic atmosphere."},{"prompts":["A slightly wider shot showcasing a human hand confidently interacting with a vibrant, glowing AI interface displaying customer profiles and real-time data. The background shows a bustling call center with diverse agents working collaboratively. Dramatic, focused lighting with strong shadows and vibrant color accents. Energetic, innovative mood. Style: High-resolution photograph, modern, bold. Color palette: Deep blues, purples, and oranges with bright yellow accents on the interface. Composition: Dynamic angle, leading lines towards the interface and agents in the background, creating depth."],"option_description":"Option 2: Bold and dynamic. Focus on contrast and vibrant colors to create a visually striking and energetic image."}],"expanded_post_concept":"Single Image post for Instagram showcasing a human hand interacting with a glowing AI interface. The image should convey a sense of collaboration and enhancement, not replacement, emphasizing the positive impact of AI on human connection in call centers. Given Instagram's visual nature, the image should be aesthetically pleasing, modern, and slightly stylized to attract attention and reflect an innovative brand voice."}}],"4. Generate Image using Prompt 1 (Replicate Flux)":[{"id":"be6ctjws49rma0cpjcy8ej169r","logs":"","urls":{"get":"https://api.replicate.com/v1/predictions/be6ctjws49rma0cpjcy8ej169r","cancel":"https://api.replicate.com/v1/predictions/be6ctjws49rma0cpjcy8ej169r/cancel","stream":"https://stream.replicate.com/v1/files/bcwr-kvewu3jt6ec4id3svfazffgcxpyifayvqjhxloix3oncveoazwya"},"error":null,"input":{"raw":false,"prompt":"Close-up shot of a diverse human hand gently touching a shimmering, holographic AI interface that displays abstract data visualizations. The background is blurred, showing a modern, bright call center environment. Soft, diffused lighting with hints of neon blue and teal. Ethereal, optimistic mood. Style: Digital art, futuristic, ethereal. Color palette: Pastel blues, teals, and whites with subtle glowing accents. Composition: Focus on the hand and interface, using shallow depth of field.","aspect_ratio":"1:1","output_format":"jpg","safety_tolerance":6},"model":"black-forest-labs/flux-1.1-pro-ultra","output":"https://replicate.delivery/xezq/LJHVcPdaiexmPyfQnkDBi6o2HpkFWKzeoktHmS5uEvgSIgRpA/tmptt54uqf7.jpg","status":"processing","version":"hidden","created_at":"2025-05-02T23:02:24.29Z","data_removed":false}],"2. Prepare Input Variables (Topic, Audience, etc.)":[{"Topic":"Call Center Transformation with Artificial Intelligence","Platform":"Instagram","BrandVoice":"Informative, reassuring, innovative","TargetAudience":"Enterprise customers, tech enthusiasts, customer experience professionals"}]},"connections":{"(Parse Caption JSON)":{"ai_outputParser":[[{"node":"3c. Generate Post Caption (Gemini)","type":"ai_outputParser","index":0}]]},"(Parse Concept JSON)":{"ai_outputParser":[[{"node":"3a. Generate Content Concept (Gemini)","type":"ai_outputParser","index":0}]]},"(Parse Prompts JSON)":{"ai_outputParser":[[{"node":"3b. Generate Image Prompt Options (Gemini)","type":"ai_outputParser","index":0}]]},"(LLM Model for Caption)":{"ai_languageModel":[[{"node":"3c. Generate Post Caption (Gemini)","type":"ai_languageModel","index":0}]]},"(LLM Model for Concept)":{"ai_languageModel":[[{"node":"3a. Generate Content Concept (Gemini)","type":"ai_languageModel","index":0}]]},"(LLM Model for Prompts)":{"ai_languageModel":[[{"node":"3b. Generate Image Prompt Options (Gemini)","type":"ai_languageModel","index":0}]]},"6c. Publish Post to Instagram":{"main":[[{"node":"7. Update Post Status in Sheet","type":"main","index":0}]]},"1. Get Next Post Idea from Sheet":{"main":[[{"node":"2. Prepare Input Variables (Topic, Audience, etc.)","type":"main","index":0}]]},"5. Prepare Data for Instagram API":{"main":[[{"node":"6a. Create Instagram Media Container","type":"main","index":0}]]},"6b. Wait for Container Processing":{"main":[[{"node":"6c. Publish Post to Instagram","type":"main","index":0}]]},"3c. Generate Post Caption (Gemini)":{"main":[[{"node":"4. Generate Image using Prompt 1 (Replicate Flux)","type":"main","index":0}]]},"6a. Create Instagram Media Container":{"main":[[{"node":"6b. Wait for Container Processing","type":"main","index":0}]]},"Scheduled Start: Check for New Posts":{"main":[[{"node":"1. Get Next Post Idea from Sheet","type":"main","index":0}]]},"3a. Generate Content Concept (Gemini)":{"main":[[{"node":"3b. Generate Image Prompt Options (Gemini)","type":"main","index":0}]]},"3b. Generate Image Prompt Options (Gemini)":{"main":[[{"node":"3c. Generate Post Caption (Gemini)","type":"main","index":0}]]},"4. Generate Image using Prompt 1 (Replicate Flux)":{"main":[[{"node":"5. Prepare Data for Instagram API","type":"main","index":0}]]},"2. Prepare Input Variables (Topic, Audience, etc.)":{"main":[[{"node":"3a. Generate Content Concept (Gemini)","type":"main","index":0}]]}}}


name: AI Customer Support Assistant · WhatsApp Ready · Works for Any Business
author_name: Matt F.
author_username: lemolex
categories: []
created_at: 2025-05-04T13:37:44.027Z
updated_at: 2025-05-04T13:37:44.027Z
url: https://n8n.io/workflows/3859
scraped_at: 2025-07-06 21:16:30
description:
  AI Customer-Support Assistant that auto-maps any business site, answers WhatsApp in real time, and lets you earn or save thousands by replacing pricey SaaS chat tools.

  ⚡ What the workflow does

  Live “AI employee”* - the bot crawls pages on demand (products, policies, FAQs) so you *never** upload documents or fine-tune a model.
  No-code setup** - Drop in API keys, paste your domain, publish the webhook—ready in \~15 min.
  Chat memory** - each conversation turn is written to Supabase/Postgres and automatically replayed into the next prompt, letting the assistant remember context so follow-up questions feel natural and coherent even across long sessions.
  WhatsApp ready** - Free-form replies inside the 24-hour service window, automatically switches to a template when required (recommended by Meta).

  🚀 Why you’ll love it

  | Benefit                   | Impact                                                                |
  | ------------------------- | --------------------------------------------------------------------- |
  | Zero content training | Point the AI Agent at any domain → go live.                            |
  | Save or earn money    | Replace pricey SaaS chat tools or sell white-label bots to clients. |
  | Channel-agnostic      | Ships with WhatsApp; swap one node for Telegram, Slack, or web chat.  |
  | Flexible voice        | Adjust tone & language by editing one prompt line.                    |

  🧰 Prerequisites (all free-tier friendly)

  OpenAI key
  Meta WhatsApp Cloud API number + permanent token (easy setup)
  Supabase (or Postgres) URL for chat memory (easy setup)

  🛠 5-step setup

  Import the template into n8n.
  Add credentials for OpenAI, WhatsApp, and Supabase.
  Enter your root domain in the root\_url variable.
  Point Meta’s Webhook to the n8n URL.
  Hit Execute Trigger and send “Hi” from WhatsApp—watch the bot answer with live data.

  🔄 Easy to extend

  Voice & language** – change wording in the System Prompt.
  Escalation** – add an “If fallback” branch → Zendesk, email, live agents.
  Extra channels** – duplicate the reply node for Telegram or Slack.
  Commerce API hooks** – plug in Shopify, Woo, Stripe for order status or payments.

  💡 Monetization ideas

  Replace costly SaaS seats.* Deploy the bot on your own server and *stop paying \$300–\$500 every month for third-party “AI support” platforms.
  Sell it as a service.* Spin up a branded instance for local shops, clinics, or e-commerce stores and *charge each client \$300–\$500 per month**—setup time is under 15 minutes.
  Upsell premium coverage (24/7 human hand-off) once the bot handles routine questions.
  Embed product links in answers and earn affiliate or upsell revenue automatically.

  Spin it up, connect a domain and a phone number, and you—or your customers—get enterprise-grade support without code, training, or ongoing licence fees.

nodes_used:
  ["n8n-nodes-base.whatsApp","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.memoryPostgresChat","@n8n/n8n-nodes-langchain.toolHttpRequest"]

workflow_json:
  {"id":"J2D0BssoDmn4BC6D","meta":{"instanceId":"143d2ab55c8bffb06f8b9c7ad30335764fdc48bbbacecbe2218dadb998a32213","templateCredsSetupCompleted":true},"name":"AI Customer-Support Assistant · WhatsApp Ready · Works for Any Business","tags":[],"nodes":[{"id":"fe395033-e36e-42d4-a0ce-8362b172be31","name":"AI Agent","type":"@n8n/n8n-nodes-langchain.agent","maxTries":5,"position":[120,140],"parameters":{"text":"={{ $json.messages[0].text.body }}","options":{"maxIterations":10,"systemMessage":"=You are [Company Name]’s real-time website assistant for https://www.your-company-url.com.\n\nAVAILABLE TOOLS\n• list_links(url) → { urls:[ … ] }  — returns up to 100 internal links from that page  \n• get_page(url)   → { text:\"…\" }    — returns the visible, tag-free text of the page (JavaScript rendered if needed)\n\nSEARCH STRATEGY\n1. Start with list_links on the root page.  \n2. Pick ≤ 5 links whose URL or anchor text best match the user’s question (producto, pago, envío, servicio, política, etc.).  \n3. For each chosen link call get_page once.  \n4. Read the returned text and look for the answer.  \n5. If the answer is still unknown, you may repeat steps 1-4 one level deeper.  \n6. Stop after two list_links rounds **or** eight get_page calls (whichever comes first).\n\nANSWER RULES\n• Reply in clear and friendly toon **as part of [Company Name]** (use “we”, “our”).  \n• Keep answers concise but complete.  \n• **No Markdown ni símbolos de formato. Nunca uses \\*, **, \\_, \\~, ni [texto](url).**  \n  Write urls like: Descriptive Text ␣URL   Ej.: Combos https://…  \n• Quote the exact wording for facts such as stock status, prices, envíos, métodos de pago, garantías o políticas.  \n• If the information is not on the site, reply exactly:  \n  “I can't find that information on our site right now. Do you want me to put you through to a human agent?”  \n• Stay on-domain; ignore mailto:, tel:, javascript:, or off-site links.\n• Finally, if any of the tools returns a status code 404, then reply:\n\"Non-subscribed user.\"","returnIntermediateSteps":true},"promptType":"define","hasOutputParser":true},"retryOnFail":false,"typeVersion":1.7,"alwaysOutputData":true,"waitBetweenTries":null},{"id":"3953a213-6140-4603-a069-93718e4d8982","name":"list_links","type":"@n8n/n8n-nodes-langchain.toolHttpRequest","position":[260,420],"parameters":{"url":"https://lemolex.app.n8n.cloud/webhook/list-links","method":"POST","sendBody":true,"parametersBody":{"values":[{"name":"url","value":"https://www.your-company-url.com","valueProvider":"fieldValue"},{"name":"auth-token","value":"your-auth-token (read setup guide)","valueProvider":"fieldValue"}]},"toolDescription":"Returns up to 100 unique, fully-qualified INTERNAL links for a given page.\n\nInput  (JSON body the model must supply)\n  {\n    \"url\": \"<absolute https://…>\"\n  }\n\nBehaviour\n  • Crawls only the domain of the input URL.\n  • Converts relative <a href> values to absolute URLs.\n  • Drops empty roots (\"/\"), mailto:, tel:, javascript:, and off-site links.\n  • De-duplicates the list.\n  • Responds with a JSON object:\n\n      {\n        \"urls\": [ \"<link-1>\", \"<link-2>\", … ]\n      }\n\nUse this tool when you need a navigation map of the current page.\nPass one of the returned URLs back into other tools (e.g. get_text) to read its content.\n"},"typeVersion":1.1},{"id":"21ceaf5e-d2d4-47c3-98cb-ee7c0ab0fcab","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[40,340],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"jh4eAOIykIxQWUI9","name":"OpenAi account"}},"typeVersion":1.2},{"id":"7e0e84c8-ad96-44d1-9de9-c639230418fd","name":"WhatsApp Trigger","type":"n8n-nodes-base.whatsAppTrigger","position":[-260,140],"webhookId":"857366e8-7b6f-45a7-bbd1-f876002620d7","parameters":{"updates":["messages"]},"credentials":{"whatsAppTriggerApi":{"id":"EB6eAVg9ZBZGYsyX","name":"WhatsApp OAuth account"}},"typeVersion":1},{"id":"c2a0ba34-4a23-4918-9be8-7b9d50279cde","name":"cleanAnswer","type":"n8n-nodes-base.code","position":[1040,120],"parameters":{"jsCode":"// cleanAnswer – run once per item\nlet txt = $('AI Agent').first().json.output || '';\n\n// 1. Remove bold / italic / strike markers\ntxt = txt.replace(/[*_~]+/g, '');\n\n// 2. Convert [Texto](https://url) → Texto https://url\ntxt = txt.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g, '$1 $2');\n\n// 3. Collapse 3+ blank lines\ntxt = txt.replace(/\\n{3,}/g, '\\n\\n').trim();\n\nreturn [{ json: { answer: txt } }];\n"},"typeVersion":2},{"id":"ef403af2-4543-4edb-80ae-afda1e98a2a9","name":"get_page","type":"@n8n/n8n-nodes-langchain.toolHttpRequest","position":[420,420],"parameters":{"url":"https://lemolex.app.n8n.cloud/webhook/get_text","method":"POST","sendBody":true,"parametersBody":{"values":[{"name":"url"},{"name":"auth-token","value":"your-auth-token (read setup guide)","valueProvider":"fieldValue"}]},"toolDescription":"Fetches the fully-rendered **plain text** of a single web.  \n• Input  : { \"url\": \"<absolute https://…>\" }  \n• Auth   : token is sent as HTTP basic-auth.  \n• Query  : url=<encoded url>  \n• Output : { \"text\": \"<visible text of the body>\", \"url\": \"<same url>\" }  \n• The \"text\" field already has **all HTML tags removed** .  \n• Use this tool whenever you need the actual words that appear on the page—product details, prices, stock lines, shipping terms, payment options, company policies, etc.  \n• Do **not** call it on off-site links or mailto:/tel:/javascript: pseudo-links.  \n"},"typeVersion":1.1},{"id":"46c1fd08-9b61-4ea9-bee3-9ad8b7e7ce4d","name":"24-hour window check","type":"n8n-nodes-base.code","position":[520,140],"parameters":{"jsCode":"// within24h?  – run once per item\n// Meta (WhatsApp) timestamp arrives as seconds since epoch\nconst lastTs = Number($('WhatsApp Trigger').first().json.messages[0].timestamp) * 1000;   // → ms\nconst withinWindow = Date.now() - lastTs < 24 * 60 * 60 * 1000;\n\nreturn [{ json: { withinWindow, answer: $json.answer, userId: $json.userId } }];"},"typeVersion":2},{"id":"0309e9fb-745e-46cd-a360-a6a4a96ffa36","name":"If","type":"n8n-nodes-base.if","position":[740,140],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"d33e218e-a49a-49ed-9c6b-55b9ea0b0dbb","operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.withinWindow }}","rightValue":""}]}},"typeVersion":2.2},{"id":"e0f6e0b0-d2f8-4be5-85e4-74b351390369","name":"Send Pre-approved Template Message to Reopen the Conversation","type":"n8n-nodes-base.whatsApp","position":[1060,360],"parameters":{"template":"hello_world|en_US","phoneNumberId":"679436108574898","requestOptions":{},"recipientPhoneNumber":"={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}"},"credentials":{"whatsAppApi":{"id":"zNN8ICsFZI5A7frT","name":"WhatsApp account"}},"typeVersion":1},{"id":"fd41fbf2-f471-4529-bb4d-358ace9cf639","name":"Send AI Agent's Answer","type":"n8n-nodes-base.whatsApp","position":[1260,120],"parameters":{"textBody":"={{ $json.answer }}","operation":"send","phoneNumberId":"679436108574898","requestOptions":{},"additionalFields":{},"recipientPhoneNumber":"={{ $('WhatsApp Trigger').item.json.contacts[0].wa_id }}"},"credentials":{"whatsAppApi":{"id":"zNN8ICsFZI5A7frT","name":"WhatsApp account"}},"typeVersion":1},{"id":"35e6c77f-56c5-4b93-a69a-e048b593cf40","name":"Postgres Users Memory","type":"@n8n/n8n-nodes-langchain.memoryPostgresChat","position":[120,500],"parameters":{"tableName":"message_history","sessionKey":"={{ $json.contacts[0].wa_id }}","sessionIdType":"customKey"},"credentials":{"postgres":{"id":"Bk7n11D1jU5zJ802","name":"Postgres account"}},"typeVersion":1.3},{"id":"67c3296e-8915-4857-a294-03c5bc8257c0","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-920,-320],"parameters":{"width":460,"height":1460,"content":"# Step by Step Setup Guide\n\n### **The technology that powers this AI Agent—continuously crawling, extracting, and generating answers—incurs real operating costs to stay online.**\n### **That’s why the workflow requires an active membership, priced at only **\\$29 per month**. Comparable AI-support platforms charge **\\$150 – \\$500 each month**, so with this template you either save a large chunk of that expense for your own business or earn the same amount by reselling the chatbot to clients—while paying just \\$29 yourself. *And because the bot pulls fresh information from the site in real time, you never have to “re-train” a model, saving you even more time and money.***\n\n **Activate your membership here:** [https://lemolex.gumroad.com/l/ejsnx](https://lemolex.gumroad.com/l/ejsnx)\n\n### Let's start setting this up step by step:\n*Total hands-on time: ≈ 15 minutes*\n\n1. Activate the tools with the membership generated key: \n- Go to the membership link to get your key.\n- Copy the key (e.g 6F0E4C97-B72A4E69-A11BF6C4-AF123456) and paste it in the body parameters of list_links and get_page (tools):\n*Name: auth-token*\n*Value:  6F0E4C97-B72A4E69-A11BF6C4-AF123456* **(example)**\n\n2. Customize for Your Company:\n- Copy the Root URL of your company's website (Home Page).\n- Open the AI Agent node and inside the `System Message`, change the following values:\n[Company Name] with your company name (e.g [Company Name] -> Facebook)\n[https://www.your-company-url.com] with your company Root URL that you copied before.\nCheck for these 2 values along the entire text.\n- Go back to the tools list_links and get_page and paste the Root URL inside the body parameters, specifically:\n*Name: url*\n*Value: https://www.your-company-url.com **(e.g https://www.facebook.com)**\n\n3. Connect your credentials:\n- Go to the OpenAI Chat Model node and connect your OpenAI credentials.\n- Go to the Postgres Users Memory node and connect your Supabase credentials. A tutorial for this: https://youtu.be/6w5f_jsPYSQ?si=MPdXYUjxv3fghQPj&t=105 (Minute 1:45 to 5:00)\n- Go to the WhatsApp nodes \"WhatsApp Trigger\", \"Send Pre-approved Template Message to Reopen the Conversation\" and \"Send AI Agent's Answer\" to connect your credentials. A tutorial for this: https://youtu.be/ZrhTQle55LQ?si=MO_leooogO9KchCV\n- Go to the \"Send Pre-approved Template Message to Reopen the Conversation\" and select the template message under the \"Template\" parameter.\n***If you don't want to use this feature (not recommended) delete the nodes \"24-hour window check\", \"If\" and \"Send Pre-approved Template Message to Reopen the Conversation\". Then connect the AI Agent node to the \"cleanAnswer\" node.***\n\n\n### **You are ready**"},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"245c3695-7177-4a1d-a33d-7aedd0eccc44","connections":{"If":{"main":[[{"node":"cleanAnswer","type":"main","index":0}],[{"node":"Send Pre-approved Template Message to Reopen the Conversation","type":"main","index":0}]]},"AI Agent":{"main":[[{"node":"24-hour window check","type":"main","index":0}]]},"get_page":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"list_links":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"cleanAnswer":{"main":[[{"node":"Send AI Agent's Answer","type":"main","index":0}]]},"WhatsApp Trigger":{"main":[[{"node":"AI Agent","type":"main","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"AI Agent","type":"ai_languageModel","index":0}]]},"24-hour window check":{"main":[[{"node":"If","type":"main","index":0}]]},"Postgres Users Memory":{"ai_memory":[[{"node":"AI Agent","type":"ai_memory","index":0}]]}}}


name: Automate SEO Blog Content Creation with GPT-4, Perplexity AI and WordPress
author_name: LukaszB
author_username: lukaszb
categories: []
created_at: 2025-05-05T11:17:44.963Z
updated_at: 2025-05-05T11:17:44.963Z
url: https://n8n.io/workflows/3874
scraped_at: 2025-07-06 21:16:30
description:
  AI Blog Publisher – Automated Blog Content Workflow
  This workflow is designed for individuals and teams who regularly publish content on their blog and want to automate the entire process from start to finish. Its main goal is to generate long-form, SEO-optimized blog posts and publish them directly to WordPress — without needing a copywriter, editor, or someone to handle CMS uploads. The workflow generates a topic, writes a full article based on your brand guidelines, pulls a featured image from Pexels, publishes the post to WordPress, and logs the publication details to Google Sheets, creating a complete archive of published content. This allows users to deliver high-quality, search-optimized content every day while saving significant time. The whole process — from idea to publication — runs fully automatically and can be scheduled to execute without any manual input. It works just as well for solo creators as for marketing teams or agencies producing content at scale.

  How it works
  When triggered, the workflow generates a new blog post idea and checks your Google Sheet to see if the topic has already been published. If the topic is unique, it calls a sub-workflow that contains your brand’s writing style, tone, and blog goals. It then uses Perplexity AI to gather supporting research and context. Based on all this input, a complete 2000–2500 word article is generated in clean HTML, ready for WordPress. The workflow then searches Pexels for a relevant image and sets it as the featured image. Finally, it publishes the post to WordPress, including the proper title, meta description, and category. All relevant data — such as title, link, and publish date — is logged in your Google Sheet.

  How to set up
  To get this workflow running, all you need to do is connect the required APIs — OpenAI, WordPress, Google Sheets, and Pexels — and make a few basic adjustments. Replace the placeholder URLs in the HTTP request nodes with your actual WordPress address. In the Google Sheets nodes, add your own spreadsheet ID and tab name. In the node that fetches brand data, insert the workflow ID of your brand brief sub-workflow. You can also personalize the AI prompts by entering your blog name, company name, and a call-to-action to give the content a more tailored voice. Once that’s done, you can trigger the workflow manually or schedule it using the Schedule Trigger node.

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","n8n-nodes-base.wordpress","n8n-nodes-base.notion","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.memoryBufferWindow","@n8n/n8n-nodes-langchain.outputParserStructured","@n8n/n8n-nodes-langchain.toolWorkflow"]

workflow_json:
  {"id":"t3Ss6xuTX0ROhL9y","meta":{"instanceId":"740c1df6d3b1b6ea8a9d2223701f6904173f9e3cf7381cd181644b043420ba09","templateCredsSetupCompleted":true},"name":"blog wordpress copy to sell","tags":[{"id":"yJOJy9E45J8vdOmq","name":"for selling","createdAt":"2025-05-04T19:55:33.401Z","updatedAt":"2025-05-04T19:55:33.401Z"}],"nodes":[{"id":"3f5c8ff4-d3e0-4636-9254-b371054714bd","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-540,-60],"parameters":{"content":""},"typeVersion":1},{"id":"0a7a98d9-e7f6-4203-a1dd-4698cff8cf32","name":"Wordpress","type":"n8n-nodes-base.wordpress","position":[440,40],"parameters":{},"typeVersion":1},{"id":"7ecccfce-c2d8-4009-9670-f14df2f2151c","name":"Sticky Note8","type":"n8n-nodes-base.stickyNote","position":[320,-60],"parameters":{"content":""},"typeVersion":1},{"id":"41ce5060-36b5-4f4a-9888-d2e6ed73bc2c","name":"Upload Image to Wordpress","type":"n8n-nodes-base.httpRequest","onError":"continueRegularOutput","position":[-980,900],"parameters":{},"retryOnFail":false,"typeVersion":4.2,"alwaysOutputData":false},{"id":"0711e9d8-3dfc-4636-b687-d438f5bfecab","name":"Set Image on Wordpress Post","type":"n8n-nodes-base.httpRequest","onError":"continueRegularOutput","position":[-760,900],"parameters":{},"typeVersion":4.2},{"id":"f3d9fbb9-496a-4ea8-bf85-1ed63027b087","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-1080,-40],"parameters":{"content":""},"typeVersion":1},{"id":"1cecbb4d-7d89-46e2-a647-f805ec19dd09","name":"Cleanup Links","type":"n8n-nodes-base.set","position":[-760,40],"parameters":{},"typeVersion":3.4},{"id":"8c51c515-022e-4f02-8aec-cc13b2f48d64","name":"Cleanup HTML ","type":"n8n-nodes-base.set","position":[-20,40],"parameters":{},"typeVersion":3.4},{"id":"e7699ef8-1969-4be6-894c-1fa8c3bf372e","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-1920,660],"parameters":{"content":""},"typeVersion":1},{"id":"ed41870d-1a26-41d5-a00a-e3fbde6874a6","name":"Simple Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[-1700,240],"parameters":{},"typeVersion":1.3},{"id":"0b80d692-ceb9-4045-a2e0-571f9a7688c7","name":"get brand brief","type":"@n8n/n8n-nodes-langchain.toolWorkflow","position":[-1460,240],"parameters":{},"typeVersion":2.1},{"id":"838cdc81-3e01-4d9b-aad1-a00dbfd74a0b","name":"When clicking ‘Test workflow’","type":"n8n-nodes-base.manualTrigger","position":[-2220,-80],"parameters":{},"typeVersion":1},{"id":"3cd9c2c2-31b2-4bf2-b501-7c9acab3d056","name":"Idea creator","type":"@n8n/n8n-nodes-langchain.agent","position":[-1680,40],"parameters":{},"typeVersion":1.8},{"id":"fd023726-8740-465f-ae6f-24eb83dbf9fa","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-1880,-40],"parameters":{"content":""},"typeVersion":1},{"id":"7a40aa12-0265-444d-b6ea-a1eb78ad4ecc","name":"Get Image from Pexcel","type":"n8n-nodes-base.httpRequest","position":[-1420,900],"parameters":{},"typeVersion":4.2},{"id":"cc238320-83f8-441c-afec-aa3e8986c9e9","name":"Download Image","type":"n8n-nodes-base.httpRequest","position":[-1200,900],"parameters":{},"typeVersion":4.2,"alwaysOutputData":true},{"id":"a7254a61-be15-4550-bc39-fe2f8b4ea0bf","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[80,660],"parameters":{"content":""},"typeVersion":1},{"id":"d60a14ec-3d0b-47a6-b78f-1c438a98dbbd","name":"WordPress excerpt add","type":"n8n-nodes-base.httpRequest","position":[-140,900],"parameters":{},"typeVersion":4.2},{"id":"1333951e-9692-4da9-bb37-756af4922c42","name":"Excerpt creator","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-460,900],"parameters":{},"typeVersion":1.6},{"id":"7a30491d-5534-4843-aae0-f62d19be8d11","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[-520,660],"parameters":{"content":""},"typeVersion":1},{"id":"43739994-1507-4ff5-a5db-475247ab63b6","name":"Update list of blog post","type":"n8n-nodes-base.googleSheets","onError":"continueRegularOutput","position":[400,900],"parameters":{},"typeVersion":4.5},{"id":"af0f812f-40bc-4c78-8fcb-e9760c5a9418","name":"Update base post","type":"n8n-nodes-base.googleSheets","position":[600,900],"parameters":{},"typeVersion":4.5},{"id":"0b99028b-d3c7-454d-8916-bb3d9b748b98","name":"Markdown","type":"n8n-nodes-base.markdown","position":[160,900],"parameters":{},"typeVersion":1},{"id":"64cb2685-11a8-4869-acac-fb9ea95d60e2","name":"Schedule Trigger","type":"n8n-nodes-base.scheduleTrigger","position":[-2220,180],"parameters":{},"typeVersion":1.2},{"id":"9d621371-1262-4efb-af27-4cd0b4117632","name":"When Executed by Another Workflow","type":"n8n-nodes-base.executeWorkflowTrigger","position":[-1400,1660],"parameters":{},"typeVersion":1.1},{"id":"05fc5ab2-6075-46b4-866c-f67c0105a9a2","name":"Notion","type":"n8n-nodes-base.notion","position":[-1160,1660],"parameters":{},"typeVersion":2.2},{"id":"93f701bb-a565-41fd-bb6d-337b9d590a95","name":"Aggregate","type":"n8n-nodes-base.aggregate","position":[-940,1660],"parameters":{},"typeVersion":1},{"id":"16b1f1d0-d108-4113-87aa-d4c556a470a9","name":"Edit Fields","type":"n8n-nodes-base.set","position":[-720,1660],"parameters":{},"typeVersion":3.4},{"id":"65b4c6e5-0fc4-4d23-936a-9a7ed5c6f3b7","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[-1560,1440],"parameters":{"content":""},"typeVersion":1},{"id":"b69ba83c-a69f-440a-b9a9-25b50dee3c6f","name":"Write prompt to search image","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-1820,900],"parameters":{},"typeVersion":1.6},{"id":"bc98b818-6bc8-4ae3-8c79-b867fd643402","name":"Content writer","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-400,40],"parameters":{},"typeVersion":1.6},{"id":"5fc72c2e-2fc1-460b-84d9-76664c75a375","name":"OpenAI 4.1 mini","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-400,260],"parameters":{},"typeVersion":1.2},{"id":"760ba592-cc05-4f36-8c07-12ad799e8164","name":"OpenAI 4.1 mini1","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-1820,1100],"parameters":{},"typeVersion":1.2},{"id":"e8169159-0b49-47fb-a409-9a581b2a2cba","name":"OpenAI 4.1 mini2","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-460,1060],"parameters":{},"typeVersion":1.2},{"id":"9b4bc2af-8be6-4bfe-a38a-dc36ced7ba9d","name":"OpenAI 4.1 mini3","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-1840,240],"parameters":{},"typeVersion":1.2},{"id":"edd5f524-0dfa-43af-b010-ba9617f9b07f","name":"Category rewrite","type":"n8n-nodes-base.code","position":[-1320,40],"parameters":{},"typeVersion":2},{"id":"6dbc61dc-8d39-425b-979f-f8bab94963f3","name":"Structured Output Parser","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[-1320,240],"parameters":{},"typeVersion":1.2},{"id":"4d2ac6fe-15f7-494c-b8a8-7b594518450d","name":"Perplexity Research","type":"n8n-nodes-base.httpRequest","position":[-1000,40],"parameters":{},"typeVersion":4.2},{"id":"fb24d40a-6740-4843-a030-ff5ac28bdcd0","name":"Check History","type":"n8n-nodes-base.googleSheetsTool","position":[-1580,240],"parameters":{},"typeVersion":4.5}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"9e71e840-da1c-4dc0-b487-39b0af143bae","connections":{"Notion":{"main":[[{"node":"Aggregate","type":"main","index":0}]]},"Markdown":{"main":[[{"node":"Update list of blog post","type":"main","index":0}]]},"Aggregate":{"main":[[{"node":"Edit Fields","type":"main","index":0}]]},"Wordpress":{"main":[[{"node":"Write prompt to search image","type":"main","index":0}]]},"Idea creator":{"main":[[{"node":"Category rewrite","type":"main","index":0}]]},"Check History":{"ai_tool":[[{"node":"Idea creator","type":"ai_tool","index":0}]]},"Cleanup HTML ":{"main":[[{"node":"Wordpress","type":"main","index":0}]]},"Cleanup Links":{"main":[[{"node":"Content writer","type":"main","index":0}]]},"Simple Memory":{"ai_memory":[[{"node":"Idea creator","type":"ai_memory","index":0}]]},"Content writer":{"main":[[{"node":"Cleanup HTML ","type":"main","index":0}]]},"Download Image":{"main":[[{"node":"Upload Image to Wordpress","type":"main","index":0}]]},"Excerpt creator":{"main":[[{"node":"WordPress excerpt add","type":"main","index":0}]]},"OpenAI 4.1 mini":{"ai_languageModel":[[{"node":"Content writer","type":"ai_languageModel","index":0}]]},"get brand brief":{"ai_tool":[[{"node":"Idea creator","type":"ai_tool","index":0}]]},"Category rewrite":{"main":[[{"node":"Perplexity Research","type":"main","index":0}]]},"OpenAI 4.1 mini1":{"ai_languageModel":[[{"node":"Write prompt to search image","type":"ai_languageModel","index":0}]]},"OpenAI 4.1 mini2":{"ai_languageModel":[[{"node":"Excerpt creator","type":"ai_languageModel","index":0}]]},"OpenAI 4.1 mini3":{"ai_languageModel":[[{"node":"Idea creator","type":"ai_languageModel","index":0}]]},"Schedule Trigger":{"main":[[{"node":"Idea creator","type":"main","index":0}]]},"Perplexity Research":{"main":[[{"node":"Cleanup Links","type":"main","index":0}]]},"Get Image from Pexcel":{"main":[[{"node":"Download Image","type":"main","index":0}]]},"WordPress excerpt add":{"main":[[{"node":"Markdown","type":"main","index":0}]]},"Structured Output Parser":{"ai_outputParser":[[{"node":"Idea creator","type":"ai_outputParser","index":0}]]},"Update list of blog post":{"main":[[{"node":"Update base post","type":"main","index":0}]]},"Upload Image to Wordpress":{"main":[[{"node":"Set Image on Wordpress Post","type":"main","index":0}]]},"Set Image on Wordpress Post":{"main":[[{"node":"Excerpt creator","type":"main","index":0}]]},"Write prompt to search image":{"main":[[{"node":"Get Image from Pexcel","type":"main","index":0}]]},"When Executed by Another Workflow":{"main":[[{"node":"Notion","type":"main","index":0}]]},"When clicking ‘Test workflow’":{"main":[[{"node":"Idea creator","type":"main","index":0}]]}}}


name: Build an MCP Server with Airtable
author_name: Aitor | 1Node
author_username: aitoralonso
categories: []
created_at: 2025-05-05T17:38:07.713Z
updated_at: 2025-05-05T17:38:07.713Z
url: https://n8n.io/workflows/3879
scraped_at: 2025-07-06 21:16:30
description:
  Who is this for?
  This template is designed for anyone who wants to integrate MCP with their AI Agents using Airtable. Whether you're a developer, a data analyst, or an automation enthusiast, if you're looking to leverage the power of MCP and Airtable in your n8n workflows, this template is for you.

  What problem is this workflow solving?
  This template caters to MCP beginners seeking a hands-on example and developers looking to integrate Airtable MCP service. When integrating MCP with Airtable, manually updating AI Agents after changes to Airtable data on the MCP Server is time-consuming and error-prone.

  This template automates the process, enabling the AI Agent to instantly recognize changes made to Airtable on the MCP Server. In data management, for example, it ensures that record updates or additions in Airtable are automatically detected by the AI Agent. With detailed steps, it simplifies the integration process for all users.

  What this workflow does
  This workflow focuses on integrating MCP with Airtable within n8n. Specifically, it allows you to build an MCP Server and Client using Airtable nodes in n8n. Any changes made to the Airtable Base/Table on the MCP Server are automatically recognized by the MCP Client in the workflow. This means that you can make changes to your Airtable (such as adding, deleting, or modifying records) on the MCP Server, and the MCP Client in the n8n workflow will immediately detect these changes without any manual intervention.

  Setup

  Requirements
  An active n8n account.
  Access to Airtable API.
  A sample base and rows in Airtable that you can use to test.
  An API key from your preferred LLM to power the AI agent.

  Step-by-step guide
  Create a new workflow in n8n: Log in to your n8n account and create a new workflow.
  Add Airtable nodes: Search for and add the Airtable nodes to your workflow that you wish the MCP client to have access to.
  Set up the MCP Server and Client: Use the appropriate nodes in n8n to set up the MCP Server and Client. Connect the Airtable nodes to the MCP nodes as required.
  Activate and test the workflow: Talk to the chat trigger once all credentials have been updated and table data synced and try adding some rows, deleting or finding and updating cells.

  How to customize this workflow to your needs
  If you want to customize this workflow, you can:
  Modify the triggers:** You can change the conditions under which the MCP Client detects changes. For example, you can set it to detect changes only in specific fields or based on certain record values in Airtable.
  Integrate with other services:** You can add more nodes to the workflow to integrate with other services, such as sending notifications to Slack or triggering further actions based on the detected Airtable changes.

  Need help?

  Feel free to contact us at 1 Node.
  Get instant access to a library of free resources we created.

nodes_used:
  ["@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.memoryBufferWindow","@n8n/n8n-nodes-langchain.mcpClientTool"]

workflow_json:
  {"id":"kS9EfgZeaK3QV6Mw","meta":{"instanceId":"9219ebc7795bea866f70aa3d977d54417fdf06c41944be95e20cfb60f992db19","templateCredsSetupCompleted":true},"name":"Build an MCP server with Airtable","tags":[],"nodes":[{"id":"357649f0-43c5-4d6c-97b9-079fa3b5c1f3","name":"When chat message received","type":"@n8n/n8n-nodes-langchain.chatTrigger","position":[-100,-80],"webhookId":"c42d1e2e-b175-48cf-bfd4-aa8289266a20","parameters":{"options":{}},"typeVersion":1.1},{"id":"ddf28f88-d76c-4ab6-82c4-c1ab1b746009","name":"AI Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[152,-180],"parameters":{"options":{}},"typeVersion":1.9},{"id":"3170d4fd-700c-4449-a800-0395c06711aa","name":"Simple Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[260,40],"parameters":{},"typeVersion":1.3},{"id":"557b0e0a-133b-4e80-afba-408803ed9898","name":"Airtable MCP Client","type":"@n8n/n8n-nodes-langchain.mcpClientTool","position":[600,100],"parameters":{"sseEndpoint":"https://your-sse-endpoint-url"},"typeVersion":1},{"id":"a0bc9aa3-decb-42f1-bee4-b9e425db81e8","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[80,40],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o","cachedResultName":"gpt-4o"},"options":{}},"credentials":{"openAiApi":{"id":"vupAk5StuhOafQcb","name":"OpenAi account"}},"typeVersion":1.2},{"id":"7737e491-ddd4-4e4f-a34d-73f518497990","name":"MCP Server Trigger","type":"@n8n/n8n-nodes-langchain.mcpTrigger","position":[140,240],"webhookId":"a93f35fb-3a86-4475-9ebd-1434aef8e433","parameters":{"path":"insert-your-cool-path-here"},"typeVersion":1},{"id":"0ce9e128-be31-41d8-ae06-894316781358","name":"Get","type":"n8n-nodes-base.airtableTool","position":[0,460],"parameters":{"id":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Record_ID', ``, 'string') }}","base":{"__rl":true,"mode":"list","value":"appltMFy409fOqCVt","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt","cachedResultName":"AI news and social posts"},"table":{"__rl":true,"mode":"list","value":"tblZwA0JCNPeORaGi","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt/tblZwA0JCNPeORaGi","cachedResultName":"Social Posts"},"options":{}},"credentials":{"airtableTokenApi":{"id":"4hNTBxRPe8ft4Iic","name":"Airtable Personal Access Token account"}},"typeVersion":2.1},{"id":"1f9c6a61-9357-4fa1-81e0-42719284d291","name":"Search","type":"n8n-nodes-base.airtableTool","position":[140,460],"parameters":{"base":{"__rl":true,"mode":"list","value":"appltMFy409fOqCVt","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt","cachedResultName":"AI news and social posts"},"table":{"__rl":true,"mode":"list","value":"tblZwA0JCNPeORaGi","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt/tblZwA0JCNPeORaGi","cachedResultName":"Social Posts"},"options":{},"operation":"search","returnAll":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Return_All', ``, 'boolean') }}","filterByFormula":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Filter_By_Formula', ``, 'string') }}"},"credentials":{"airtableTokenApi":{"id":"4hNTBxRPe8ft4Iic","name":"Airtable Personal Access Token account"}},"typeVersion":2.1},{"id":"061a0eb9-26de-47f1-b444-5dd98c984d70","name":"Update","type":"n8n-nodes-base.airtableTool","position":[260,460],"parameters":{"base":{"__rl":true,"mode":"list","value":"appltMFy409fOqCVt","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt","cachedResultName":"AI news and social posts"},"table":{"__rl":true,"mode":"list","value":"tblZwA0JCNPeORaGi","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt/tblZwA0JCNPeORaGi","cachedResultName":"Social Posts"},"columns":{"value":{},"schema":[{"id":"id","type":"string","display":true,"removed":false,"readOnly":true,"required":false,"displayName":"id","defaultMatch":true},{"id":"sourceHeadline","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceHeadline","defaultMatch":false,"canBeUsedToMatch":true},{"id":"sourceSummary","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceSummary","defaultMatch":false,"canBeUsedToMatch":true},{"id":"goToArticle","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"goToArticle","defaultMatch":false,"canBeUsedToMatch":true},{"id":"sourceURL","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceURL","defaultMatch":false,"canBeUsedToMatch":true},{"id":"socialChannels","type":"array","display":true,"options":[{"name":"Twitter","value":"Twitter"},{"name":"LinkedIn","value":"LinkedIn"},{"name":"Blog","value":"Blog"},{"name":"Instagram","value":"Instagram"},{"name":"Facebook","value":"Facebook"}],"removed":false,"readOnly":false,"required":false,"displayName":"socialChannels","defaultMatch":false,"canBeUsedToMatch":true},{"id":"needsImage?","type":"options","display":true,"options":[{"name":"Yes","value":"Yes"},{"name":"No","value":"No"}],"removed":false,"readOnly":false,"required":false,"displayName":"needsImage?","defaultMatch":false,"canBeUsedToMatch":true},{"id":"twitterCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"twitterCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"linkedinCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"linkedinCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"instagramCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"instagramCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"facebookCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"facebookCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"blogCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"blogCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"imagePrompt","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"imagePrompt","defaultMatch":false,"canBeUsedToMatch":true},{"id":"postImage","type":"array","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"postImage","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Status","type":"options","display":true,"options":[{"name":"Waiting for Content","value":"Waiting for Content"},{"name":"Needs Approval","value":"Needs Approval"},{"name":"Approved","value":"Approved"},{"name":"Posted","value":"Posted"}],"removed":false,"readOnly":false,"required":false,"displayName":"Status","defaultMatch":false,"canBeUsedToMatch":true},{"id":"datePosted","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"datePosted","defaultMatch":false,"canBeUsedToMatch":true},{"id":"ID","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"ID","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"autoMapInputData","matchingColumns":["id"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update"},"credentials":{"airtableTokenApi":{"id":"4hNTBxRPe8ft4Iic","name":"Airtable Personal Access Token account"}},"typeVersion":2.1},{"id":"b0e17724-5a56-4b71-997d-f9f44d16e5bc","name":"Delete","type":"n8n-nodes-base.airtableTool","position":[400,460],"parameters":{"id":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Record_ID', ``, 'string') }}","base":{"__rl":true,"mode":"list","value":"appltMFy409fOqCVt","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt","cachedResultName":"AI news and social posts"},"table":{"__rl":true,"mode":"list","value":"tblZwA0JCNPeORaGi","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt/tblZwA0JCNPeORaGi","cachedResultName":"Social Posts"},"operation":"deleteRecord"},"credentials":{"airtableTokenApi":{"id":"4hNTBxRPe8ft4Iic","name":"Airtable Personal Access Token account"}},"typeVersion":2.1},{"id":"2d0273b6-520b-45b7-8192-a83b10661028","name":"Create","type":"n8n-nodes-base.airtableTool","position":[520,460],"parameters":{"base":{"__rl":true,"mode":"list","value":"appltMFy409fOqCVt","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt","cachedResultName":"AI news and social posts"},"table":{"__rl":true,"mode":"list","value":"tblZwA0JCNPeORaGi","cachedResultUrl":"https://airtable.com/appltMFy409fOqCVt/tblZwA0JCNPeORaGi","cachedResultName":"Social Posts"},"columns":{"value":{},"schema":[{"id":"sourceHeadline","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceHeadline","defaultMatch":false,"canBeUsedToMatch":true},{"id":"sourceSummary","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceSummary","defaultMatch":false,"canBeUsedToMatch":true},{"id":"goToArticle","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"goToArticle","defaultMatch":false,"canBeUsedToMatch":true},{"id":"sourceURL","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"sourceURL","defaultMatch":false,"canBeUsedToMatch":true},{"id":"socialChannels","type":"array","display":true,"options":[{"name":"Twitter","value":"Twitter"},{"name":"LinkedIn","value":"LinkedIn"},{"name":"Blog","value":"Blog"},{"name":"Instagram","value":"Instagram"},{"name":"Facebook","value":"Facebook"}],"removed":false,"readOnly":false,"required":false,"displayName":"socialChannels","defaultMatch":false,"canBeUsedToMatch":true},{"id":"needsImage?","type":"options","display":true,"options":[{"name":"Yes","value":"Yes"},{"name":"No","value":"No"}],"removed":false,"readOnly":false,"required":false,"displayName":"needsImage?","defaultMatch":false,"canBeUsedToMatch":true},{"id":"twitterCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"twitterCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"linkedinCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"linkedinCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"instagramCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"instagramCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"facebookCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"facebookCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"blogCopy","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"blogCopy","defaultMatch":false,"canBeUsedToMatch":true},{"id":"imagePrompt","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"imagePrompt","defaultMatch":false,"canBeUsedToMatch":true},{"id":"postImage","type":"array","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"postImage","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Status","type":"options","display":true,"options":[{"name":"Waiting for Content","value":"Waiting for Content"},{"name":"Needs Approval","value":"Needs Approval"},{"name":"Approved","value":"Approved"},{"name":"Posted","value":"Posted"}],"removed":false,"readOnly":false,"required":false,"displayName":"Status","defaultMatch":false,"canBeUsedToMatch":true},{"id":"datePosted","type":"string","display":true,"removed":false,"readOnly":false,"required":false,"displayName":"datePosted","defaultMatch":false,"canBeUsedToMatch":true},{"id":"ID","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"ID","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"autoMapInputData","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"create"},"credentials":{"airtableTokenApi":{"id":"4hNTBxRPe8ft4Iic","name":"Airtable Personal Access Token account"}},"typeVersion":2.1},{"id":"69e906cf-82da-45c4-bacc-00970902d1f5","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[480,-60],"parameters":{"width":360,"height":280,"content":"## Update SSE endpoint "},"typeVersion":1},{"id":"819d82c9-da54-48c6-a007-2e8750cfb3e2","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-520,-220],"parameters":{"width":380,"height":540,"content":"## Talk to your Airtable database \nPoint to your SSE endpoint, update your credentials and talk to your Airtable to:\n\n- Get records\n- Search records\n- Update records\n- Delete records\n- Create records\n\nand more!\n\nThis example showcases basic yet powerful functionality for a table.\n\nFeel free to combine it with other tools, connect a Slack channel as trigger node or another as output to receive the updates for the stakeholders and project owners.\n\nEnjoy!\n\nAitor\n[1 Node](https://1node.ai)"},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"2355fe0d-0515-4d1b-8a02-42712191f466","connections":{"Get":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Create":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Delete":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Search":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Update":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"AI Agent":{"main":[[]]},"Simple Memory":{"ai_memory":[[{"node":"AI Agent","type":"ai_memory","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"AI Agent","type":"ai_languageModel","index":0}]]},"Airtable MCP Client":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"When chat message received":{"main":[[{"node":"AI Agent","type":"main","index":0}]]}}}


name: Automated YouTube Video Scheduling & AI Metadata Generation 🎬
author_name: JPres
author_username: stardawnai
categories: []
created_at: 2025-05-06T14:51:43.081Z
updated_at: 2025-05-06T14:51:43.081Z
url: https://n8n.io/workflows/3900
scraped_at: 2025-07-06 21:16:30
description:
  👥 Who Is This For?
  Content creators, marketing teams, and channel managers who need to streamline video publishing with optimized metadata and scheduled releases across multiple videos.

  🛠 What Problem Does This Solve?
  Manual YouTube video publishing is time-consuming and often results in inconsistent descriptions, tags, and scheduling. This workflow fully automates:
  Extracting video transcripts via Apify for metadata generation
  Creating SEO-optimized descriptions and tags for each video
  Setting videos to private during initial upload (critical for scheduling)
  Implementing scheduled publishing at strategic times
  Maintaining consistent branding and formatting across all content

  🔄 Node-by-Node Breakdown
  | Step | Node Purpose |
  |------|--------------|
  | 1 | Every Day (Scheduler) | Trigger workflow on a regular schedule |
  | 2 | Get Videos to Harmonize | Retrieve videos requiring metadata enhancement |
  | 3 | Get Video IDs (Unpublished) | Filter for videos that need publishing |
  | 4 | Loop over Video IDs | Process each video individually |
  | 5 | Get Video Data | Retrieve metadata for the current video |
  | 6 | Loop over Videos with Parameter IS | Set parameters for processing |
  | 7 | Set Videos to Private | Ensure videos are private (required for scheduling) |
  | 8 | Apify: Get Transcript | Extract video transcript via Apify |
  | 9 | Fetch Latest Videos | Get most recent channel content |
  | 10 | Loop Over Items | Process each video item |
  | 11 | Generate Description, Tags, etc. | Create optimized metadata from transcript |
  | 12 | AP Clean ID | Format identifiers |
  | 13 | Retrieve Generated Data | Collect the enhanced metadata |
  | 14 | Adjust Transcript Format | Format transcript for better processing |
  | 15 | Update Video's Metadata | Apply generated description and tags to video |

  ⚙️ Pre-conditions / Requirements
  n8n with YouTube API credentials configured
  Apify account with API access for transcript extraction
  YouTube channel with upload permissions
  Master templates for description formatting
  Videos must be initially set to private for scheduling to work

  ⚙️ Setup Instructions
  Import this workflow into your n8n instance.
  Configure YouTube API credentials with proper channel access.
  Set up Apify integration with appropriate actor for transcript extraction.
  Define scheduling parameters in the Every Day node.
  Configure description templates with placeholders for dynamic content.
  Set default tags and customize tag generation rules.
  Test with a single video before batch processing.

  🎨 How to Customize
  Adjust prompt templates for description generation to match your brand voice.
  Modify tag selection algorithms based on your channel's SEO strategy.
  Create multiple publishing schedules for different content categories.
  Integrate with analytics tools to optimize publishing times.
  Add notification nodes to alert when videos are successfully scheduled.

  ⚠️ Important Notes
  Videos MUST be uploaded as private initially - the Publish At logic only works for private videos that haven't been published before.
  Publishing schedules require videos to remain private until their scheduled time.
  Transcript quality affects metadata generation results.
  Consider YouTube API quotas when scheduling large batches of videos.

  🔐 Security and Privacy
  API credentials are stored securely within n8n.
  Transcripts are processed temporarily and not stored permanently.
  Webhook URLs should be protected to prevent unauthorized triggering.
  Access to the workflow should be limited to authorized team members only.

nodes_used:
  ["n8n-nodes-base.httpRequest","n8n-nodes-base.youTube","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.openAi","@n8n/n8n-nodes-langchain.lmChatGoogleGemini"]

workflow_json:
  {"id":"wLbJ7rE6vQzizCp2","meta":{"instanceId":"5ce52989094be90be3b3bdd9ed9cee1d7ce1fcecaa598afaec4a50646d32e291","templateCredsSetupCompleted":true},"name":"Youtube_Automation","tags":[{"id":"5eZb3e5PJspoJjVN","name":"Privat","createdAt":"2025-02-22T09:31:58.972Z","updatedAt":"2025-02-22T09:31:58.972Z"},{"id":"fSDcaaN3w5sV5e3S","name":"Templates","createdAt":"2025-02-23T15:20:47.262Z","updatedAt":"2025-02-23T15:20:47.262Z"}],"nodes":[{"id":"2001b9ca-f76b-437e-90a9-16c0d17accef","name":"Fetch Latest Videos","type":"n8n-nodes-base.youTube","position":[-60,40],"parameters":{"limit":1,"filters":{},"options":{"order":"date"},"resource":"video"},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1},{"id":"e8857adf-63ec-4612-aa49-cd77130a6728","name":"Loop Over Items","type":"n8n-nodes-base.splitInBatches","position":[160,40],"parameters":{"options":{}},"typeVersion":3},{"id":"1a8cf640-caf4-4163-a658-400714702314","name":"YT Title","type":"@n8n/n8n-nodes-langchain.openAi","disabled":true,"position":[2220,120],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1-nano","cachedResultName":"GPT-4.1-NANO"},"options":{},"messages":{"values":[{"role":"system","content":"Du bist ein professioneller Texter für SEO-optimierte YouTube-Titel."},{"content":"=Schreib mir einen passenden SEO Youtube Titel für das Transkript folgendes Videotranskriptes. Gib mir nur den Titel sonst nichts. Maximal 100 Character also halte dich kurz.\n\n{{ $('Adjust Transcript Format').item.json.transcript }}"}]}},"credentials":{"openAiApi":{"id":"ftBgqCi1fD1fFEZq","name":"Midgard#1"}},"typeVersion":1.7},{"id":"ec6e5d83-d8c8-417e-8df0-86634feef3e6","name":"Create Description","type":"@n8n/n8n-nodes-langchain.openAi","position":[1920,80],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1-nano","cachedResultName":"GPT-4.1-NANO"},"options":{},"messages":{"values":[{"role":"system","content":"Du bist ein professioneller Texteschreiber.\nDu erhältst das Transkript eines wirtschaftsbezogenen Videos und erstellst eine ausführlichere aber auch nicht zu lange  Zusammenfassung (mit Absätzen) darüber, worum es geht.\n\nSchreibe eine ausführlichere Zusammenfassung (mit Absätzen) über den Inhalt des Podcasts. \n\nDein Output wird für die Youtube Video Beschreibung verwendet. Also starte mit sowas wie: \"In diesem Video...\" oder \"In dieser Folge...\". \nSchreibe aus meiner Perspektive also Sachen wie \"meine Meinung\" oder \"meiner Ansicht nach\"...  aus der Ich- Perspektive aber niemals sowas wie \"In dieser Folge lerne ich...\" oder so ähnlich, denn ich erkläre stets den Inhalt bzw. diskutiere darüber. DU SCHREIBST NIEMALS SOWAS WIE \"DER SPRECHER SAGT\"!!! Immer aus meiner Position heraus.\n\nWichtig: Verwende klare und dominante Aussagen, wie sie im Transkript formuliert sind. Vermeide neutrale oder unsichere Formulierungen wie \"es könnte\", \"ich vermute, dass\", \"möglicherweise\" oder ähnliche Phrasen. Die Aussagen sollen selbstbewusst und eindeutig sein, um die Inhalte des Podcasts kraftvoll zu vermitteln.\nFüge einige wenige (2-4) Emojis an wo es sich anbietet. \n   \nEnde den Post mit 2-5 passenden Hashtags. Die Hashtags sollten grob sein also sowas wie #wirtschaft #geld #gold oder so ähnlich - je nachdem was passt.\n"},{"content":"=Hier ist das Transkript: \n\n{{ $json.transcript }}"}]}},"credentials":{"openAiApi":{"id":"ftBgqCi1fD1fFEZq","name":"Midgard#1"}},"typeVersion":1.7},{"id":"f59d950b-4e29-4a41-8756-85ea7814b3d3","name":"When clicking ‘Test workflow’","type":"n8n-nodes-base.manualTrigger","position":[-2260,300],"parameters":{},"typeVersion":1},{"id":"27ef7d44-7cca-417f-8177-b5b896a79aa0","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[2660,-20],"parameters":{"color":3,"width":220,"content":"### 🎥Title is kept from the upload, alternatively you can just add the YT Title module in the mix \n# 👇🏻\n"},"typeVersion":1},{"id":"8d763312-35f0-4d53-a210-1e0e22a06323","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[1920,-140],"parameters":{"color":3,"height":200,"content":"# Adjust the Prompts 👉🏻\n\n# 👇🏻"},"typeVersion":1},{"id":"9e7f22d5-7776-4bc4-a274-963cb8c8810c","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[-540,-560],"parameters":{"color":5,"width":620,"height":420,"content":"# Youtube Video Description/Tags/etc. Automation\n\n👉🏻 **Repos**: [github.com/JimPresting](https://github.com/JimPresting) 🛠️  \n👉🏻 **YouTube**: [youtube.com/@StardawnAI](https://www.youtube.com/@StardawnAI) 🎥  \n\nStay up to date for guides on Github repos and tutorials on YouTube! 🚀\n\n\n**Note:** By default, this takes only the latest video and adjusts the values. If you upload multiple videos within a day or even at once within one hour, you need to set that value higher, but bear in mind that if you set it to a high number, it will process older, already published videos. Using the *Publish After* option can't be recommended as it might lead to errors with scheduled videos.\nYou can also detach the *Remove Duplicates* node from the ongoing nodes and set the limit of the *Get All Videos* node to *Return all*. This way, everything that has already been uploaded will not be returned in the future. To undo this, you can select *Clear Database* in the *Remove Duplicates* node.\n"},"typeVersion":1},{"id":"4c5667cf-f8a9-45ab-876b-3a6b5730484c","name":"2.5FlashPrev","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[2220,0],"parameters":{"options":{},"modelName":"models/gemini-2.5-flash-preview-04-17"},"credentials":{"googlePalmApi":{"id":"clmB8ZYJMHaHmnsu","name":"Stardawn#1"}},"typeVersion":1},{"id":"7c73cc89-ea8e-42b3-a1c9-2dc493026216","name":"YT Tags","type":"@n8n/n8n-nodes-langchain.agent","position":[2220,-120],"parameters":{"text":"=Nun folgt das eigentliche Thema/Transkript. Gib mir die Youtube Tags dafür:\n\n{{ $('Adjust Transcript Format').item.json.transcript }}","options":{"systemMessage":"You will get the transcript of a Youtube video for which you should generate matching tags (YOU NEED TO separate it by comma).\n\nBased on the topic/transcript of the video generate YouTube tags. These tags should be very general about the topics. Give multiple matching YouTube Tags that improve SEO for the video. \n\nExample:\nif the video is about why gold is a good investment you will for example not use gold investments as a tag but rather just gold     \n\nThe tags (if appropriate) should be in German as the channel content is in German.\n\nReturn just the tags one word by one separated via Comma. \n\n\nDieses Video handelt vom zukünftigen Goldpreis und davon, wie er die Renditen von performanten Vermögenswerten wie Aktien und Anleihen in ihrer angepassten Rendite beeinflusst.\n\nErwartetet output:\nGoldpreis, zukünftiger Goldpreis, Goldinvestitionen, Vermögensrenditen, Aktien und Anleihen, Investitionsrenditen, angepasste Rendite, Goldmarkt, Finanzmärkte, Goldpreisprognose, Wirtschaftstrends, Investieren in Gold, Aktienmarktanalyse, Anleihenmarkt, Anlagestrategien, Inflation und Gold, Gold vs. Aktien, Finanzanalyse, Edelmetalle, Portfoliomanagement, Marktausblick, Investmenttipps\n "},"promptType":"define"},"typeVersion":1.9},{"id":"e8782ac7-ca31-4a5f-a9f1-62548f56407d","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-2340,-140],"parameters":{"color":4,"width":2000,"height":660,"content":"# 📅Scheduling Logic⏰\n\n"},"typeVersion":1},{"id":"33d289eb-c989-4c8d-b387-405f31ba11d6","name":"3s","type":"n8n-nodes-base.wait","position":[2920,140],"webhookId":"1e75fe1f-e553-4530-a8bc-5e64208a1184","parameters":{"amount":3},"typeVersion":1.1},{"id":"19337563-a349-485d-a064-32f58c8fde90","name":"gettitle","type":"n8n-nodes-base.youTube","position":[-780,200],"parameters":{"options":{},"videoId":"={{ $json.videoId }}","resource":"video","operation":"get"},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1},{"id":"da93ff18-8d19-45ab-b268-dbbebcb86719","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-1040,-140],"parameters":{"color":5,"width":180,"content":"## Code only returns the videos that are not listed"},"typeVersion":1},{"id":"71e9606c-4b6e-4b49-b3ec-bd9bd261f7a9","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-600,-80],"parameters":{"color":3,"width":220,"height":260,"content":"## Video needs to be set to private TOGETHER with the PublishAt parameter in order for it to work."},"typeVersion":1},{"id":"c5a240aa-b705-4219-b293-da4ae0168350","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[1320,-140],"parameters":{"color":3,"width":280,"height":240,"content":"### Video needs to be Unlisted or Published in order for the scraper to be able to get the transcript\n\n### ADJUST YOUR APIFY API TOKEN HERE      \n# 👇🏻"},"typeVersion":1},{"id":"5fb12ed7-8992-424d-86e0-7c8cd0f0b9d3","name":"Sticky Note7","type":"n8n-nodes-base.stickyNote","position":[-80,-140],"parameters":{"color":4,"width":3200,"height":660,"content":"# Generate Description, Tags, etc. 🖌️📝 #️⃣"},"typeVersion":1},{"id":"33b865ef-ec2e-4349-bbba-d76d41345fe3","name":"Set Publish Date","type":"n8n-nodes-base.youTube","position":[-600,280],"parameters":{"title":"={{ $json.snippet.title }}","videoId":"={{ $json.id }}","resource":"video","operation":"update","categoryId":"25","regionCode":"DE","updateFields":{"publishAt":"={{ $('Loop over All Videos not Published').item.json.publishAt }}","privacyStatus":"private"}},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1},{"id":"9a228886-c91a-44f7-b894-e23095166efc","name":"Every Day","type":"n8n-nodes-base.scheduleTrigger","disabled":true,"position":[-2260,60],"parameters":{"rule":{"interval":[{"triggerAtHour":14,"triggerAtMinute":22}]}},"typeVersion":1.2},{"id":"c0277051-f146-43f7-a2cd-36739d933209","name":"Get Videos to reschedule","type":"n8n-nodes-base.youTube","position":[-1880,40],"parameters":{"limit":2,"filters":{},"options":{"order":"date"},"resource":"video"},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1},{"id":"5cf3813b-931b-4c0a-84fe-4edd3d55a99a","name":"Get video Ids seperated","type":"n8n-nodes-base.code","position":[-1660,40],"parameters":{"jsCode":"// Extract video IDs from YouTube search results\n// This function processes all input items and creates separate items for each videoId\n\n// Initialize empty array for our result items\nconst resultItems = [];\n\n// Process each input item\nfor (const item of items) {\n  // Check if the item has a valid structure\n  if (item.json && item.json.id && item.json.id.videoId) {\n    // Create a new item for each videoId\n    resultItems.push({\n      json: {\n        videoId: item.json.id.videoId\n      }\n    });\n  }\n}\n\n// Return each videoId as a separate item that can be processed individually\nreturn resultItems;"},"typeVersion":2},{"id":"8131b388-b842-4c46-b82b-e53283d938ed","name":"Loop over Video IDs","type":"n8n-nodes-base.splitInBatches","position":[-1440,40],"parameters":{"options":{}},"typeVersion":3},{"id":"665cb406-f6b9-4ba4-82fa-daa1141eb0a3","name":"Get Video Data","type":"n8n-nodes-base.youTube","position":[-1220,60],"parameters":{"options":{},"videoId":"={{ $json.videoId }}","resource":"video","operation":"get"},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1},{"id":"3d366fb3-f579-46f0-9254-8d4c1612038e","name":"Return Private Videos","type":"n8n-nodes-base.code","position":[-1220,-120],"parameters":{"jsCode":"// Utility function to get next Friday at 17:00 UTC in YouTube ISO 8601 format (YYYY-MM-DDTHH:mm:ssZ)\nfunction getNextFridayUTC(startDate, weekOffset = 0) {\n  const date = new Date(startDate); // Work with a copy\n  \n  const currentUTCDay = date.getUTCDay(); // 0 for Sunday, ..., 5 for Friday\n  const daysUntilFriday = (5 - currentUTCDay + 7) % 7; // Calculate days to next Friday\n  \n  date.setUTCDate(date.getUTCDate() + daysUntilFriday + (weekOffset * 7));\n  date.setUTCHours(17, 0, 0, 0); // Set time to 17:00:00.000 UTC\n  \n  // toISOString() returns \"YYYY-MM-DDTHH:mm:ss.sssZ\"\n  // We split at '.' to remove milliseconds and add 'Z' back for \"YYYY-MM-DDTHH:mm:ssZ\"\n  return date.toISOString().split('.')[0] + \"Z\";\n}\n\n// INPUT `items` is an array from n8n.\n// Each item.json is expected to be a YouTube video object from a previous node.\nconst videosToSchedule = items.filter(item => \n  item.json && \n  item.json.status && \n  (item.json.status.privacyStatus === \"unlisted\" || item.json.status.privacyStatus === \"private\")\n  // Adjust this filter if you only want to process \"unlisted\" or only \"private\" videos\n);\n\nif (videosToSchedule.length === 0) {\n  // console.log(\"No videos found matching the filter criteria.\");\n  return []; // Return empty array if no videos to schedule\n}\n\n// Sort videos by their original published/uploaded date (snippet.publishedAt), earliest first.\nvideosToSchedule.sort((a, b) => {\n  const dateA = new Date(a.json?.snippet?.publishedAt || '1970-01-01T00:00:00Z');\n  const dateB = new Date(b.json?.snippet?.publishedAt || '1970-01-01T00:00:00Z');\n  return dateA - dateB;\n});\n\nconst now = new Date(); // Current date to calculate future Fridays\n\n// Map the filtered and sorted videos to the desired output structure for the YouTube update node.\nconst scheduledItems = videosToSchedule.map((item, index) => {\n  const videoData = item.json; // The actual video data object\n  const scheduleDate = getNextFridayUTC(now, index); // Calculate the publishAt date\n  \n  return {\n    json: { // This is the structure the next n8n YouTube node will receive\n      videoId: videoData.id,                             // ID of the video to update\n      publishAt: scheduleDate,                           // The calculated schedule time: YYYY-MM-DDTHH:mm:ssZ\n      title: videoData.snippet?.title || \"Untitled Video\", // Keep original title or use a default\n      \n      // --- CRITICAL PARAMETERS FOR THE YOUTUBE API ---\n      privacy: \"private\", // **MUST BE 'private' FOR 'publishAt' TO WORK!**\n                          // The API requires the video to be set to private when scheduling.\n      \n      // **VERY LIKELY REQUIRED: selfDeclaredMadeForKids**\n      // You MUST tell YouTube if the video is made for kids or not.\n      // Get it from existing data if available, otherwise set a default.\n      selfDeclaredMadeForKids: videoData.status?.selfDeclaredMadeForKids === true ? true : false,\n\n      // **POSSIBLY REQUIRED: categoryId (if updating snippet like title)**\n      // categoryId: videoData.snippet?.categoryId || \"YOUR_DEFAULT_CATEGORY_ID\", \n      // e.g., \"10\" for Music, \"22\" for People & Blogs.\n      // Check YouTube API docs for category IDs.\n      \n      // (Optional) You can include other fields like description if you want to update them\n      // description: videoData.snippet?.description || \"\" \n    }\n  };\n});\n\nreturn scheduledItems; // Return the array of video objects to be processed"},"typeVersion":2,"alwaysOutputData":true},{"id":"bb3e90ae-ff3f-4c22-b920-d1a99a1f99e8","name":"4s","type":"n8n-nodes-base.wait","position":[-260,240],"webhookId":"7d5c70f8-a592-4634-8c5a-0fbd0cebf6a4","parameters":{"amount":4},"typeVersion":1.1},{"id":"f67c7668-71eb-42e6-b385-f66f9e5e80eb","name":"Loop over All Videos not Published","type":"n8n-nodes-base.splitInBatches","position":[-1020,60],"parameters":{"options":{}},"typeVersion":3},{"id":"f98b1399-7970-4585-8f2a-be897562fa40","name":"get video id","type":"n8n-nodes-base.set","position":[380,80],"parameters":{"options":{},"assignments":{"assignments":[{"id":"c2e2eecd-ca73-40c9-a364-4713030ab451","name":"id.videoId","type":"string","value":"={{ $json.id.videoId }}"}]},"includeOtherFields":true},"typeVersion":3.4},{"id":"6b907512-945b-4c1e-8a97-b14409ddfcaa","name":"Remove Duplicates from previous Runs","type":"n8n-nodes-base.removeDuplicates","position":[600,80],"parameters":{"options":{},"operation":"removeItemsSeenInPreviousExecutions","dedupeValue":"={{ $json.id.videoId }}"},"typeVersion":2,"alwaysOutputData":false},{"id":"d6c7152e-e508-43c3-8748-ba12652ac117","name":"new video?","type":"n8n-nodes-base.if","position":[820,80],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"adfea7c7-ed64-4e1e-a9c3-dc5e33aa1147","operator":{"type":"array","operation":"notExists","singleValue":true},"leftValue":"={{$('Remove Duplicates from previous Runs').all() }}","rightValue":""}]}},"typeVersion":2.2},{"id":"d1c31718-4a26-4108-a618-f67cfb87053c","name":"getLatestVideoID","type":"n8n-nodes-base.youTube","position":[1000,160],"parameters":{"options":{},"videoId":"={{ $('get video id').item.json.id.videoId }}","resource":"video","operation":"get"},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1,"alwaysOutputData":true},{"id":"66814975-e4a5-4c23-9bf2-c8d30d96c122","name":"Get Transcript","type":"n8n-nodes-base.httpRequest","position":[1320,120],"parameters":{"url":"=https://api.apify.com/v2/acts/pintostudio~youtube-transcript-scraper/run-sync-get-dataset-items","method":"POST","options":{},"jsonBody":"={\n  \"videoUrl\": \"https://www.youtube.com/watch?v={{ $json.id }}\"\n}","sendBody":true,"sendQuery":true,"specifyBody":"json","queryParameters":{"parameters":[{"name":"token","value":"YOURAPITOKEN"}]}},"typeVersion":4.2,"alwaysOutputData":false},{"id":"fd355571-8c74-4d31-972e-13f737aaec05","name":"Adjust Transcript Format","type":"n8n-nodes-base.code","position":[1600,120],"parameters":{"jsCode":"const items = $input.all();\n\nconst transcriptStrings = items.flatMap(item => {\n  const dataArray = item.json.data;\n\n  if (!dataArray || !Array.isArray(dataArray)) {\n    return [];\n  }\n\n  const segmentTexts = dataArray.map(segment => {\n      if (segment && typeof segment.text === 'string') {\n          return segment.text;\n      } else {\n          return '';\n      }\n  });\n\n  return segmentTexts;\n});\n\nconst transcript = transcriptStrings.join(' ');\n\nreturn [\n  {\n    json: {\n      transcript: transcript,\n    },\n  },\n];"},"typeVersion":2},{"id":"7b69339f-aa12-430e-ba21-b85a0db596b5","name":"Update Video's Metadata","type":"n8n-nodes-base.youTube","position":[2660,140],"parameters":{"title":"={{ $('Fetch Latest Videos').first().json.snippet.title }}","videoId":"={{ $('getLatestVideoID').first().json.id }}","resource":"video","operation":"update","categoryId":"25","regionCode":"DE","updateFields":{"tags":"={{ $('YT Tags').first().json.message.content }}","description":"={{ $('Create Description').first().json.message.content }}\n\nDiese textbasierte Zusammenfassung des Videos wurde automatisch mit dem KI-Modell gpt-4.1-nano erstellt.]\n"}},"credentials":{"youTubeOAuth2Api":{"id":"cpgVAMXp8iMLXwKW","name":"Private Pres"}},"typeVersion":1}],"active":true,"pinData":{},"settings":{"callerPolicy":"workflowsFromSameOwner","errorWorkflow":"RhPW7iLQQJGJZqQQ","executionOrder":"v1"},"versionId":"268a8dc5-0408-458c-9dff-d7c91b223b76","connections":{"3s":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"4s":{"main":[[{"node":"Fetch Latest Videos","type":"main","index":0}]]},"YT Tags":{"main":[[{"node":"Update Video's Metadata","type":"main","index":0}]]},"YT Title":{"main":[[]]},"gettitle":{"main":[[{"node":"Set Publish Date","type":"main","index":0}]]},"Every Day":{"main":[[{"node":"Get Videos to reschedule","type":"main","index":0}]]},"new video?":{"main":[[],[{"node":"getLatestVideoID","type":"main","index":0}]]},"2.5FlashPrev":{"ai_languageModel":[[{"node":"YT Tags","type":"ai_languageModel","index":0}]]},"get video id":{"main":[[{"node":"Remove Duplicates from previous Runs","type":"main","index":0}]]},"Get Transcript":{"main":[[{"node":"Adjust Transcript Format","type":"main","index":0}]]},"Get Video Data":{"main":[[{"node":"Loop over Video IDs","type":"main","index":0}]]},"Loop Over Items":{"main":[[],[{"node":"get video id","type":"main","index":0}]]},"Set Publish Date":{"main":[[{"node":"Loop over All Videos not Published","type":"main","index":0}]]},"getLatestVideoID":{"main":[[{"node":"Get Transcript","type":"main","index":0}]]},"Create Description":{"main":[[{"node":"YT Tags","type":"main","index":0},{"node":"YT Title","type":"main","index":0}]]},"Fetch Latest Videos":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"Loop over Video IDs":{"main":[[{"node":"Return Private Videos","type":"main","index":0}],[{"node":"Get Video Data","type":"main","index":0}]]},"Return Private Videos":{"main":[[{"node":"Loop over All Videos not Published","type":"main","index":0}]]},"Get video Ids seperated":{"main":[[{"node":"Loop over Video IDs","type":"main","index":0}]]},"Update Video's Metadata":{"main":[[{"node":"3s","type":"main","index":0}]]},"Adjust Transcript Format":{"main":[[{"node":"Create Description","type":"main","index":0}]]},"Get Videos to reschedule":{"main":[[{"node":"Get video Ids seperated","type":"main","index":0}]]},"When clicking ‘Test workflow’":{"main":[[{"node":"Get Videos to reschedule","type":"main","index":0}]]},"Loop over All Videos not Published":{"main":[[{"node":"4s","type":"main","index":0}],[{"node":"gettitle","type":"main","index":0}]]},"Remove Duplicates from previous Runs":{"main":[[{"node":"new video?","type":"main","index":0}]]}}}


name: Amazon Product Search Scraper with BrightData, GPT-4, and Google Sheets
author_name: phil
author_username: phil
categories: []
created_at: 2025-05-06T14:51:48.388Z
updated_at: 2025-05-06T14:51:48.388Z
url: https://n8n.io/workflows/3901
scraped_at: 2025-07-06 21:16:30
description:
  This workflow automates web scraping of Amazon search result pages by retrieving raw HTML, cleaning it to retain only the relevant product elements, and then using an LLM to extract structured product data (name, description, rating, reviews, and price), before saving the results back to Google Sheets.

  It integrates Google Sheets to supply and collect URLs, BrightData to fetch page HTML, a custom n8n Function node to sanitize the HTML, LangChain (OpenRouter GPT-4) to parse product details, and Google Sheets again to store the output.


  URL to scape

  .


  Result

  Who Needs Amazon Search Result Scraping?

  This scraping workflow is ideal for teams and businesses that need to monitor Amazon product listings at scale:

  E-commerce Analysts** – Track competitor pricing, ratings, and inventory trends.  
  Market Researchers** – Collect data on product popularity and reviews for market analysis.  
  Data Teams** – Automate ingestion of product metadata into BI pipelines or data lakes.  
  Affiliate Marketers** – Keep affiliate catalogs up to date with latest product details and prices.

  If you need reliable, structured data from Amazon search results delivered directly into your spreadsheets, this workflow saves you hours of manual copy-and-paste.

  Why Use This Workflow?

  End-to-End Automation** – From URL list to clean JSON output in Sheets.  
  Robust HTML Cleaning** – Strips scripts, styles, unwanted tags, and noise.  
  Accurate Structured Parsing** – Leverages GPT-4 via LangChain for reliable extraction.  
  Scalable & Repeatable** – Processes thousands of URLs in batches.

  Step-by-Step: How This Workflow Scrapes Amazon

  Get URLs from Google Sheets – Reads a list of search result URLs.  
  Loop Over Items – Iterates through each URL in controlled batches.  
  Fetch Raw HTML – Uses BrightData’s Web Unlocker proxy to retrieve the page.  
  Clean HTML – A Function node removes doctype, scripts, styles, head, comments, classes, and non-whitelisted tags, collapsing extra whitespace.  
  Extract with LLM – Passes cleaned HTML into LangChain → GPT-4 to output JSON for each product:  
     name, description, rating, reviews, price  
  Save Results – Appends the JSON fields as columns back into a “results” sheet in Google Sheets.

  Customization: Tailor to Your Needs

  Adaptable Sites** – This workflow can be adapted to any e-commerce or other website, for example Walmart or eBay. 
  Whitelist Tags** – Modify the allowedTags array in the Code node to keep additional HTML elements.  
  Schema Changes** – Update the Structured Output Parser schema to include more fields (e.g., availability, SKU).  
  Alternate Data Sink** – Instead of Sheets, route output to a database, CSV file, or webhook.

  🔑 Prerequisites

  Google Sheets Credentials** – OAuth credentials configured in n8n.  
  BrightData API token** – Stored in n8n credentials as BRIGHTDATA_TOKEN.  
  OpenRouter API Key** – Configured for the LangChain node to call GPT-4.  
  n8n Instance** – Self-hosted or cloud with sufficient quota for HTTP requests and LLM calls.

  🚀 Installation & Setup

  Configure Credentials**  
    In n8n, set up Google Sheets OAuth under “Credentials.”  
    Add BrightData token as a new HTTP Request credential.  
    Create an OpenRouter API key credential for the LangChain node.  
  Import the Workflow**  
    Copy the JSON workflow into n8n’s “Import” dialog.  
    Map your Google Sheet IDs and GIDs to the {{WEB_SHEET_ID}}, {{TRACK_SHEET_GID}}, and {{RESULTS_SHEET_GID}} placeholders.  
    Ensure the BRIGHTDATA_TOKEN credential is selected on the HTTP Request node.  
  Test & Run**  
    Add a few Amazon search URLs to your “track” sheet.  
    Execute the workflow and verify product data appears in your “results” sheet.  
    Tweak batch size or parser schema as needed.

  ⚠ Important

  API Rate Limits** – Monitor your BrightData and OpenRouter usage to avoid throttling.  
  Amazon’s Terms** – Ensure your scraping complies with Amazon’s policies and legal requirements.

  Summary

  This workflow delivers a fully automated, scalable solution to extract structured product data from Amazon search pages directly into Google Sheets—streamlining your competitive analysis and data collection. 🚀

  Phil | Inforeole 

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.outputParserStructured","@n8n/n8n-nodes-langchain.lmChatOpenRouter"]

workflow_json:
  {"meta":{"instanceId":"4a11afdb3c52fd098e3eae9fad4b39fdf1bbcde142f596adda46c795e366b326"},"nodes":[{"id":"f1b36f4b-6558-4e83-a999-e6f2d24e196c","name":"OpenRouter Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenRouter","position":[620,240],"parameters":{"model":"openai/gpt-4.1","options":{}},"typeVersion":1},{"id":"89ca0a07-286f-4e68-9e85-0327a4859cc0","name":"Structured Output Parser","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[900,240],"parameters":{"schemaType":"manual","inputSchema":"{\n  \"type\": \"array\",\n  \"items\": {\n    \"type\": \"object\",\n    \"properties\": {\n      \"name\": { \"type\": \"string\" },\n      \"description\": { \"type\": \"string\" },\n      \"rating\": { \"type\": \"number\" },\n      \"reviews\": { \"type\": \"integer\" },\n      \"price\": { \"type\": \"string\" }\n    },\n    \"required\": [\"name\", \"description\", \"rating\", \"reviews\", \"price\"]\n  }\n}"},"typeVersion":1.2},{"id":"e4800c1d-c0d8-4093-81ec-fc19ad0034cd","name":"scrap url","type":"n8n-nodes-base.httpRequest","position":[240,60],"parameters":{"url":"https://api.brightdata.com/request","method":"POST","options":{},"sendBody":true,"sendHeaders":true,"bodyParameters":{"parameters":[{"name":"zone","value":"web_unlocker1"},{"name":"url","value":"={{ $json.url }}"},{"name":"format","value":"raw"}]},"headerParameters":{"parameters":[{"name":"Authorization","value":"{{BRIGHTDATA_TOKEN}}"}]}},"typeVersion":4.2},{"id":"1a1f768f-615d-4035-81b0-63b860f8e6ac","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[160,-140],"parameters":{"content":"## Web Scraper API\n\n[Inscription - Free Trial](https://get.brightdata.com/website-scraper)"},"typeVersion":1},{"id":"2f260d96-4fff-4a4f-af29-1e43f465d54c","name":"When clicking ‘Test workflow’","type":"n8n-nodes-base.manualTrigger","position":[-440,200],"parameters":{},"typeVersion":1},{"id":"4be9033f-0b9f-466d-916e-88fbb2a80417","name":"url","type":"n8n-nodes-base.splitInBatches","position":[20,200],"parameters":{"options":{}},"typeVersion":3},{"id":"21b6d21c-b977-4175-9068-e0e2e19fa472","name":"get urls to scrape","type":"n8n-nodes-base.googleSheets","position":[-200,200],"parameters":{"options":{},"sheetName":"{{TRACK_SHEET_GID}}","documentId":"{{WEB_SHEET_ID}}"},"credentials":{"googleSheetsOAuth2Api":{"id":"KsXWRZTrfCUFrrHD","name":"Google Sheets"}},"typeVersion":4.5},{"id":"25ef76ec-cf0d-422e-b060-68c49192a008","name":"clean html","type":"n8n-nodes-base.code","position":[460,60],"parameters":{"jsCode":"// CleanHtmlFunction.js\n// Purpose: n8n Function node to clean HTML: remove doctype, scripts, styles, head, comments, classes, extra blank lines, and non-whitelisted tags\n\nreturn items.map(item => {\n  const rawHtml = item.json.data;\n\n  // 1) remove doctype, scripts, styles, comments and head section, and strip class attributes\n  let cleaned = rawHtml\n    .replace(/<!doctype html>/gi, '')\n    .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/g, '')\n    .replace(/<head[\\s\\S]*?<\\/head>/gi, '')\n    .replace(/\\sclass=\"[^\"]*\"/gi, '');\n\n  // 2) define whitelist of tags to keep\n  const allowedTags = [\n    'h1','h2','h3','h4','h5','h6',\n    'p','ul','ol','li',\n    'strong','em','a','blockquote',\n    'code','pre'\n  ];\n\n  // 3) strip out all tags not in the whitelist, reconstruct allowed tags cleanly\n  cleaned = cleaned.replace(\n    /<\\/?([a-z][a-z0-9]*)\\b[^>]*>/gi,\n    (match, tagName) => {\n      const name = tagName.toLowerCase();\n      if (allowedTags.includes(name)) {\n        return match.startsWith('</') ? `</${name}>` : `<${name}>`;\n      }\n      return '';\n    }\n  );\n\n  // 4) collapse multiple blank or whitespace-only lines into a single newline\n  cleaned = cleaned.replace(/(\\s*\\r?\\n\\s*){2,}/g, '\\n');\n\n  // 5) trim leading/trailing whitespace\n  cleaned = cleaned.trim();\n\n  return {\n    json: { cleanedHtml: cleaned }\n  };\n});"},"typeVersion":2},{"id":"f72660d5-8427-4655-acbe-10365273c27b","name":"extract data","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[680,60],"parameters":{"text":"={{ $json.cleanedHtml }}","messages":{"messageValues":[{"message":"=You are an expert in web page scraping. Provide a structured response in JSON format. Only the response, without commentary.\n\nExtract the product information for {{ $(‘url’).item.json.url.split(’/s?k=’)[1].split(’&’)[0] }} present on the page.\n\nname\ndescription\nrating\nreviews\nprice"}]},"promptType":"define","hasOutputParser":true},"typeVersion":1.6},{"id":"8b4af1bb-d7f8-456e-b630-ecd9b6e4bcdc","name":"add results","type":"n8n-nodes-base.googleSheets","position":[1280,200],"parameters":{"columns":{"value":{"name":"={{ $json.output.name }}","price":"={{ $json.output.price }}","rating":"={{ $json.output.rating }}","reviews":"={{ $json.output.reviews }}","description":"={{ $json.output.description }}"},"schema":[{"id":"name","type":"string"},{"id":"description","type":"string"},{"id":"rating","type":"string"},{"id":"reviews","type":"string"},{"id":"price","type":"string"}],"mappingMode":"defineBelow"},"options":{},"operation":"append","sheetName":"{{RESULTS_SHEET_GID}}","documentId":"{{WEB_SHEET_ID}}"},"credentials":{"googleSheetsOAuth2Api":{"id":"KsXWRZTrfCUFrrHD","name":"Google Sheets"}},"typeVersion":4.5},{"id":"7a5ba438-2ede-4d6c-b8fa-9a958ba1ef3e","name":"Split items","type":"n8n-nodes-base.splitOut","position":[1060,60],"parameters":{"include":"allOtherFields","options":{},"fieldToSplitOut":"output"},"typeVersion":1}],"pinData":{},"connections":{"url":{"main":[[],[{"node":"scrap url","type":"main","index":0}]]},"scrap url":{"main":[[{"node":"clean html","type":"main","index":0}]]},"clean html":{"main":[[{"node":"extract data","type":"main","index":0}]]},"Split items":{"main":[[{"node":"add results","type":"main","index":0}]]},"add results":{"main":[[{"node":"url","type":"main","index":0}]]},"extract data":{"main":[[{"node":"Split items","type":"main","index":0}]]},"get urls to scrape":{"main":[[{"node":"url","type":"main","index":0}]]},"OpenRouter Chat Model":{"ai_languageModel":[[{"node":"extract data","type":"ai_languageModel","index":0}]]},"Structured Output Parser":{"ai_outputParser":[[{"node":"extract data","type":"ai_outputParser","index":0}]]},"When clicking ‘Test workflow’":{"main":[[{"node":"get urls to scrape","type":"main","index":0}]]}}}


name: Search LinkedIn companies, Score with AI and add them to Google Sheet CRM
author_name: Matthieu
author_username: yaznow
nodes_used: ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","@n8n/n8n-nodes-langchain.openAi"]
categories: []
created_at: 2025-05-06T16:14:52.314Z
updated_at: 2025-05-06T16:14:52.314Z
url: https://n8n.io/workflows/3904
scraped_at: 2025-07-06 21:16:30
description:
  Search LinkedIn companies, Score with AI and add them to Google Sheet CRM

  Setup Video: https://youtube.com/watch?v=m904RNxtF0w&t

  Who is this for?
  This template is ideal for sales teams, business development professionals, and marketers looking to build a targeted prospect database with automatic qualification. Perfect for agencies, consultants, and B2B companies wanting to identify and prioritize the most promising potential clients.

  What problem does this workflow solve?
  Manually researching companies on LinkedIn, evaluating their fit for your services, and tracking them in your CRM is time-consuming and subjective. This automation streamlines lead generation by automatically finding, scoring, and importing qualified prospects into your database.

  What this workflow does
  This workflow automatically searches for companies on LinkedIn based on your criteria, retrieves detailed information about each company, filters them based on quality indicators, uses AI to score how well they match your ideal customer profile, and adds them to your Google Sheet CRM while preventing duplicates.

  Setup
  Create a Ghost Genius API account and get your API key
  Configure HTTP Request nodes with Header Auth credentials
  Create a copy of the provided Google Sheet template
  Set up your Google Sheet and OpenAI credentials following n8n documentation
  Customize the "Set Variables" node to match your target audience and scoring criteria

  How to customize this workflow
  Modify search parameters to target different industries, locations, or company sizes
  Adjust the follower count threshold based on your qualification criteria
  Customize the AI scoring system to align with your specific product or service offering
  Add notification nodes to alert you when high-scoring companies are identified

workflow_json:
  {"id":"r82OA8ExycLYuK1u","meta":{"instanceId":"95a1299fb2b16eb2219cb044f54e72c2d00dcd2c72efe717b3c308d200f29927"},"name":"My workflow 8","tags":[],"nodes":[{"id":"ed517555-fff0-4cbb-9ea0-59794b95e6f4","name":"Process Each Company","type":"n8n-nodes-base.splitInBatches","onError":"continueRegularOutput","position":[520,780],"parameters":{"options":{}},"typeVersion":3,"alwaysOutputData":false},{"id":"5b1c73ea-812f-4623-ad6e-64b52dc33c58","name":"Get Company Info","type":"n8n-nodes-base.httpRequest","onError":"continueRegularOutput","position":[760,780],"parameters":{"url":"https://api.ghostgenius.fr/v2/company","options":{"batching":{"batch":{"batchSize":1,"batchInterval":2000}}},"sendQuery":true,"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","queryParameters":{"parameters":[{"name":"url","value":"={{ $json.url }}"}]}},"credentials":{"httpHeaderAuth":{"id":"XdFg4wGkcxwRPUMo","name":"Header Auth account 4"}},"retryOnFail":true,"typeVersion":4.2},{"id":"06a1acfb-7aa3-43fa-abd7-0fdf1a5f0cf9","name":"Filter Valid Companies","type":"n8n-nodes-base.if","onError":"continueRegularOutput","position":[980,780],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"5ea943a6-8f6c-4cb0-b194-8c92d4b2aacc","operator":{"type":"string","operation":"notEmpty","singleValue":true},"leftValue":"={{ $json.website }}","rightValue":"[null]"},{"id":"8235b9bb-3cd4-4ed4-a5dc-921127ff47c7","operator":{"type":"number","operation":"gt"},"leftValue":"={{ $json.followers_count }}","rightValue":200}]}},"typeVersion":2.2},{"id":"4803085d-ba62-4e38-bf49-af867a909939","name":"Is New Company?","type":"n8n-nodes-base.if","position":[1380,780],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"050c33be-c648-44d7-901c-51f6ff024e97","operator":{"type":"object","operation":"empty","singleValue":true},"leftValue":"={{ $('Check If Company Exists').all().first().json }}","rightValue":""}]}},"typeVersion":2.2},{"id":"744592d9-bcbe-448d-893d-d8fe4d55bb19","name":"Set Variables","type":"n8n-nodes-base.set","position":[-220,780],"parameters":{"options":{},"assignments":{"assignments":[{"id":"e81e4891-4786-4dd9-a338-d1095e27f382","name":"Your target","type":"string","value":"Growth Marketing Agency"},{"id":"ed2b6b08-66aa-4d4b-b68c-698b5e841930","name":"B: 1-10 employees, C: 11-50 employees, D: 51-200 employees, E: 201-500 employees, F: 501-1000 employees, G: 1001-5000 employees, H: 5001-10,000 employees, I: 10,001+ employees","type":"string","value":"C"},{"id":"f1d02f1a-8115-4e0c-a5ec-59bf5b54263b","name":"Location (find it on : https://www.ghostgenius.fr/tools/search-sales-navigator-locations-id)","type":"string","value":"103644278"},{"id":"21bdb871-9327-4553-bb4a-a138be9f735c","name":"Your product or service","type":"string","value":"our CRM implementation services"},{"id":"31f5adfc-8a8f-498c-9e57-24584c42f7de","name":"Positive indicators (3-5 specific factors that indicate a company might need your product)","type":"string","value":"- Mentions challenges with customer relationships or sales processes \n- Company is in growth phase with expanding client base \n- Mentions need for better data organization or customer insights \n- References marketing automation, sales pipelines, or customer retention "},{"id":"630807cb-9d06-41ee-8534-a652ed76cb4c","name":"Negative indicators (3-5 specific factors that indicate a company might NOT need your product)","type":"string","value":"- Very small companies (1-5 employees) or extremely large enterprises \n- Already mentions using advanced CRM solutions \n- No indication of sales process or customer relationship management in description \n- Pure manufacturing or product-based business with minimal customer interaction \n- Non-profit or government entity with unique relationship management needs"}]}},"typeVersion":3.4},{"id":"289016bf-2d5f-4bd0-b69d-cc2e2e437015","name":"Search Companies","type":"n8n-nodes-base.httpRequest","position":[-20,780],"parameters":{"url":"https://api.ghostgenius.fr/v2/search/companies","options":{"pagination":{"pagination":{"parameters":{"parameters":[{"name":"page","value":"={{ $pageCount + 1 }}"}]},"maxRequests":3,"requestInterval":2000,"limitPagesFetched":true,"completeExpression":"={{ $response.body.data.isEmpty() }}","paginationCompleteWhen":"other"}}},"sendQuery":true,"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","queryParameters":{"parameters":[{"name":"keywords","value":"={{ $json['Your target'] }}"},{"name":"locations","value":"={{ $json['Location (find it on : https://www'].ghostgenius['fr/tools/search-sales-navigator-locations-id)'] }}"},{"name":"company_size","value":"={{ $json['B: 1-10 employees, C: 11-50 employees, D: 51-200 employees, E: 201-500 employees, F: 501-1000 employees, G: 1001-5000 employees, H: 5001-10,000 employees, I: 10,001+ employees'] }}"}]}},"credentials":{"httpHeaderAuth":{"id":"XdFg4wGkcxwRPUMo","name":"Header Auth account 4"}},"typeVersion":4.2},{"id":"0ce071a3-bbd2-4f4d-8a09-ed66a63e976f","name":"Extract Company Data","type":"n8n-nodes-base.splitOut","onError":"continueRegularOutput","position":[180,780],"parameters":{"options":{},"fieldToSplitOut":"data"},"typeVersion":1},{"id":"3f4b829a-915f-4cce-84e5-c8f5fd92b24d","name":"Check If Company Exists","type":"n8n-nodes-base.googleSheets","position":[1200,780],"parameters":{"options":{},"filtersUI":{"values":[{"lookupValue":"={{ $json.id }}","lookupColumn":"ID"}]},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM/edit#gid=0","cachedResultName":"Companies"},"documentId":{"__rl":true,"mode":"list","value":"1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM/edit?usp=drivesdk","cachedResultName":"CRM"}},"credentials":{"googleSheetsOAuth2Api":{"id":"Y8D8KsfgZCZmP2Vh","name":"Google Sheets account"}},"typeVersion":4.5,"alwaysOutputData":true},{"id":"7e40feaa-c4f9-4d9d-bfbe-1586ca64374b","name":"AI Company Scoring","type":"@n8n/n8n-nodes-langchain.openAi","position":[1700,800],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1","cachedResultName":"GPT-4.1"},"options":{"temperature":0.2},"messages":{"values":[{"role":"system","content":"=You are an AI assistant that evaluates companies to determine if they might be interested in {{ $('Set Variables').item.json['Your product or service'] }}.\n\nEvaluate the company information provided on a scale of 0 to 10, where:\n- 0 = Not at all likely to be interested\n- 10 = Extremely likely to be interested\n\nBase your evaluation on these criteria:\n1. Industry fit: How well does the company's industry align with {{ $('Set Variables').item.json['Your product or service'] }}?\n2. Company profile: Is the company size, growth stage, and location appropriate for {{ $('Set Variables').item.json['Your product or service'] }}?\n3. Pain points: Based on their description, do they likely have challenges that {{ $('Set Variables').item.json['Your product or service'] }} solves?\n\nFactors that indicate a good fit:\n{{ $('Set Variables').item.json['Positive indicators (3-5 specific factors that indicate a company might need your product)'] }}\n\nFactors that indicate a poor fit:\n{{ $('Set Variables').item.json['Negative indicators (3-5 specific factors that indicate a company might NOT need your product)'] }}\n\nRespond ONLY with this JSON format:\n```json\n{\n  \"score\": [number between 0 and 10],\n}"},{"content":"=Here is the company to analyze:\nName: {{ $('Filter Valid Companies').item.json.name }}\n{{ $('Filter Valid Companies').item.json.tagline }}\n{{ $('Filter Valid Companies').item.json.description }}\nNumber of employees: {{ $('Filter Valid Companies').item.json.staff_count }}\nIndustry: {{ $('Filter Valid Companies').item.json.industries }}\nSpecialties: {{ $('Filter Valid Companies').item.json.specialities }}\nLocation: {{ $('Filter Valid Companies').item.json.locations?.toJsonString() }}\nFounded in: {{ $('Filter Valid Companies').item.json.founded_on }}\nFunding: {{ $('Filter Valid Companies').item.json.funding?.toJsonString() }}\n"}]},"jsonOutput":true},"credentials":{"openAiApi":{"id":"SSQ6BcbSKW6I0uSn","name":"OpenAi account"}},"typeVersion":1.8},{"id":"dfe8263f-a4ed-411f-a962-7dcca478a4be","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-500,440],"parameters":{"color":6,"width":860,"height":560,"content":"## LinkedIn Company Search\nThis section initiates the workflow and searches for your target companies on LinkedIn using the Ghost Genius API.\n\nYou can filter and refine your search using keywords, company size, location, industry, or even whether the company has active job postings. Use the \"Set Variables\" node for it (this node also allows you to customize the AI Lead Scoring prompt).\n\nNote that you can retrieve a maximum of 1000 companies per search (corresponding to 100 LinkedIn pages), so it's important not to exceed this number of results to avoid losing prospects.\n\nExample: Let's say I want to target Growth Marketing Agencies with 11-50 employees. I do my search and see that there are 10,000 results. So I refine my search by using location to go country by country and retrieve all 10,000 results in several batches ranging from 500 to 1000 depending on the country.\n\nTips: To test the workflow or to see the number of results of your search, change the pagination parameter (Max Pages) in the \"Search Companies\" node. It will be displayed at the very top of the response JSON."},"typeVersion":1},{"id":"ec72fb51-1113-435d-b4fb-575d478a7bed","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[440,440],"parameters":{"color":4,"width":1120,"height":560,"content":"## Company Data Processing \nThis section processes each company individually.\n\nWe retrieve all the company information using Get Company Details by using the LinkedIn link obtained from the previous section.\n\nThen we filter the company based on the number of followers, which gives us a first indication of the company's credibility (200 in this case), and whether their LinkedIn page has a website listed.\n\nThe workflow implements batch processing with a 2-second delay between requests to respect API rate limits. This methodical approach ensures reliable data collection while preventing API timeouts.\n\nYou can adjust these thresholds based on your target market - increasing the follower count for more established businesses or decreasing it for emerging markets.\n\nThe last two modules checks if the company already exists in your database (using LinkedIn ID) to prevent duplicates because when you do close enough searches, some companies may come up several times."},"typeVersion":1},{"id":"5fe1edea-58fa-44d0-8ae7-4dc2720f1e34","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[1640,440],"parameters":{"color":5,"width":780,"height":560,"content":"## AI Scoring and Storage\nThis section scores the company and stores it in a Google Sheet.\n\nIt's important to properly fill in the \"Set variables\" node at the beginning of the workflow to get a result relevant to your use case. You can also manually modify the system prompt.\n\nRegardless of the score obtained, it's very important to always store the company. Note that we add a 2-second \"wait\" module to respect Google Sheet's rate limits.\n\nWe add the company to the \"Companies\" sheet in this [Google Sheet](https://docs.google.com/spreadsheets/d/1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM/edit?usp=sharing) which you can make a copy of and use.\n\nThis AI scoring functionality is extremely impressive once perfectly configured, so I recommend taking some time to test with several companies to ensure the scoring system works well for your needs!\n\n"},"typeVersion":1},{"id":"bf61dc0f-68a7-4ddc-b281-b03a8d8979c1","name":"Wait 2s","type":"n8n-nodes-base.wait","position":[2060,800],"webhookId":"d22fd305-d8f6-4fc3-8a96-62386fa30f94","parameters":{"amount":2},"typeVersion":1.1},{"id":"a428ece3-c4e1-4ba0-826f-3fdb861ba867","name":"Add Company to CRM","type":"n8n-nodes-base.googleSheets","position":[2260,800],"parameters":{"columns":{"value":{"ID":"={{ $('Get Company Info').item.json.id }}","Name":"={{ $('Get Company Info').item.json.name }}","Score":"={{ $json.message.content.score }}","State":"Qualified","Summary":"={{ $('Get Company Info').item.json.description }}","Website":"={{ $('Get Company Info').item.json.website }}","LinkedIn":"={{ $('Get Company Info').item.json.url }}"},"schema":[{"id":"Name","type":"string","display":true,"required":false,"displayName":"Name","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Website","type":"string","display":true,"required":false,"displayName":"Website","defaultMatch":false,"canBeUsedToMatch":true},{"id":"LinkedIn","type":"string","display":true,"required":false,"displayName":"LinkedIn","defaultMatch":false,"canBeUsedToMatch":true},{"id":"ID","type":"string","display":true,"required":false,"displayName":"ID","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Summary","type":"string","display":true,"required":false,"displayName":"Summary","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Score","type":"string","display":true,"required":false,"displayName":"Score","defaultMatch":false,"canBeUsedToMatch":true},{"id":"State","type":"string","display":true,"removed":false,"required":false,"displayName":"State","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/10lxvwdeCf7vrAuWsNRGIsSTICEI3z-SUCDVHr8XzAYQ/edit#gid=0","cachedResultName":"Companies"},"documentId":{"__rl":true,"mode":"list","value":"1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM/edit?usp=drivesdk","cachedResultName":"CRM"}},"credentials":{"googleSheetsOAuth2Api":{"id":"Y8D8KsfgZCZmP2Vh","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"66bb5166-a259-433d-afa7-3d08c3fc0291","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[0,0],"parameters":{"width":600,"height":380,"content":"## Introduction\nWelcome to my template! Before explaining how to set it up, here's some important information:\n\nThis automation is an alternative version of [this template](https://n8n.io/workflows/3717-search-linkedin-companies-and-add-them-to-airtable-crm/) that differs by using Google Sheets instead of Airtable and adding a Lead Scoring system allowing for more precision in our targeting.\n\nThis automation therefore allows you, starting from a LinkedIn search, to enrich company data and score them to determine if they might be interested in your services/product.\n\nFor any questions, you can send me a DM on my [LinkedIn](https://www.linkedin.com/in/matthieu-belin83/) :)  "},"typeVersion":1},{"id":"65163811-d0e8-4fbc-8cf6-f7534cdbf952","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[680,0],"parameters":{"width":600,"height":380,"content":"## Setup\n- Create an account on [Ghost Genius API](ghostgenius.fr) and get your API key.\n\n- Configure the Search Companies and Get Company Info modules by creating a \"Header Auth\" credential:\n**Name: \"Authorization\"**\n**Value: \"Bearer YOUR_TOKEN_HERE\"**\n\n- Create a copy of this [Google Sheet](https://docs.google.com/spreadsheets/d/1LfhqpyjimLjyQcmWY8mUr6YtNBcifiOVLIhAJGV9jiM/edit?usp=sharing) by clicking on File => Make a copy (in Google Sheet).\n\n- Configure your Google Sheet credential by following the n8n documentation.\n\n- Create an OpenAI key [here](https://platform.openai.com/docs/overview) and add the credential to the \"AI Company Scoring\" node following the n8n documentation.\n\n- Add your information to the \"Set Variables\" node."},"typeVersion":1},{"id":"46d71790-f8f1-4d27-b1c2-127e86981721","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[1360,0],"parameters":{"width":600,"height":380,"content":"## Tools \n**(You can use the API and CRM of your choice; this is only a suggestion)**\n\n- API Linkedin : [Ghost Genius API](https://ghostgenius.fr) \n\n- API Documentation : [Documentation](https://ghostgenius.fr/docs)\n\n- CRM : [Google Sheet](https://workspace.google.com/intl/en/products/sheets/)\n\n- AI : [OpenAI](https://openai.com)\n\n- LinkedIn Location ID Finder : [Ghost Genius Locations ID Finder](https://ghostgenius.fr/tools/search-sales-navigator-locations-id)"},"typeVersion":1},{"id":"efa59bb6-f08b-449d-90d5-6adef12ed05d","name":"Start","type":"n8n-nodes-base.manualTrigger","position":[-420,780],"parameters":{},"typeVersion":1},{"id":"1014422f-95c8-4473-8760-acbe7f9f9898","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[880,-120],"parameters":{"color":3,"height":80,"content":"# [Setup Video](http://youtube.com/watch?v=m904RNxtF0w&t)\n"},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"08f2e920-6ec1-46a2-8e39-3dfe5f479528","connections":{"Start":{"main":[[{"node":"Set Variables","type":"main","index":0}]]},"Wait 2s":{"main":[[{"node":"Add Company to CRM","type":"main","index":0}]]},"Set Variables":{"main":[[{"node":"Search Companies","type":"main","index":0}]]},"Is New Company?":{"main":[[{"node":"AI Company Scoring","type":"main","index":0}],[{"node":"Process Each Company","type":"main","index":0}]]},"Get Company Info":{"main":[[{"node":"Filter Valid Companies","type":"main","index":0}]]},"Search Companies":{"main":[[{"node":"Extract Company Data","type":"main","index":0}]]},"AI Company Scoring":{"main":[[{"node":"Wait 2s","type":"main","index":0}]]},"Add Company to CRM":{"main":[[{"node":"Process Each Company","type":"main","index":0}]]},"Extract Company Data":{"main":[[{"node":"Process Each Company","type":"main","index":0}]]},"Process Each Company":{"main":[[],[{"node":"Get Company Info","type":"main","index":0}]]},"Filter Valid Companies":{"main":[[{"node":"Check If Company Exists","type":"main","index":0}],[{"node":"Process Each Company","type":"main","index":0}]]},"Check If Company Exists":{"main":[[{"node":"Is New Company?","type":"main","index":0}]]}}}


name: Build a Personal Assistant with Google Gemini, Gmail and Calendar using MCP
author_name: Aitor | 1Node
author_username: aitoralonso
categories: []
created_at: 2025-05-06T17:01:07.757Z
updated_at: 2025-05-06T17:01:07.757Z
url: https://n8n.io/workflows/3905
scraped_at: 2025-07-06 21:16:30
description:
  Talk to Your Apps: Building a Personal Assistant MCP Server with Google Gemini

  Wouldn't it be cool to just tell your computer or phone to "schedule a meeting with Sarah next Tuesday at 3 PM" or "find John Doe's email address" and have it actually do it? That's the dream of a personal assistant!

  With n8n and the power of MCP and AI models like Google Gemini, you can actually build something pretty close to that. We've put together a workflow that shows you how you can use a natural language chat interface to interact with your other apps, like your CRM, email, and calendar.

  What You Need to Get Started

  Before you dive in, you'll need a few things:

  n8n:** An n8n instance (either cloud or self-hosted) to build and run your workflow.
  Google Gemini Access:** Access to the Google Gemini model via an API key.
  Credentials for Your Apps:** API keys or login details for the specific CRM, Email, and Calendar services you want to connect (like Google Sheets for CRM, Gmail, Google Calendar, etc., depending on your chosen nodes).
  A Chat Interface:** A way to send messages to n8n to trigger the workflow (e.g., via a chat app node or webhook).

  How it Works (In Simple Terms)

  Imagine this workflow is like a helpful assistant who sits between you and your computer.

  Step 1: You Talk, the AI Agent Listens

  It all starts when you send a message through your connected chat interface. Think of this as you speaking directly to your assistant.

  Step 2: The Assistant's Brain (Google Gemini)

  Your message goes straight to the assistant's "brain." In this case, the brain is powered by a smart AI model like Google Gemini. In our template we are using the latest Gemini 2.5 Pro. But this is totally up to you. Experiment and track which model fits the kind of tasks you will pass to the agent. Its job is to understand exactly what you're asking for.

  Are you asking to create something?
  Are you asking to find information?
  Are you asking to update something?

  The brain also uses a "memory" so it can remember what you've talked about recently, making the conversation feel more natural. We are using the default context window, which is the past 5 interactions.

  Step 3: The Assistant Decides What Tool to Use

  Once the brain understands your request, the assistant figures out the best way to help you. It looks at the request and thinks, "Okay, to do this, I need to use one of my tools."

  Step 4: The Assistant's Toolbox (MCP & Your Apps)

  Here's where the "MCP" part comes in. Think of "MCP" (Model Context Protocol) as the assistant's special toolbox. Inside this toolbox are connections to all the different apps and services you use – your CRM for contacts, your email service, and your calendar.

  The MCP system acts like a manager for these tools, making them available to the assistant whenever they're needed.

  Step 5: Using the Right Tool for the Job

  Based on what you asked for, the assistant picks the correct tool from the toolbox.

  If you asked to find a contact, it grabs the "Get Contact" node from the CRM section.
  If you wanted to schedule a meeting, it picks the "Create Event" node from the Calendar section.
  If you asked to draft an email, it uses the "Draft Email" node.

  Step 6: The Tool Takes Action

  Now, the node or set of nodes get to work! It performs the action you requested within the specific app.

  The CRM tool finds or adds the contact.
  The Email tool drafts the message.
  The Calendar tool creates the event.

  Step 7: Task Completed!

  And just like that, your request is handled automatically, all because you simply told your assistant what you wanted in plain language.

  Why This is Awesome

  This kind of workflow shows the power of combining AI with automation platforms like n8n. You can move beyond clicking buttons and filling out forms, and instead, interact with your digital life using natural conversation. n8n makes it possible to visually build these complex connections between your chat, the AI brain, and all your different apps.

  Taking it Further (Possible Enhancements)

  This is just the start! You could enhance this personal assistant by:

  Connecting more apps and services (task managers, project tools, etc.).
  Adding capabilities to search the web or internal documents.
  Implementing more sophisticated memory or context handling.
  Getting a notification when the AI agent is done completing each task such as in Slack or Microsoft Teams.
  Allowing the assistant to ask clarifying questions if needed. Building a robust prompt for the AI agent.

  Ready to Automate Your Workflow?

  Imagine the dozens of hours your team could save weekly by automating repetitive tasks through a simple, natural language interface. 

  Need help?
  Feel free to contact us at 1 Node.
  Get instant access to a library of free resources we created.

nodes_used:
  ["@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.memoryBufferWindow","@n8n/n8n-nodes-langchain.lmChatGoogleGemini","@n8n/n8n-nodes-langchain.mcpClientTool"]

workflow_json:
  {"id":"KhUd3rHKtZAImiXZ","meta":{"instanceId":"9219ebc7795bea866f70aa3d977d54417fdf06c41944be95e20cfb60f992db19","templateCredsSetupCompleted":true},"name":"Personal Assistant MCP server","tags":[],"nodes":[{"id":"f27f3d00-8019-401f-a1c4-5c9754ca5d7e","name":"When chat message received","type":"@n8n/n8n-nodes-langchain.chatTrigger","position":[-220,-60],"webhookId":"989c3a79-5a0c-4ca1-a542-55e060816121","parameters":{"options":{}},"typeVersion":1.1},{"id":"49e4bd69-141f-47ae-bb97-f03a92e56131","name":"Google Gemini Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[-80,140],"parameters":{"options":{},"modelName":"models/gemini-2.5-pro-preview-05-06"},"credentials":{"googlePalmApi":{"id":"MF12DwQJWL1egyiN","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"aaa58803-52ad-439b-8876-05a84fc63eaf","name":"Simple Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[120,140],"parameters":{},"typeVersion":1.3},{"id":"5c49309e-054c-4097-b8c3-1bf0b10539ec","name":"MCP Server Trigger","type":"@n8n/n8n-nodes-langchain.mcpTrigger","position":[20,340],"webhookId":"b37ab045-0b99-4d57-af44-6ae1e9ac6381","parameters":{"path":"b37ab045-0b99-4d57-af44-6ae1e9ac6381"},"typeVersion":1},{"id":"24e5ee35-c53c-4e82-9d79-d48d9220d7ac","name":"MCP Client","type":"@n8n/n8n-nodes-langchain.mcpClientTool","position":[480,140],"parameters":{"sseEndpoint":"<set-your-url-here>"},"typeVersion":1},{"id":"24d7de59-9db2-43e8-ad2a-923bbfc9877b","name":"Create event","type":"n8n-nodes-base.googleCalendarTool","position":[820,380],"parameters":{"calendar":{"__rl":true,"mode":"list","value":"hello@1node.ai","cachedResultName":"hello@1node.ai"},"additionalFields":{}},"credentials":{"googleCalendarOAuth2Api":{"id":"UfvyikkM1kt4EcMl","name":"Google Calendar account"}},"typeVersion":1.3},{"id":"54a2e041-8c5c-40bb-ae6b-1494b8a5a198","name":"Update event","type":"n8n-nodes-base.googleCalendarTool","position":[580,720],"parameters":{"eventId":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}","calendar":{"__rl":true,"mode":"list","value":"hello@1node.ai","cachedResultName":"hello@1node.ai"},"operation":"update","updateFields":{}},"credentials":{"googleCalendarOAuth2Api":{"id":"UfvyikkM1kt4EcMl","name":"Google Calendar account"}},"typeVersion":1.3},{"id":"7bebda2e-711f-478b-8ba3-36306b1ffb49","name":"Draft email","type":"n8n-nodes-base.gmailTool","position":[260,780],"webhookId":"4e76cb3d-4239-4030-a23a-544029535f70","parameters":{"message":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message', `Sign off as \"Your name, company name\"`, 'string') }}","options":{},"subject":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Subject', ``, 'string') }}","resource":"draft"},"credentials":{"gmailOAuth2":{"id":"q3P6IybvNdDiPZ52","name":"Gmail account"}},"typeVersion":2.1},{"id":"26538db2-f3af-47a8-b97e-2afa7d9ea05d","name":"Personal Assistant","type":"@n8n/n8n-nodes-langchain.agent","position":[40,-60],"parameters":{"options":{}},"typeVersion":1.9},{"id":"04c5d14f-a80d-4113-b3ff-a6ee1ab3917e","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[920,280],"parameters":{"color":5,"width":560,"height":620,"content":"# Calendar nodes\n\nYou could order your agent to create a new event in your Google Calendar, find a specific event, get multiple events or update an event's details. \n\n**The true power of these nodes regarding Email, CRM and Calendar remains in combining multiple into one set of instructions**.\n\n## Examples:\n\n- Find the contact for Jhon for A. Corp and send him an email asking saying that you have scheduled the meeting for next Wednesday at 9AM. Draft an email to remind him of the details and the topic of discussion being the weekly update call and the main company bottlenecks.\n- Update the contact details for Jhon since he changed his email and company to B corp and john[at]bcorpfakeemail[dot]com and please update me about my upcoming meetings with him next month.\n- Send me a summary for all my meetings today. Draft one email for each different person that I'll meet with today, reminding them about today's meeting\n"},"typeVersion":1},{"id":"0de610d2-20bf-4fd4-b93e-60b082d22e56","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[0,700],"parameters":{"color":3,"width":460,"height":500,"content":"# Email nodes\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nYour AI Agent will be able to search through your email inbox to find specific email content for you. Based on this records you can fetch information quickly and order to draft responses to review later.\n\n## Examples:\n\n- hey what were the last 5 emails sent to Jon from X corp? \n- Draft an email with these details to Jon sharing I can't make it today and propose a new time for 9AM tomorrow. "},"typeVersion":1},{"id":"e084e23e-473b-4798-a39c-00529ef9e827","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-720,360],"parameters":{"color":4,"width":660,"height":480,"content":"# CRM nodes\n\nWith these node operations your \nAI agent will be able to do the following:\n\n- Add a new row with contact data\n- Find a row and its details in the table\n- Update a value or group of values\n\n\n## Examples:\n\n- Add a new contact data with Rick as first name\n his cell is +1 XXX XXX XXXX. \nI will tell you the email later on.\n- Can you tell me the details and email for Jon Doe?\n I want to send him an email reminder.\n- Update Rick's email to rick[at]someemail[dot]com from X corp. please."},"typeVersion":1},{"id":"dc9dcee5-35ec-4ea3-8c67-21c277705dec","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[360,-220],"parameters":{"width":480,"height":480,"content":"## MCP Client\n\nPaste your MCP client URL from the MCP server trigger node.\n\nCustomize your output node to receive the workflow completion notifications (eg. Telegram, Gmail) from your personal assistant"},"typeVersion":1},{"id":"1764e9cd-7fc1-46e7-bc97-33d4b81d5141","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[520,280],"parameters":{"color":5,"width":400,"height":620,"content":""},"typeVersion":1},{"id":"b4dda81f-fa22-43ec-a841-7b924b8884e8","name":"Add new row","type":"n8n-nodes-base.googleSheetsTool","position":[-340,440],"parameters":{"columns":{"value":{},"schema":[],"mappingMode":"autoMapInputData","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit?usp=drivesdk","cachedResultName":"Contacts"}},"credentials":{"googleSheetsOAuth2Api":{"id":"twZdLFsI3kTnqtpG","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"e3bc61e0-1d95-4554-b7ba-f76c3f105339","name":"Find row","type":"n8n-nodes-base.googleSheetsTool","position":[-280,580],"parameters":{"options":{},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit?usp=drivesdk","cachedResultName":"Contacts"}},"credentials":{"googleSheetsOAuth2Api":{"id":"twZdLFsI3kTnqtpG","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"461469da-c47a-486f-98c2-71fcc9abc235","name":"Update row","type":"n8n-nodes-base.googleSheetsTool","position":[-180,680],"parameters":{"columns":{"value":{},"schema":[{"id":"row_number","type":"string","display":true,"removed":false,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"autoMapInputData","matchingColumns":["row_number"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1JDoEkNqk1c_TrIht2n1XF-jmIWpk48DP3NUaNbhcFV8/edit?usp=drivesdk","cachedResultName":"Contacts"}},"credentials":{"googleSheetsOAuth2Api":{"id":"twZdLFsI3kTnqtpG","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"01c0ba70-c1b1-454f-9b4e-0727c8280ace","name":"Find emails","type":"n8n-nodes-base.gmailTool","position":[120,780],"webhookId":"b36e3112-52b1-4e03-a2d3-74d5d4705891","parameters":{"filters":{},"operation":"getAll","returnAll":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Return_All', ``, 'boolean') }}"},"credentials":{"gmailOAuth2":{"id":"q3P6IybvNdDiPZ52","name":"Gmail account"}},"typeVersion":2.1},{"id":"40efd312-032f-496b-8485-a6a49001aa75","name":"Find single event","type":"n8n-nodes-base.googleCalendarTool","position":[760,520],"parameters":{"eventId":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Event_ID', ``, 'string') }}","options":{},"calendar":{"__rl":true,"mode":"list","value":"hello@1node.ai","cachedResultName":"hello@1node.ai"},"operation":"get"},"credentials":{"googleCalendarOAuth2Api":{"id":"UfvyikkM1kt4EcMl","name":"Google Calendar account"}},"typeVersion":1.3},{"id":"8ac5c33e-f4fb-4627-98d5-b66838db3037","name":"Find multiple events","type":"n8n-nodes-base.googleCalendarTool","position":[680,620],"parameters":{"limit":10,"options":{},"calendar":{"__rl":true,"mode":"list","value":"hello@1node.ai","cachedResultName":"hello@1node.ai"},"operation":"getAll"},"credentials":{"googleCalendarOAuth2Api":{"id":"UfvyikkM1kt4EcMl","name":"Google Calendar account"}},"typeVersion":1.3}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"99536a4c-8e1b-4b7e-9a2a-8baa404499fe","connections":{"Find row":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"MCP Client":{"ai_tool":[[{"node":"Personal Assistant","type":"ai_tool","index":0}]]},"Update row":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Add new row":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Draft email":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Find emails":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Create event":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Update event":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Simple Memory":{"ai_memory":[[{"node":"Personal Assistant","type":"ai_memory","index":0}]]},"Find single event":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Find multiple events":{"ai_tool":[[{"node":"MCP Server Trigger","type":"ai_tool","index":0}]]},"Google Gemini Chat Model":{"ai_languageModel":[[{"node":"Personal Assistant","type":"ai_languageModel","index":0}]]},"When chat message received":{"main":[[{"node":"Personal Assistant","type":"main","index":0}]]}}}


name: Automate YouTube Uploads with AI-Generated Metadata from Google Drive
author_name: JPres
author_username: stardawnai
categories: []
created_at: 2025-05-06T17:06:31.065Z
updated_at: 2025-05-06T17:06:31.065Z
url: https://n8n.io/workflows/3906
scraped_at: 2025-07-06 21:16:30
description:
  👥 Who Is This For?  
  Content creators, marketing teams, and channel managers who want a simple, hands‑off solution to upload videos and automatically generate optimized metadata from video transcripts.

  🛠 What Problem Does This Solve?  
  Manual video uploads with proper metadata creation is time‑consuming and repetitive. This workflow fully automates:  
  Monitoring a specific Google Drive folder for new video uploads  
  Seamless YouTube upload processing  
  Transcript extraction for context understanding  
  AI‑powered generation of titles, descriptions, and tags  
  Metadata application to uploaded videos without manual intervention  

  🔄 Node‑by‑Node Breakdown  
  | Step | Node Purpose                                                        |
  |------|---------------------------------------------------------------------|
  | 1    | New Video? (Trigger) – Monitors specified Google Drive folder       |
  | 2    | Download New Video – Retrieves the video file from Google Drive     |
  | 3    | Upload to YouTube – Uploads the video to YouTube with initial settings |
  | 4    | Get Transcript – Extracts transcript from the uploaded video        |
  | 5    | Adjust Transcript Format – Formats raw transcript for processing    |
  | 6    | Create Description – Generates SEO‑optimized description            |
  | 7    | YT Tags (Message Model) – Creates relevant tags based on content    |
  | 8    | YT Title (Message Model) – Generates compelling title               |
  | 9    | Define File Path Upload Format (Optional) – Structures data paths   |
  | 10   | Update Video’s Metadata – Applies generated title, description, tags|

  ⚙️ Pre‑conditions / Requirements  
  n8n with Google Drive and YouTube API credentials configured (stored as n8n credentials/variables; no hard‑coded IDs)  
  Dedicated Google Drive folder for video uploads  
  YouTube channel with proper upload permissions  
  AI service access for transcript processing and metadata generation  
  Sufficient storage for temporary video handling  

  ⚙️ Setup Instructions  
  Import this workflow into your n8n instance.  
  Configure Google Drive credentials; reference folder ID via n8n variable (do not hard‑code).  
  Set up YouTube API credentials with upload and edit permissions.  
  Specify the target Google Drive folder ID in the New Video? trigger node (via variable).  
  Configure AI service credentials for transcript and metadata generation.  
  Adjust message templates for title, description, and tag creation.  
  Test with a small video file before production use.  

  🎨 How to Customize  
  Modify AI prompts to match your channel’s tone and style.  
  Add conditional logic based on video categories or naming conventions.  
  Implement notification systems to alert when uploads complete.  
  Create custom metadata templates for different content types.  
  Include timestamps or chapter markers based on transcript analysis.  
  Add social media sharing nodes to announce new uploads.  

  ⚠️ Important Notes  
  Video quality is preserved through the upload process.  
  Consider YouTube API quotas when handling multiple uploads.  
  Transcript quality affects metadata generation results.  
  Videos are initially uploaded without visibility adjustments.  
  Processing time depends on video length and transcript complexity.  

  🔐 Security and Privacy  
  Store API credentials and folder IDs as n8n Credentials/Variables—remove any hard‑coded tokens or IDs.  
  Video files are processed temporarily and not stored permanently.  
  Limit Google Drive folder access to authorized users only.  
  Manage YouTube upload permissions carefully (use OAuth/service accounts).  
  Ensure compliance with organizational data‑handling policies.  

nodes_used:
  ["n8n-nodes-base.httpRequest","n8n-nodes-base.googleDrive","n8n-nodes-base.youTube","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.openAi","@n8n/n8n-nodes-langchain.lmChatGoogleGemini"]

workflow_json:
  {"id":"gIZpJgLpUgdoNNDZ","meta":{"instanceId":"9ee1559971f36d9785fb97b48d76ed3a8ac302ff00dd93d8b7301e3feab97aed","templateCredsSetupCompleted":true},"name":"YT New Video Upload","tags":[],"nodes":[{"id":"add1cd70-4bf4-42f5-87d9-68fa31af7f56","name":"Download New Video","type":"n8n-nodes-base.googleDrive","position":[-140,220],"parameters":{"fileId":{"__rl":true,"mode":"id","value":"={{ $json.id }}"},"options":{},"operation":"download"},"credentials":{"googleDriveOAuth2Api":{"id":"8GbkH30Del5g4Kbq","name":"Jim Privat"}},"typeVersion":3},{"id":"1308e94d-1c68-4fae-9d94-ba6248947581","name":"New Video?","type":"n8n-nodes-base.googleDriveTrigger","position":[-360,220],"parameters":{"event":"fileCreated","options":{},"pollTimes":{"item":[{"mode":"everyMinute"}]},"triggerOn":"specificFolder","folderToWatch":{"__rl":true,"mode":"id","value":"YOURIDHERE"}},"credentials":{"googleDriveOAuth2Api":{"id":"8GbkH30Del5g4Kbq","name":"Jim Privat"}},"typeVersion":1},{"id":"dfb073fc-4365-4669-a17c-73407a14a655","name":"Create Description","type":"@n8n/n8n-nodes-langchain.openAi","position":[1000,180],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1-nano","cachedResultName":"GPT-4.1-NANO"},"options":{},"messages":{"values":[{"role":"system","content":"You are a professional copywriter.  \nYou receive the transcript of an economics-related video and create a detailed but concise summary (with paragraphs) about its content.  \n\nWrite a detailed summary (with paragraphs) about the content of the podcast.  \n\nYour output will be used for the YouTube video description. Start with something like: \"In this video...\" or \"In this episode...\".  \nWrite from my perspective, using phrases like \"my opinion\" or \"in my view,\" in the first person, but never phrases like \"In this episode, I learn...\" or similar, as I always explain or discuss the content. YOU NEVER WRITE THINGS LIKE \"THE SPEAKER SAYS\"! Always from my position.  \n\nImportant: Use clear and assertive statements as formulated in the transcript. Avoid neutral or uncertain phrases like \"it could,\" \"I assume that,\" \"possibly,\" or similar. The statements should be confident and definitive to powerfully convey the podcast’s content.  \nInclude a few (2-4) emojis where appropriate.  \nEnd the post with 2-5 relevant hashtags. The hashtags should be broad, like #economics #money #gold, or similar, depending on what fits."},{"content":"=Here is the transcript:\n\n{{ $json.transcript }}"}]}},"credentials":{"openAiApi":{"id":"8VZkrnuHUTXNwnlP","name":"Midgard#1"}},"typeVersion":1.7},{"id":"25ba2d0b-3a7a-49d6-acfa-6e7fa2b98e86","name":"2.5FlashPrev","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[1340,180],"parameters":{"options":{},"modelName":"models/gemini-2.5-flash-preview-04-17"},"credentials":{"googlePalmApi":{"id":"2Y7XbTPZaxPz6Vhw","name":"Jim Privat"}},"typeVersion":1},{"id":"23141199-5df7-47d0-8d0e-f5b3a7fe7668","name":"YT Tags","type":"@n8n/n8n-nodes-langchain.agent","position":[1300,-20],"parameters":{"text":"=Now follows the actual topic/transcript. Give me the YouTube tags for it:\n\n{{ $('Adjust Transcript Format').item.json.transcript }}","options":{"systemMessage":"This video is about the future gold price and how it affects the returns of high-performing assets like stocks and bonds in their adjusted returns.\n\nExpected output:\nGold price, future gold price, gold investments, asset returns, stocks and bonds, investment returns, adjusted returns, gold market, financial markets, gold price forecast, economic trends, investing in gold, stock market analysis, bond market, investment strategies, inflation and gold, gold vs. stocks, financial analysis, precious metals, portfolio management, market outlook, investment tips"},"promptType":"define"},"typeVersion":1.9},{"id":"c3ff8e2e-7ce6-4538-baea-91a375b98dcb","name":"Get Transcript","type":"n8n-nodes-base.httpRequest","position":[500,220],"parameters":{"url":"=https://api.apify.com/v2/acts/pintostudio~youtube-transcript-scraper/run-sync-get-dataset-items","method":"POST","options":{},"jsonBody":"={\n  \"videoUrl\": \"https://www.youtube.com/watch?v={{ $json.id }}\"\n}","sendBody":true,"sendQuery":true,"specifyBody":"json","queryParameters":{"parameters":[{"name":"token","value":"={{$json.token}}"}]}},"typeVersion":4.2,"alwaysOutputData":false},{"id":"6a03cbf3-5718-4db8-b9cf-31d6d93f73e7","name":"Adjust Transcript Format","type":"n8n-nodes-base.code","position":[680,220],"parameters":{"jsCode":"const items = $input.all();\n\nconst transcriptStrings = items.flatMap(item => {\n  const dataArray = item.json.data;\n\n  if (!dataArray || !Array.isArray(dataArray)) {\n    return [];\n  }\n\n  const segmentTexts = dataArray.map(segment => {\n      if (segment && typeof segment.text === 'string') {\n          return segment.text;\n      } else {\n          return '';\n      }\n  });\n\n  return segmentTexts;\n});\n\nconst transcript = transcriptStrings.join(' ');\n\nreturn [\n  {\n    json: {\n      transcript: transcript,\n    },\n  },\n];"},"typeVersion":2},{"id":"c6e0bf83-6eaa-41be-9ea1-0dba4cace444","name":"Update Video's Metadata","type":"n8n-nodes-base.youTube","onError":"continueErrorOutput","position":[2160,240],"parameters":{"title":"={{ $('YT Title').item.json.title }}","videoId":"={{ $('Upload Video to Youtube').item.json.uploadId }}","resource":"video","operation":"update","categoryId":"25","regionCode":"DE","updateFields":{"tags":"={{ $('YT Tags').item.json.message.content }}","description":"={{ $('Create Description').first().json.message.content }}\n\nDiese textbasierte Zusammenfassung des Videos wurde automatisch mit dem KI-Modell gpt-4.1-nano erstellt.]\n"}},"credentials":{"youTubeOAuth2Api":{"id":"jKf1Vc4ZycKxf0im","name":"YouTube Jim Privat"}},"typeVersion":1},{"id":"813fc8fc-ed3c-4cd7-8aa5-4c4084fd4e0d","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-380,160],"parameters":{"color":4,"width":700,"height":240,"content":"# Upload New Video to Youtube 🎥⬆️"},"typeVersion":1},{"id":"0fb360ba-f843-4820-b05d-3149d7516526","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[320,-100],"parameters":{"color":4,"width":2660,"height":500,"content":"# Get Transcript for Context and Generate Metadata from It 📝🔍"},"typeVersion":1},{"id":"bb494b9e-a707-492a-b88a-83fce01a742f","name":"YT Title","type":"@n8n/n8n-nodes-langchain.openAi","position":[1720,-20],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1-nano","cachedResultName":"GPT-4.1-NANO"},"options":{},"messages":{"values":[{"role":"system","content":"You are a professional copywriter for SEO-optimized YouTube titles."},{"content":"=Write me a suitable SEO YouTube title for the transcript of the following video transcript. Only the title, nothing else. Max 100 characters, so keep it short."}]}},"credentials":{"openAiApi":{"id":"8VZkrnuHUTXNwnlP","name":"Midgard#1"}},"typeVersion":1.7},{"id":"a82d27b7-967e-48f8-8ead-a184872a7abc","name":"Delete File from Upload Folder (Optional)","type":"n8n-nodes-base.googleDrive","position":[2460,-20],"parameters":{"fileId":{"__rl":true,"mode":"id","value":"={{ $('Download New Video').item.json.id }}"},"options":{},"operation":"deleteFile"},"credentials":{"googleDriveOAuth2Api":{"id":"8GbkH30Del5g4Kbq","name":"GDRIVE ACCOUNT P"}},"typeVersion":3},{"id":"4cff725b-cff0-45e0-9cab-24c2f233c94e","name":"Upload Video to Youtube","type":"n8n-nodes-base.youTube","position":[140,220],"parameters":{"title":"adadada","options":{"privacyStatus":"private","selfDeclaredMadeForKids":false},"resource":"video","operation":"upload","categoryId":"25","regionCode":"DE"},"credentials":{"youTubeOAuth2Api":{"id":"jKf1Vc4ZycKxf0im","name":"YouTube Jim Privat"}},"typeVersion":1},{"id":"691fda3c-736c-4d6e-b4f3-8db68a581c79","name":"ApifyToken","type":"n8n-nodes-base.set","position":[320,220],"parameters":{"options":{},"assignments":{"assignments":[{"id":"2eb41a6f-d0ef-4ca2-be47-f93b1d5c1edb","name":"token","type":"string","value":"YOURTOKENHERE"}]}},"typeVersion":3.4}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"3a879f43-d8a6-4a87-a233-bafeb296d21a","connections":{"YT Tags":{"main":[[{"node":"YT Title","type":"main","index":0}]]},"YT Title":{"main":[[{"node":"Update Video's Metadata","type":"main","index":0}]]},"ApifyToken":{"main":[[{"node":"Get Transcript","type":"main","index":0}]]},"New Video?":{"main":[[{"node":"Download New Video","type":"main","index":0}]]},"2.5FlashPrev":{"ai_languageModel":[[{"node":"YT Tags","type":"ai_languageModel","index":0}]]},"Get Transcript":{"main":[[{"node":"Adjust Transcript Format","type":"main","index":0}]]},"Create Description":{"main":[[{"node":"YT Tags","type":"main","index":0}]]},"Download New Video":{"main":[[{"node":"Upload Video to Youtube","type":"main","index":0}]]},"Update Video's Metadata":{"main":[[{"node":"Delete File from Upload Folder (Optional)","type":"main","index":0}]]},"Upload Video to Youtube":{"main":[[{"node":"ApifyToken","type":"main","index":0}]]},"Adjust Transcript Format":{"main":[[{"node":"Create Description","type":"main","index":0}]]}}}


name: Automate Lead Qualification with RetellAI Phone Agent, OpenAI GPT & Google Sheet
author_name: Dr. Firas
author_username: drfiras
categories: []
created_at: 2025-05-06T23:32:32.465Z
updated_at: 2025-05-06T23:32:32.465Z
url: https://n8n.io/workflows/3912
scraped_at: 2025-07-06 21:16:30
description:
  👉 Build a Phone Agent to qualify outbound leads and schedule inbound calls

  Who is this for?

  This workflow is designed for sales teams, call centers, and businesses handling both outbound and inbound lead calls who want to automate their qualification, follow-up, and call documentation process without manual intervention. It’s ideal for teams using Google Sheets, RetellAI, OpenAI, and Gmail as part of their tech stack.

  Real-World Use Cases

  🛍 E-commerce – Instantly handle product FAQs and order status checks, 24/7.
  🏬 Retail Stores – Share store hours, directions, and return policies without lifting a finger.
  🍽 Restaurants – Take reservations or answer menu questions automatically.
  💼 Service Providers – Book appointments or consultations while you focus on your craft.
  📞 Any Local Business – Deliver friendly, consistent phone support — no live agent required.

  What problem is this workflow solving?

  Managing lead calls at scale can be chaotic—between scheduling outbound qualification calls, handling inbound appointment requests, and making sure every call is documented and followed up. This workflow automates the entire process, reducing human error and saving time by:

  ✅ Sending reminders to reps for outbound calls
  ✅ Automatically placing calls with RetellAI
  ✅ Handling inbound calls and checking caller details
  ✅ Generating and emailing call summaries automatically

  What this workflow does

  This n8n template connects Google Sheets, RetellAI, OpenAI, and Gmail into a seamless workflow:

  Outbound Lead Qualification Workflow
     Triggers when a new lead is added to Google Sheets
     Sends an SMS notification to remind the rep to call in 5 minutes
     (Optional) Waits 5 minutes
     Initiates an automated call to the lead via RetellAI

  Inbound Call Appointment Scheduler
     Receives inbound calls from RetellAI (via webhook)
     Checks if the caller’s number exists in Google Sheets
     Responds to RetellAI with a success or error message

  Post-Call Workflow
     Receives post-call data from RetellAI
     Filters only analyzed calls
     Updates the lead’s record in Google Sheets
     Uses OpenAI to generate a call summary
     Emails the summary to a team inbox or rep

  Setup

  ✅ You need an active RetellAI API key

  Sign up for RetellAI, create an agent, and set the webhook URLs (n8n_call for call events).
  Purchase a Twilio phone number and link it to the agent.  

  ✅ Your Google Sheet must have a column for phone numbers (e.g., "Phone")  
  ✅ Gmail account connected and authorized in n8n  
  ✅ OpenAI API key added to your environment variables or credentials

  Configure your Google Sheets node with the correct spreadsheet ID and range
  Add your RetellAI API key to the HTTP request nodes
  Connect your Gmail account in the Gmail node
  Add your OpenAI key in the OpenAI node

  👉 See full setup guide here: Notion Documentation

  How to customize this workflow to your needs

  Change SMS content**: Edit the text in the “Send SMS reminder” node to match your team’s tone
  Modify call wait time**: Enable and adjust the “Wait 5 minutes” node to any delay you prefer
  Add CRM integration**: Replace or extend the Google Sheets node to update your CRM instead of a spreadsheet
  Customize call summary prompts**: Edit the prompt sent to OpenAI to change the summary style or add extra insights
  Send email to different recipients**: Change the recipient address in the Gmail node or make it dynamic from the lead record

  Need help customizing?
  Contact me for consulting and support : Linkedin

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","n8n-nodes-base.twilio","n8n-nodes-base.gmail","@n8n/n8n-nodes-langchain.openAi"]

workflow_json:
  {"id":"QO4Mg23JvVfNCICy","meta":{"instanceId":"a2b23892dd6989fda7c1209b381f5850373a7d2b85609624d7c2b7a092671d44","templateCredsSetupCompleted":true},"name":"Build a Phone Agent to qualify outbound leads and inbound calls with RetellAI -vide","tags":[{"id":"12w64ydbjEKDaM0B","name":"inbound","createdAt":"2025-05-06T20:31:43.427Z","updatedAt":"2025-05-06T20:31:43.427Z"},{"id":"xSqaFWDcbJCRECKZ","name":"outbound","createdAt":"2025-05-06T20:31:38.072Z","updatedAt":"2025-05-06T20:31:38.072Z"}],"nodes":[{"id":"78f39980-c9f8-49b6-93bb-a1f61d347ac3","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-240,0],"parameters":{"width":1260,"height":320,"content":"# Outbound lead qualification call workflow"},"typeVersion":1},{"id":"661006b9-dac7-4ac0-882a-2e0cba9dbae1","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-240,360],"parameters":{"color":5,"width":1260,"height":320,"content":"# Inbound call appointment scheduler workflow"},"typeVersion":1},{"id":"96a278b9-8d2e-4f85-9f6a-2997932a7ca4","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[1060,-420],"parameters":{"color":4,"width":1400,"height":1100,"content":"# Post-call workflow\n## Triggers when a new lead is added in Google Sheets:\n\n### 1 -Sends SMS to remind rep to call in 5 min\n### 2- (Optional delay step)\n### 3- Triggers RetellAI to place an automated call to the lead\n\n## 💡 Requires phone numbers to be formatted in E.164"},"typeVersion":1},{"id":"d082f904-f185-4615-b0d8-9438c731786f","name":"Detect new lead in Google Sheets","type":"n8n-nodes-base.googleSheetsTrigger","position":[-160,100],"parameters":{"event":"rowAdded","options":{},"pollTimes":{"item":[{"mode":"everyHour"}]},"sheetName":{"__rl":true,"mode":"id","value":"="},"documentId":{"__rl":true,"mode":"id","value":"="}},"credentials":{"googleSheetsTriggerOAuth2Api":{"id":"pXaVMshaL2QzVDYh","name":"Google Sheets Trigger account"}},"typeVersion":1},{"id":"c61172c2-7795-47be-acaa-d4824ca69680","name":"Send SMS reminder to call lead in 5 minutes","type":"n8n-nodes-base.twilio","position":[140,100],"parameters":{"to":"={{ $json['Phone Number'] }}","from":"+33600000000","message":"Hello, thanks for your interest in our roofing services. We'll be calling you shortly to ask about your project!","options":{},"resource":"call"},"credentials":{"twilioApi":{"id":"udXVmM3xHYZbMW4g","name":"Twilio account"}},"typeVersion":1},{"id":"d88573d4-ec99-40e4-8603-f1e910d034d1","name":"Wait 5 minutes before making call","type":"n8n-nodes-base.wait","position":[460,100],"webhookId":"344c2d5d-5629-4466-866b-ac6359b3b042","parameters":{"unit":"minutes","amount":1},"typeVersion":1.1},{"id":"d6778895-90dd-471e-9d9d-c48a35154291","name":"Call new lead using RetellAI","type":"n8n-nodes-base.httpRequest","position":[760,100],"parameters":{"url":"https://api.retellai.com/v2/create-phone-call","method":"POST","options":{},"jsonBody":"={\n  \"from_number\": \"+33600000000\",\n  \"to_number\": \"{{ $json['Phone Number'] }}\",\n  \"retell_llm_dynamic_variables\": {\n    \"uuid\": \"{{ $('Detect new lead in Google Sheets').item.json.UUID }}\"\n  }\n}","sendBody":true,"sendHeaders":true,"specifyBody":"json","headerParameters":{"parameters":[{"name":"Authorization","value":"Bearer YOUR_TOKEN_HERE"},{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2},{"id":"8e7e7c0c-2600-4b20-ba30-b855d456d302","name":"Receive inbound call from RetellAI (webhook)","type":"n8n-nodes-base.webhook","position":[-160,460],"webhookId":"ebd11c9b-129c-4b59-8c27-9a4b567305f7","parameters":{"path":"ebd11c9b-129c-4b59-8c27-9a4b567305f7","options":{},"httpMethod":"POST","responseMode":"responseNode"},"typeVersion":2},{"id":"36bf25b0-d39d-4127-b005-5e3619069744","name":"Check if phone number exists in Google Sheets","type":"n8n-nodes-base.googleSheets","position":[300,460],"parameters":{"options":{},"filtersUI":{"values":[{"lookupValue":"={{ $json.body.call_inbound.from_number }}","lookupColumn":"Phone Number"}]},"sheetName":{"__rl":true,"mode":"id","value":"="},"documentId":{"__rl":true,"mode":"id","value":"="}},"credentials":{"googleSheetsOAuth2Api":{"id":"51us92xkOlrvArhV","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"0b2dc7b9-84c1-488b-9d02-47cf6ee460c7","name":"Send response to inbound webhook call","type":"n8n-nodes-base.respondToWebhook","position":[760,460],"parameters":{"options":{},"respondWith":"json","responseBody":"={\n  \"call_inbound\": {\n    \"dynamic_variables\": {\n        \"name\": \"{{ $json.Name }}\"\n    }\n  }\n}"},"typeVersion":1.1},{"id":"063a4a31-429f-4cf0-b248-869131e92633","name":"Receive post-call data from RetellAI (webhook)","type":"n8n-nodes-base.webhook","position":[1180,80],"webhookId":"f8878b78-43ea-4caa-b16c-cde9aaf2e9b1","parameters":{"path":"f8878b78-43ea-4caa-b16c-cde9aaf2e9b1","options":{},"httpMethod":"POST"},"typeVersion":2},{"id":"215e2031-983a-4785-b46d-026f64c115e4","name":"Filter for analyzed calls only","type":"n8n-nodes-base.filter","position":[1400,80],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"a0e40476-0054-43ec-b7a7-e872d1c6061a","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.body.event }}","rightValue":"call_analyzed"}]}},"typeVersion":2.2},{"id":"f8cae0c3-1b5d-47e6-b7fd-b47558c30d3f","name":"Check if call was outbound","type":"n8n-nodes-base.if","position":[1620,80],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"46590184-4e33-48fd-a9f4-c63b13f88c1f","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.body.call.direction }}","rightValue":"outbound"}]}},"typeVersion":2.2},{"id":"8997d5ec-bfb9-4ce9-9e13-6035f02b634e","name":"Update lead record in Google Sheets","type":"n8n-nodes-base.googleSheets","position":[1860,-40],"parameters":{"columns":{"value":{"UUID":"={{ $json.body.call.retell_llm_dynamic_variables.uuid }}","Qualification":"={{ $json.body.call.call_analysis.custom_analysis_data.qualification }}"},"schema":[{"id":"UUID","type":"string","display":true,"removed":false,"required":false,"displayName":"UUID","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Name","type":"string","display":true,"required":false,"displayName":"Name","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Phone Number","type":"string","display":true,"required":false,"displayName":"Phone Number","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Datetime Called","type":"string","display":true,"required":false,"displayName":"Datetime Called","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Qualification","type":"string","display":true,"required":false,"displayName":"Qualification","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":true,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["UUID"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"id","value":"="},"documentId":{"__rl":true,"mode":"id","value":"="}},"credentials":{"googleSheetsOAuth2Api":{"id":"51us92xkOlrvArhV","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"20757ff8-6604-4c80-96ec-32bfac983ed7","name":"Send call summary email","type":"n8n-nodes-base.gmail","position":[2220,-40],"webhookId":"806ec3f9-8bcb-48ef-8e22-9d1ce3b06bf0","parameters":{"sendTo":"youremail@gmail.com","message":"=Name:\n{{ $json.body.call.call_analysis.custom_analysis_data.first_name }}\n\nNumber:\n{{ $json.body.call.call_analysis.custom_analysis_data.phone_number }}\n\nQualification:\n{{ $json.body.call.call_analysis.custom_analysis_data.qualification }}\n\n\nCall Summary:\n{{ $json.body.call.call_analysis.custom_analysis_data.call_summary }}","options":{},"subject":"=New Lead - Call Summary","emailType":"text"},"credentials":{"gmailOAuth2":{"id":"rKxQHWZ2F5XLJmwF","name":"Gmail account"}},"typeVersion":2.1},{"id":"753bd92d-b95b-4710-bf49-6da89a43223f","name":"Generate call summary with OpenAI","type":"@n8n/n8n-nodes-langchain.openAi","position":[1860,180],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"gpt-4.1","cachedResultName":"GPT-4.1"},"options":{},"messages":{"values":[{"content":"=Analyze this call transcript to identify how the call went and identify possible improvements to the voice prompt:\n\n{{ $json.body.call.transcript }}"}]}},"credentials":{"openAiApi":{"id":"6h3DfVhNPw9I25nO","name":"OpenAi account"}},"typeVersion":1.8},{"id":"cf600277-bb07-4f7a-a9b7-3e20017f716d","name":"Send confirmation email to lead","type":"n8n-nodes-base.gmail","position":[2220,180],"webhookId":"453fe9d9-1de6-40a2-bd0c-88cb9c1cc7ef","parameters":{"sendTo":"youremail@gmail.com","message":"=New roofing appointment:\n\nClient Name:\n{{ $('Check if call was outbound').item.json.body.call.call_analysis.custom_analysis_data.first_name }}\n\nClient Number:\n{{ $('Check if call was outbound').item.json.body.call.call_analysis.custom_analysis_data.phone_number }}\n\nAvailabilities:\n{{ $('Check if call was outbound').item.json.body.call.call_analysis.custom_analysis_data.availabilities }}\n\n\nCall Summary:\n{{ $('Check if call was outbound').item.json.body.call.call_analysis.call_summary }}\n\n\nChatGPT analysis of how the call went and suggestions for improving the voice prompt:\n{{ $json.message.content }}","options":{},"subject":"=Roofing Appointment Scheduled","emailType":"text"},"credentials":{"gmailOAuth2":{"id":"rKxQHWZ2F5XLJmwF","name":"Gmail account"}},"typeVersion":2.1},{"id":"f75763b6-0867-4625-89e1-cafa3c9e6e44","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-240,-420],"parameters":{"width":1260,"height":400,"content":"# ✅ General Workflow Explanation\n##  This workflow automates outbound and inbound lead calls with RetellAI, Google Sheets, OpenAI, and Gmail. It handles:\n\nScheduling and reminding outbound qualification calls\nHandling inbound appointment calls\nAutomatically updating records and sending summaries post-call\n\n## 👉 Dependencies:\n\nActive RetellAI API key\nGoogle Sheet set up with lead data\nGmail API credentials configured"},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"879f8e4d-91d7-41fc-825d-08f2ef283c25","connections":{"Check if call was outbound":{"main":[[{"node":"Update lead record in Google Sheets","type":"main","index":0}],[{"node":"Generate call summary with OpenAI","type":"main","index":0}]]},"Filter for analyzed calls only":{"main":[[{"node":"Check if call was outbound","type":"main","index":0}]]},"Detect new lead in Google Sheets":{"main":[[{"node":"Send SMS reminder to call lead in 5 minutes","type":"main","index":0}]]},"Generate call summary with OpenAI":{"main":[[{"node":"Send confirmation email to lead","type":"main","index":0}]]},"Wait 5 minutes before making call":{"main":[[{"node":"Call new lead using RetellAI","type":"main","index":0}]]},"Update lead record in Google Sheets":{"main":[[{"node":"Send call summary email","type":"main","index":0}]]},"Send SMS reminder to call lead in 5 minutes":{"main":[[{"node":"Wait 5 minutes before making call","type":"main","index":0}]]},"Receive inbound call from RetellAI (webhook)":{"main":[[{"node":"Check if phone number exists in Google Sheets","type":"main","index":0}]]},"Check if phone number exists in Google Sheets":{"main":[[{"node":"Send response to inbound webhook call","type":"main","index":0}]]},"Receive post-call data from RetellAI (webhook)":{"main":[[{"node":"Filter for analyzed calls only","type":"main","index":0}]]}}}


name: Document Q&A Chatbot with Gemini AI and Supabase Vector Search for Telegram
author_name: Mohammad Ghaffarifar
author_username: mohamadghaffari
categories: []
created_at: 2025-05-08T14:40:13.702Z
updated_at: 2025-05-08T14:40:13.702Z
url: https://n8n.io/workflows/3940
scraped_at: 2025-07-06 21:16:30
description:
  This template creates a Telegram AI Assistant that answers questions based on your documents, powered by Google Gemini and Supabase. Key features include Intelligent HTML Post-processing for rich formatting in Telegram and Adaptive Message Chunking to handle long text responses.
  📹 Watch the Bot in Action



  ▶️ Click the image above to watch a live demo on YouTube. 

  This video provides a live demonstration of the bot's core features and how it interacts. See a quick walkthrough of its capabilities and user flow.

  How it works:

  User uploads a PDF document to a Telegram bot.

  The workflow processes the PDF, creates embeddings using Google Gemini, and stores these embeddings in a Supabase vector table.

  Users then ask questions to the bot.

  The workflow performs a vector search in Supabase to find relevant document chunks based on the user's query.

  Google Gemini uses the retrieved relevant chunks to generate an intelligent answer.

  The bot sends the formatted answer back to the user on Telegram, utilizing HTML markup for enhanced presentation.

  Set up steps:

  Setup should take approximately 15-20 minutes.

  Import the workflow into your n8n instance.

  Configure credentials for Telegram, Google Gemini, and Supabase.

  Set up your Supabase vector table using the provided SQL script.

  Activate the workflow.

  Detailed setup instructions, including how to get API keys and configure nodes, are available in the sticky notes within the workflow itself.

nodes_used:
  ["n8n-nodes-base.telegram","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.memoryBufferWindow","@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter","@n8n/n8n-nodes-langchain.vectorStoreSupabase","@n8n/n8n-nodes-langchain.documentDefaultDataLoader","@n8n/n8n-nodes-langchain.embeddingsGoogleGemini","@n8n/n8n-nodes-langchain.lmChatGoogleGemini","@n8n/n8n-nodes-langchain.toolVectorStore","@n8n/n8n-nodes-langchain.toolThink"]

workflow_json:
  {"id":"LL0TBxEbXoK2zhqp","meta":{"instanceId":"af80dcc2dbd3882359ca17a5fe5b2d4bd4ca3cf3cbe39546ecc263e2e97807e5","templateId":"self-building-ai-agent","templateCredsSetupCompleted":true},"name":"AI Document Assistant via Telegram + Supabase","tags":[{"id":"Fo1OtHUY0RXxPbjJ","name":"google-gemini","createdAt":"2025-05-01T23:10:32.399Z","updatedAt":"2025-05-01T23:10:32.399Z"},{"id":"HcgCSAB27xdCFyCf","name":"vectorstore","createdAt":"2025-05-01T23:10:13.148Z","updatedAt":"2025-05-01T23:10:13.148Z"},{"id":"NFkP0TdshXJdwIOG","name":"chatbot","createdAt":"2025-05-01T23:09:53.855Z","updatedAt":"2025-05-01T23:09:53.855Z"},{"id":"QXeMQNrN4XlEXs1I","name":"telegram","createdAt":"2025-05-01T23:09:23.634Z","updatedAt":"2025-05-01T23:09:23.634Z"},{"id":"RLZgltwJo60sK1Dm","name":"embeddings","createdAt":"2025-05-01T23:10:20.621Z","updatedAt":"2025-05-01T23:10:20.621Z"},{"id":"fMH2im2pHJBOzkXp","name":"document-qa","createdAt":"2025-05-01T23:10:07.948Z","updatedAt":"2025-05-01T23:10:07.948Z"},{"id":"ghpuX9kkAqpLyIVR","name":"n8n-ai","createdAt":"2025-05-01T23:10:38.373Z","updatedAt":"2025-05-01T23:10:38.373Z"},{"id":"tSHEttl48VrqMYiV","name":"supabase","createdAt":"2025-05-01T23:10:16.583Z","updatedAt":"2025-05-01T23:10:16.583Z"}],"nodes":[{"id":"0213dfab-a1b2-42c9-9ab1-8a0f1de4c4c0","name":"Google Gemini Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[480,40],"parameters":{"options":{},"modelName":"models/gemini-2.5-flash-preview-04-17"},"credentials":{"googlePalmApi":{"id":"QuysglXiB421WI90","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"9c166f83-8ea4-4dc7-8ea2-92ec186c9f32","name":"OpenWeatherMap","type":"n8n-nodes-base.openWeatherMapTool","position":[740,-100],"parameters":{"cityName":"={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('City', ``, 'string') }}"},"credentials":{"openWeatherMapApi":{"id":"MCzSGdWHBJE7l1aN","name":"OpenWeatherMap account"}},"typeVersion":1},{"id":"aa0abeff-b5e9-497b-9d9c-8f79721a5c11","name":"AI Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[480,-320],"parameters":{"text":"={{ $json.message.text }}","options":{"systemMessage":"=4. If the user sends you a message starting with / sign, it means this is a Telegram bot command. For example, all users send /start command as their first message. Try to figure out what these commands mean and reply accodringly.\nUser can only send pdf files and text messages and let them know that this type is not supported if it was not a PDF file or text.\nAt first let them know that they can ask questions about sent PDF files you can use your own capabilities as well. \nGenerate a detailed, well-structured response ,\nFormat the response strictly using Telegram's supported HTML syntax. Use tags like <b>, <i>, <u>, <s>, <span class=\"tg-spoiler\">, <code>, <pre> (with optional <code class=\"language-...\"> inside), <a href=\"...\">, and <blockquote> where appropriate.\n\nStructure the content logically using paragraphs and distinct sections. **Be mindful that this text might need to be split into multiple messages due to character limits (Telegram's limit is around 4096 characters per message). Try to make sections or paragraphs relatively self-contained where possible to facilitate splitting.**\n\n**Ensure all <, >, and & symbols within the *text content* (i.e., not part of an HTML tag or entity) are replaced with the corresponding HTML entities: < with &lt;, > with &gt;, and & with &amp;.**\n\nMaintain proper nesting of HTML tags according to Telegram's rules. While the final splitting will be handled by a script, aim for a structure that is easy to break into logical parts without leaving tags improperly open mid-message."},"promptType":"define","hasOutputParser":true},"typeVersion":1.9},{"id":"72b85aff-4fe7-4705-a07c-463f381cb806","name":"Telegram Trigger","type":"n8n-nodes-base.telegramTrigger","position":[-20,100],"webhookId":"d4f286b2-8094-40e3-aeb2-813eb1895ecf","parameters":{"updates":["message"],"additionalFields":{}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2},{"id":"ea716dba-2856-40a8-ad73-86132f52dda8","name":"Telegram","type":"n8n-nodes-base.telegram","onError":"continueErrorOutput","position":[1540,-320],"webhookId":"137d8d2f-a941-4803-8646-8932525360c3","parameters":{"text":"={{ $json.text }}","chatId":"={{ $json.chatId }}","additionalFields":{"parse_mode":"HTML","appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2,"alwaysOutputData":true},{"id":"59a22620-0d26-4e19-940a-5c07efccbdfa","name":"Think","type":"@n8n/n8n-nodes-langchain.toolThink","position":[640,-100],"parameters":{},"typeVersion":1},{"id":"7bb66887-c9c6-4057-bbc0-306d1e20ea12","name":"Embeddings Google Gemini","type":"@n8n/n8n-nodes-langchain.embeddingsGoogleGemini","position":[840,340],"parameters":{"modelName":"models/text-embedding-004"},"credentials":{"googlePalmApi":{"id":"QuysglXiB421WI90","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"668db8fd-3d5f-433a-8ccb-4bea237107ce","name":"Default Data Loader","type":"@n8n/n8n-nodes-langchain.documentDefaultDataLoader","position":[1220,460],"parameters":{"options":{}},"typeVersion":1},{"id":"d1495354-bfc0-4ef1-9102-dc3577580d5b","name":"Recursive Character Text Splitter","type":"@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter","position":[1440,620],"parameters":{"options":{}},"typeVersion":1},{"id":"6a8e5ce6-4c52-4ca0-962f-045ea42dac7c","name":"Extract from File","type":"n8n-nodes-base.extractFromFile","position":[1020,480],"parameters":{"options":{},"operation":"pdf"},"typeVersion":1,"alwaysOutputData":true},{"id":"3f4a9da9-0364-4861-a6f3-33b1d5c501e0","name":"Answer questions with a vector store","type":"@n8n/n8n-nodes-langchain.toolVectorStore","position":[860,-60],"parameters":{"description":"Use this data if the user's question appears to reference an uploaded file, document content, or specific information that might be stored in prior user documents. If not relevant, ignore this source."},"typeVersion":1.1},{"id":"933a93c7-9401-4bac-9b9c-395866b46d61","name":"Supabase Vector Store","type":"@n8n/n8n-nodes-langchain.vectorStoreSupabase","position":[760,80],"parameters":{"options":{"queryName":"match_documents"},"tableName":{"__rl":true,"mode":"list","value":"user_knowledge_base","cachedResultName":"user_knowledge_base"}},"credentials":{"supabaseApi":{"id":"jq6dt73fwyUImYqH","name":"Supabase account"}},"typeVersion":1.1},{"id":"5f37c202-a1ca-4ee0-9de0-267349adffbd","name":"Sticky Note10","type":"n8n-nodes-base.stickyNote","position":[360,200],"parameters":{"color":5,"width":1625,"height":779,"content":"✅ Scenario 2 – Document Upload and Embedding\n\nFlow for downloading a document sent via Telegram, extracting its text, generating embeddings, and inserting them into Supabase Vector Store."},"typeVersion":1},{"id":"6e9c1070-90bc-4ab7-a8a0-62461bede708","name":"Sticky Note11","type":"n8n-nodes-base.stickyNote","position":[360,-420],"parameters":{"color":5,"width":1625,"height":599,"content":"✅ Scenario 1 – Chatbot Interaction\n\nFlow for handling user messages sent to the bot. Includes accessing weather data, answering questions based on user-uploaded documents, and running code using a code execution tool."},"typeVersion":1},{"id":"3b211a14-6813-459f-8d23-b40fc0eb4bd6","name":"Telegram - Embedding Complete","type":"n8n-nodes-base.telegram","position":[1760,320],"webhookId":"4eaead72-f9a7-49a3-95ca-b3bc8f6b9a95","parameters":{"text":"=✅ Document saved!\nFeel free to start asking questions about it.","chatId":"={{ $('Command Router').item.json.message.chat.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2},{"id":"05703266-aaed-491d-87a6-ed7f96a9c49a","name":"Supabase - Save Embeddings","type":"@n8n/n8n-nodes-langchain.vectorStoreSupabase","position":[1200,320],"parameters":{"mode":"insert","options":{},"tableName":{"__rl":true,"mode":"list","value":"user_knowledge_base","cachedResultName":"user_knowledge_base"}},"credentials":{"supabaseApi":{"id":"jq6dt73fwyUImYqH","name":"Supabase account"}},"typeVersion":1.1,"alwaysOutputData":false},{"id":"3b7db0e6-b551-4698-921a-306e837ceffc","name":"Command Router","type":"n8n-nodes-base.switch","position":[160,100],"parameters":{"rules":{"values":[{"outputKey":"document","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"895b32db-777d-4d8e-b1d3-596cc9863d09","operator":{"type":"boolean","operation":"exists","singleValue":true},"leftValue":"={{ $json.message.document }}","rightValue":"={{ $json.message.document }}"}]},"renameOutput":true},{"outputKey":"text","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"26c12573-8e00-4832-8410-73d2d739c455","operator":{"type":"boolean","operation":"exists","singleValue":true},"leftValue":"={{ $json.message.text }}","rightValue":""}]},"renameOutput":true}]},"options":{"fallbackOutput":"extra"},"looseTypeValidation":true},"typeVersion":3.2},{"id":"fa06fc6c-3661-4065-81fc-09f93d6a4a25","name":"Telegram - Download file","type":"n8n-nodes-base.telegram","position":[600,540],"webhookId":"11b8f884-34bc-401c-8978-b28507d96e40","parameters":{"fileId":"={{ $('Telegram Trigger').item.json.message.document.file_id }}","resource":"file"},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2},{"id":"756a36aa-187d-48ca-894c-f8c9a79a4794","name":"Aggregate","type":"n8n-nodes-base.aggregate","notes":"This is used to flag the end of progress—no real aggregation.","position":[1580,320],"parameters":{"options":{},"fieldsToAggregate":{"fieldToAggregate":[{}]}},"notesInFlow":true,"typeVersion":1},{"id":"3b49f357-5d21-4710-bd32-3218d23b1bd9","name":"Fallback- No formatting","type":"n8n-nodes-base.telegram","notes":"This is used if, even after HTML formatting,g Telegram wasn't able to process the text, so we send it without formatting.","position":[1740,-260],"webhookId":"dd2182fe-0b11-4d96-9838-30d60bf8c229","parameters":{"text":"={{ $('Manual Mapping').item.json.text }}","chatId":"={{ $('Manual Mapping').item.json.chatId }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"notesInFlow":true,"typeVersion":1.2},{"id":"eafdbacb-17e5-4de6-a4e9-b986140353e5","name":"Split Out","type":"n8n-nodes-base.splitOut","position":[1120,-320],"parameters":{"options":{},"fieldToSplitOut":"output"},"typeVersion":1},{"id":"538be3ed-4bd6-4295-ac11-e4d46b943f5a","name":"Simple Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[540,-100],"parameters":{"sessionKey":"={{ $('Telegram Trigger').item.json.message.from.id }}","sessionIdType":"customKey"},"typeVersion":1.3},{"id":"0afca77d-0e08-4f04-a6d3-b107c1dd54f9","name":"Handle formatting and split","type":"n8n-nodes-base.code","notes":"This is used to prevent Markdown issues in Telegram while sending messages.","position":[900,-320],"parameters":{"language":"python","pythonCode":"import re\nimport html\n\ngemini_output_text = _('AI Agent').first().json.output;\n# Regex to match any HTML tag <...>\nHTML_TAG_PATTERN = re.compile(r'(<[^>]*?>)', re.IGNORECASE)\n\n# List of UNSUPPORTED Telegram HTML tag names\nUNSUPPORTED_TAG_NAMES = [\n    'p', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol',\n    'table', 'thead', 'tbody', 'tr', 'td', 'th', 'div', 'br', 'font',\n    'span', # Span is unsupported *unless* it has the specific class\n    'a'     # A is unsupported *unless* it has the href attribute\n    # Add more unsupported tags if you encounter them\n]\n\n# Regex to match unsupported opening or closing tags based on the names list\n# This pattern is simplified and might misinterpret complex attributes\nUNSUPPORTED_TAG_PATTERN = re.compile(r'<\\/?(' + '|'.join(UNSUPPORTED_TAG_NAMES) + r')\\b[^>]*?>', re.IGNORECASE)\n\n# Regex to match a span tag *without* the class=\"tg-spoiler\" attribute\n# This tries to capture the tag and its content to remove both\nUNSUPPORTED_SPAN_FULL_PATTERN = re.compile(r'<span(?! class=\"tg-spoiler\"\\b)[^>]*?>.*?<\\/span>', re.IGNORECASE | re.DOTALL) # DOTALL allows . to match newlines\n\n# Regex to match an a tag *without* an href attribute\n# This tries to capture the tag and its content to remove both\nUNSUPPORTED_A_FULL_PATTERN = re.compile(r'<a(?![^>]*href=)[^>]*?>.*?<\\/a>', re.IGNORECASE | re.DOTALL)\n\n\n# --- Cleaning Function (Regex Only) ---\n\ndef unescape_common_html_entities(text):\n    \"\"\"\n    Unescapes a limited set of common HTML entities in text.\n    Does NOT use html.unescape for maximum compatibility with \"no external library\" rule.\n    \"\"\"\n    # Order matters: &amp; must be replaced first!\n    text = text.replace('&amp;', '&')\n    text = text.replace('&lt;', '<')\n    text = text.replace('&gt;', '>')\n    text = text.replace('&quot;', '\"')\n    text = text.replace('&apos;', \"'\")\n    # Add more common entities here if needed, e.g., text = text.replace('&nbsp;', ' ')\n    return text\n\n\ndef clean_html_regex_only(html_string):\n    \"\"\"\n    Cleans HTML string using regex: removes unsupported tags and escapes text content.\n    Handles &apos; and other basic entities.\n    WARNING: This is a regex-based approach and is NOT as robust as using an HTML parser.\n    It may fail on complex or malformed HTML.\n\n    Args:\n        html_string (str): The input HTML string.\n\n    Returns:\n        str: The cleaned HTML string.\n    \"\"\"\n    # 1. Remove unsupported tags and their content where specific attributes are missing\n    # Process specific full patterns first\n    cleaned_text = UNSUPPORTED_SPAN_FULL_PATTERN.sub('', html_string)\n    cleaned_text = UNSUPPORTED_A_FULL_PATTERN.sub('', cleaned_text)\n\n    # 2. Remove remaining unsupported opening/closing tags, leaving content behind\n    cleaned_text = UNSUPPORTED_TAG_PATTERN.sub('', cleaned_text)\n\n    # 3. Split the remaining string into tags and text segments\n    # This pattern captures the tags themselves so we can differentiate them from text\n    parts = HTML_TAG_PATTERN.split(cleaned_text)\n\n    cleaned_parts = []\n    for part in parts:\n        if not part:\n            continue\n\n        if HTML_TAG_PATTERN.fullmatch(part):\n            # If the part is a tag (matches the full tag pattern)\n            # We assume at this point it's a supported tag due to previous removal steps.\n            # Keep the tag as is.\n            cleaned_parts.append(part)\n        else:\n            # If the part is text content\n            # 1. Unescape common HTML entities (like &apos;) that might be in the text\n            unescaped_text = unescape_common_html_entities(part)\n\n            # 2. Escape the literal characters <, >, & that are *in* the text content\n            # This ensures only the characters themselves are escaped, not entities.\n            # Need to escape & first to avoid issues with '&amp;' if it resulted from unescaping or was original.\n            re_escaped_text = unescaped_text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')\n\n            cleaned_parts.append(re_escaped_text)\n\n    # Join the processed parts back into a single string\n    return \"\".join(cleaned_parts)\n\n# --- Splitting Logic ---\nSPLIT_PATTERN_REGEX_ONLY = re.compile(r'(<\\/blockquote>|<\\/pre>|\\n\\n|\\s{2,}|(?<=[.!?])\\s+|<[a-z]+[^>]*?>|<\\/[a-z]+>)', flags=re.IGNORECASE)\n\n\ndef split_telegram_message_regex_only(text, max_length=4096):\n    \"\"\"\n    Splits text into multiple messages based on character count and basic patterns.\n    Operates on text already cleaned by clean_html_regex_only.\n    Does NOT guarantee HTML tag integrity across splits due to lack of parsing.\n\n    Args:\n        text (str): The input text (preferably cleaned by clean_html_regex_only).\n        max_length (int): The maximum length for each message part.\n\n    Returns:\n        list: A list of strings, where each string is a message part.\n    \"\"\"\n    if len(text) <= max_length:\n        return [text]\n\n    messages = []\n    current_chunk = \"\"\n\n    # Split by the defined pattern\n    parts = SPLIT_PATTERN_REGEX_ONLY.split(text)\n\n    for part in parts:\n        # Handle parts that are None (can happen with split) or just short whitespace\n        if part is None or (not part.strip() and len(part) < 2 and part != '\\n\\n'):\n             if part is not None and len(part) > 0: # Keep meaningful whitespace splits like \\n\\n\n                  if len(current_chunk) + len(part) <= max_length:\n                       current_chunk += part\n                  else:\n                       # Split happens within meaningful whitespace, finalize chunk\n                       if current_chunk.strip(): # Only add if chunk has content\n                            messages.append(current_chunk.strip())\n                       current_chunk = part # Start new chunk with the whitespace\n             continue # Skip to next part\n\n\n        # Check if adding the current part exceeds the max length\n        if len(current_chunk) + len(part) > max_length:\n            # If the current chunk is empty or only whitespace after stripping,\n            # it means the 'part' itself is too long to fit in a new chunk.\n            if not current_chunk.strip():\n                # Handle very long individual parts (e.g., a huge code block line, a very long word, a single huge tag)\n                # Hard split the long part. WARNING: This can break tags, words, or escape sequences.\n                while len(part) > max_length:\n                    messages.append(part[:max_length])\n                    part = part[max_length:]\n                if part.strip():\n                    current_chunk = part # Remaining part starts a new chunk\n                else:\n                     current_chunk = \"\" # If remainder is just whitespace, clear\n            else:\n                # The current part makes the chunk too long, finalize the current chunk\n                messages.append(current_chunk.strip())\n                # Start a new chunk with the current part\n                current_chunk = part # Keep original part for the new chunk\n\n        else:\n            # Add the current part to the chunk\n            current_chunk += part\n\n    # Add the last chunk\n    if current_chunk.strip(): # Only add if the final chunk has content\n        messages.append(current_chunk.strip())\n\n    # Clean up any empty messages that might have been created\n    messages = [msg for msg in messages if msg.strip()]\n\n    return messages\n  \ncleaned_html_regex = clean_html_regex_only(gemini_output_text)\nmessage_parts_regex = split_telegram_message_regex_only(cleaned_html_regex)\n\nreturn dict({'output': message_parts_regex })"},"typeVersion":2},{"id":"dbea9e13-6ad4-4eb3-8da1-9db9e2116283","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[2000,-420],"parameters":{"width":1960,"height":3520,"content":"# 🤖 Telegram AI Assistant for Your Documents (n8n + Supabase + Gemini)\n\nThis project transforms a standard **Telegram bot** into your dedicated AI assistant – designed to understand and answer questions based on **your own documents**. It seamlessly integrates the power of **Google Gemini** for advanced language capabilities and **Supabase's vector database** for efficient, intelligent document retrieval. Built entirely within the no-code platform **n8n**, it allows you to deploy a sophisticated document chatbot without writing a single line of code.\n\nSimply upload any PDF document to the bot, and instantly gain the ability to chat with it, querying its contents as if it were a knowledgeable expert on your uploaded files.\n\n---\n## 📹 Watch the Bot in Action\n\n[![𝗨𝗻𝗹𝗲𝗮𝘀𝗵𝗶𝗻𝗴 𝗔𝗜 𝗼𝗻 𝗠𝘆 𝗕𝗼𝗼𝗸𝘀𝗵𝗲𝗹𝗳: 𝗙𝗹𝗼𝘄 𝗣𝗿𝗼𝗴𝗿𝗮𝗺𝗺𝗶𝗻𝗴 𝗣𝗼𝘄𝗲𝗿𝘀 𝗮 𝗡𝗲𝘅𝘁-𝗟𝗲𝘃𝗲𝗹 𝗧𝗲𝗹𝗲𝗴𝗿𝗮𝗺 𝗕𝗼𝘁🤖](https://img.youtube.com/vi/r_KGyJApy5M/maxresdefault.jpg)](https://www.youtube.com/watch?v=r_KGyJApy5M)\n\n**▶️ Click the image above to watch a live demo on YouTube.** \n\nThis video provides a live demonstration of the bot's core features and how it interacts. See a quick walkthrough of its capabilities and user flow.\n\n---\n\n## ✨ Ignite Your Workflow: Use Cases\n\nThis project empowers two core interactions:\n\n### 1. Conversational AI Interface (User Inquiry → Telegram Bot → Intelligent Answers)\n- Users pose questions directly to the Telegram bot.\n- The bot generates relevant, informative answers using the cutting-edge capabilities of the Google Gemini LLM.\n- Leveraging a powerful vector search mechanism, it can pull specific, contextual information from previously uploaded documents to provide highly relevant and informed responses.\n- (Optional) Augment answers with real-time data, like current **weather information**.\n\n### 2. Effortless Document Integration (User Upload PDF → Processing → Searchable Knowledge)\n- Users upload a PDF document directly to the bot.\n- The workflow automatically parses the document content, converts it into numerical representations called embeddings using Gemini's embedding models.\n- These embeddings, alongside the document's text content, are then securely stored in a dedicated **Supabase vector table**, creating a searchable knowledge base.\n- Immediately after successful processing, the document becomes part of the bot's memory, enabling users to ask questions about its contents via the standard chat interface.\n\n---\n## 🧠 Core Intelligence Features\n\n- ✅ **Pure No-Code**: Developed and managed entirely within the intuitive [n8n](https://n8n.io) automation platform.\n- 📄 **Seamless PDF Integration**: Easily upload and process PDF documents to expand the bot's knowledge.\n- 🧠 **Powered by Google Gemini**: Utilizes Gemini for both generating document embeddings and formulating intelligent conversational responses.\n- 🗂 **Vector Database Memory (Supabase)**: Employs **Supabase as a robust vector database** for storing and efficiently searching document embeddings, providing the bot with long-term memory about your content.\n- **⚡️ Rapid & Private Retrieval**: The vector search allows for swift identification and retrieval of the most relevant document snippets based on the user's query. This approach enhances response speed and significantly improves data privacy, as **the original document content remains securely stored in your Supabase instance, and only the user's query and the retrieved relevant chunks are sent to the LLM for generating a response.**\n- 🧹 **Intelligent HTML Post-processing**: Cleans the LLM's responses by removing HTML tags not supported by Telegram while preserving essential formatting and correctly escaping special characters in the text content.\n- 📤 **Adaptive Message Chunking**: Splits lengthy AI-generated answers into multiple messages that adhere to Telegram's 4096-character limit, ensuring the full response is delivered cleanly.\n- 🌦️ **Dynamic Weather Data**: (Optional) Integrates with OpenWeatherMap to provide current weather information upon request.\n- **📝 Note on Usage**: This workflow is designed primarily for **personal, single-user** scenarios. It processes each message independently and **does not include multi-user session management**, making it unsuitable for public deployment where different users require separate conversational contexts. For a session-based Telegram bot implemented in Python, you may refer to this project, which is a multi-model telegram bot: [https://github.com/mohamadghaffari/gemini-tel-bot](https://github.com/mohamadghaffari/gemini-tel-bot).\n---\n\n## 🛠 Getting Started: Setup\n\n### 1. Deploy the Workflow in n8n\n\n- Click the \"Use this workflow\" button on the n8n template page.\n- This will open the workflow directly in your n8n instance, ready for configuration.\n\n\n### 2. Connect Your Services: Configure Credentials\n\nCreate API credentials for the following services within your n8n instance:\n\n| Service          | Purpose                          |\n|------------------|------------------------------------|\n| Telegram API     | Receiving user messages & sending replies |\n| Google Gemini    | Generating embeddings & LLM responses |\n| Supabase         | Storing & searching document vectors |\n| OpenWeatherMap   | (Optional) Fetching weather data    |\n\n### 3. Prepare Your Supabase Knowledge Base\n\nSet up a vector-enabled table in your Supabase project to store your document embeddings. Execute the following SQL commands in your Supabase SQL Editor:\n\n``` sql\n-- Enable the pgvector extension to work with embedding vectors\ncreate extension vector;\n\n-- Create a table to store your documents and their embeddings\ncreate table user_knowledge_base (\n  id bigserial primary key,\n  content text, -- Stores the text chunk from the document\n  metadata jsonb, -- Stores document information (e.g., filename, page number)\n  embedding vector(768) -- Stores the vector representation (embedding) generated by Gemini. Adjust dimension if using a different model.\n);\n\n-- Create a function to perform vector similarity search against your documents\ncreate function match_documents (\n  query_embedding vector(768),\n  match_count int default null,\n  filter jsonb DEFAULT '{}'\n) returns table (\n  id bigint,\n  content text,\n  metadata jsonb,\n  similarity float\n)\nlanguage plpgsql\nas $$\n#variable_conflict use_column\nbegin\n  return query\n  select\n    id,\n    content,\n    metadata,\n    -- Calculate cosine similarity: 1 - cosine distance (using the '<=>' operator provided by pgvector)\n    1 - (user_knowledge_base.embedding <=> query_embedding) as similarity\n  from user_knowledge_base\n  where metadata @> filter -- Optional: filter results based on metadata\n  order by user_knowledge_base.embedding <=> query_embedding -- Order by similarity (closest first)\n  limit match_count; -- Limit the number of results\nend;\n$$;\n````\n\nThis sets up the necessary table and a function to perform vector similarity searches, allowing you to find document chunks most similar to a user's query.\n-----\n\n## 📚 Integrated Technologies\n\nThis project brings together powerful tools:\n\n  - [n8n](https://n8n.io) – The central hub for workflow automation and integration.\n  - [Telegram Bot API](https://core.telegram.org/bots/api) – The communication layer for user interaction.\n  - [Supabase](https://supabase.com/) + [pgvector Extension](https://www.google.com/search?q=https://supabase.com/docs/guides/ai/vector-embeddings) – Provides a scalable database with powerful vector search capabilities.\n  - [Google Gemini API](https://ai.google.dev/) – The intelligence engine for embeddings and text generation.\n  - [OpenWeatherMap API](https://openweathermap.org/api) – (Optional) For adding real-time weather features.\n\n-----\n"},"typeVersion":1},{"id":"965ba2bd-747d-4718-a76e-9f7d685dcea4","name":"Manual Mapping","type":"n8n-nodes-base.set","position":[1320,-320],"parameters":{"options":{},"assignments":{"assignments":[{"id":"cdeb5bf1-c91c-44ae-bebd-ab3f4ba2561a","name":"text","type":"string","value":"={{ $json.output }}"},{"id":"7cd7d120-96fa-4539-b343-25bc9b75abb4","name":"chatId","type":"number","value":"={{ $('Command Router').item.json.message.from.id }}"}]}},"typeVersion":3.4},{"id":"c6a315f1-6f0b-4127-b377-b7b12975929f","name":"Unsupported message","type":"n8n-nodes-base.telegram","position":[500,760],"webhookId":"52f3456a-06ef-4799-b245-0293213dcc4b","parameters":{"text":"Unsupported command or file. 😓 Please upload a valid PDF document or ask your question regarding your files.","chatId":"={{ $('Command Router').item.json.message.chat.id }}","additionalFields":{"appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2},{"id":"375bd185-3836-4f25-8708-d6dcd79b2675","name":"Send processing document message","type":"n8n-nodes-base.telegram","position":[920,720],"webhookId":"32ade357-f14b-4d10-91f2-02c8aa6e198e","parameters":{"text":"=<b>Processing document...</b>\n<b>Please wait...⏳</b>","chatId":"={{ $('Command Router').item.json.message.chat.id }}","additionalFields":{"parse_mode":"HTML","appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2},{"id":"d01f8b15-e495-46cf-bfdf-20b4399c23d7","name":"Send embedding Started message","type":"n8n-nodes-base.telegram","position":[1220,660],"webhookId":"32ade357-f14b-4d10-91f2-02c8aa6e198e","parameters":{"text":"=<b>Document processed ✅ </b> \n<b>Num of pages:</b> {{ $json.numpages }} \n<b>Creator:</b> {{ $json.info.Creator }}\n<b>Title:</b> {{ $json.info.Title }} \n<b>Version:</b> {{ $json.version }}","chatId":"={{ $('Command Router').item.json.message.chat.id }}","additionalFields":{"parse_mode":"HTML","appendAttribution":false}},"credentials":{"telegramApi":{"id":"jOxapcl3g1n1HrCE","name":"Telegram account"}},"typeVersion":1.2}],"active":true,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"749ec7d0-e135-478a-b02e-9241dbf4ab68","connections":{"Think":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"AI Agent":{"main":[[{"node":"Handle formatting and split","type":"main","index":0}]]},"Telegram":{"main":[[],[{"node":"Fallback- No formatting","type":"main","index":0}]]},"Aggregate":{"main":[[{"node":"Telegram - Embedding Complete","type":"main","index":0}]]},"Split Out":{"main":[[{"node":"Manual Mapping","type":"main","index":0}]]},"Simple Memory":{"ai_memory":[[{"node":"AI Agent","type":"ai_memory","index":0}]]},"Command Router":{"main":[[{"node":"Telegram - Download file","type":"main","index":0}],[{"node":"AI Agent","type":"main","index":0}],[{"node":"Unsupported message","type":"main","index":0}]]},"Manual Mapping":{"main":[[{"node":"Telegram","type":"main","index":0}]]},"OpenWeatherMap":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"Telegram Trigger":{"main":[[{"node":"Command Router","type":"main","index":0}]]},"Extract from File":{"main":[[{"node":"Supabase - Save Embeddings","type":"main","index":0},{"node":"Send embedding Started message","type":"main","index":0}]]},"Default Data Loader":{"ai_document":[[{"node":"Supabase - Save Embeddings","type":"ai_document","index":0}]]},"Supabase Vector Store":{"ai_vectorStore":[[{"node":"Answer questions with a vector store","type":"ai_vectorStore","index":0}]]},"Embeddings Google Gemini":{"ai_embedding":[[{"node":"Supabase - Save Embeddings","type":"ai_embedding","index":0},{"node":"Supabase Vector Store","type":"ai_embedding","index":0}]]},"Google Gemini Chat Model":{"ai_languageModel":[[{"node":"AI Agent","type":"ai_languageModel","index":0},{"node":"Answer questions with a vector store","type":"ai_languageModel","index":0}]]},"Telegram - Download file":{"main":[[{"node":"Extract from File","type":"main","index":0},{"node":"Send processing document message","type":"main","index":0}]]},"Supabase - Save Embeddings":{"main":[[{"node":"Aggregate","type":"main","index":0}]]},"Handle formatting and split":{"main":[[{"node":"Split Out","type":"main","index":0}]]},"Send embedding Started message":{"main":[[]]},"Recursive Character Text Splitter":{"ai_textSplitter":[[{"node":"Default Data Loader","type":"ai_textSplitter","index":0}]]},"Answer questions with a vector store":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]}}}


name: AI-Powered Restaurant Order Chatbot with GPT-4o for POS Integration
author_name: Kumar Shivam
author_username: shivam840708
categories: []
created_at: 2025-05-08T15:20:19.166Z
updated_at: 2025-05-08T15:20:19.166Z
url: https://n8n.io/workflows/3942
scraped_at: 2025-07-06 21:16:30
description:
  This workflow automates the restaurant POS (Point of Sale) data management process, facilitating seamless order handling, customer tracking, inventory management, and sales reporting. It retrieves order details, processes payment information, updates inventory, and generates real-time sales reports, all integrated into a centralized system that improves restaurant operations.

  The workflow integrates various systems, including a POS terminal to gather order data, payment gateways to process transactions, inventory management tools to update stock, and reporting tools like Google Sheets or an internal database for generating sales and performance reports.

  Who Needs Restaurant POS Automation?
  This POS automation workflow is ideal for restaurant owners, managers, and staff looking to streamline their operations:

  Restaurant Owners – Automate order processing, track sales, and monitor inventory to ensure smooth operations.

  Managers – Access real-time sales data and performance reports to make informed decisions.

  Staff – Reduce manual work, focusing on providing better customer service while the system handles orders and payments.

  Inventory Teams – Automatically update inventory levels based on orders and ingredient usage.

  If you need a reliable and automated POS solution to manage restaurant orders, payments, inventory, and reporting, this workflow minimizes human error, boosts efficiency, and saves valuable time.

  Why Use This Workflow?

  End-to-End Automation – Automates everything from order input to inventory updates and sales reporting.

  Seamless Integration – Connects POS, payment systems, inventory management, and reporting tools for smooth data flow.(if needed)

  Real-Time Data – Provides up-to-the-minute reports on sales, stock levels, and order statuses.

  Scalable & Efficient – Supports multiple locations, multiple users, and high order volumes.

  Step-by-Step: How This Workflow Manages POS Data

  Collect Orders – Retrieves order details from the POS system, including customer information, ordered items, and payment details.

  Update Inventory – Decreases inventory levels based on sold items, ensuring stock counts are always accurate.

  Generate Reports – Compiles sales, revenue, and inventory data into real-time reports and stores them in Google Sheets or an internal database.

  Track Customer Data – Keeps a log of customer details and order history for better service and marketing insights.

  Customization: Tailor to Your Needs

  Multiple POS Systems – Adapt the workflow to work with different POS systems or terminals based on your restaurant setup.

  Custom Reporting – Modify the reporting format or include specific sales metrics (e.g., daily totals, best-selling items, employee performance).

  Inventory Management – Adjust inventory updates to include alerts when stock reaches critical levels or needs reordering.

  Integration with Accounting Software – Connect with platforms like QuickBooks for automated financial tracking.

  🔑 Prerequisites

  POS System Integration – Ensure the POS system can export order data in a compatible format.

  Payment Gateway API – Set up the necessary API keys for payment processing (e.g., Stripe, PayPal).

  Inventory Management Tools – Use inventory software or databases that can automatically update stock levels.

  Reporting Tools – Use Google Sheets or an internal database to store and generate sales and inventory reports.

  🚀 Installation & Setup

  Configure Credentials

  Set up API credentials for payment gateways and inventory management tools.

  Import Workflow

  Import the workflow into your automation platform (e.g., n8n, Zapier).

  Link POS system, payment gateway, and inventory management systems.

  Test & Run

  Process a test order to ensure that data flows correctly through each step.

  Verify that inventory updates and reports are generated as expected.

  ⚠ Important

  Data Privacy – Ensure compliance with data protection regulations (e.g., GDPR, PCI DSS) when handling customer payment and order data.

  System Downtime – Monitor system performance to ensure that the workflow runs without disruptions during peak hours.

  Summary
  This restaurant POS automation workflow integrates order management, payment processing, inventory updates, and real-time reporting, enabling efficient restaurant operations. Whether you are running a single location or a chain of restaurants, this solution streamlines daily tasks, reduces errors, and provides valuable insights, saving time and improving customer satisfaction. 🚀

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.memoryBufferWindow","@n8n/n8n-nodes-langchain.toolWorkflow","@n8n/n8n-nodes-langchain.informationExtractor"]

workflow_json:
  {"meta":{"instanceId":"d73282515b90623d4a8783919a2d772c706425d649e1512792f37ac51e07e4a8"},"nodes":[{"id":"62b3c7cb-1993-44f1-8b86-38a34ca1d029","name":"Information Extractor","type":"@n8n/n8n-nodes-langchain.informationExtractor","position":[-200,500],"parameters":{"text":"={{ $json.query }}","options":{},"schemaType":"fromJson","jsonSchemaExample":"{\n  \"name\": \"Information Extractor\",\n  \"type\": \"n8n-nodes-base.informationExtractor\",\n  \"parameters\": {\n    \"extract\": [\n      {\n        \"name\": \"items\",\n        \"pattern\": \"(latte|coffee|tea|cappuccino)\"\n      },\n      {\n        \"name\": \"quantity\",\n        \"pattern\": \"\\\\d+\"\n      },\n      {\n        \"name\": \"table\",\n        \"pattern\": \"table number (\\\\d+)\"\n      }\n    ]\n  }\n}\n"},"typeVersion":1},{"id":"75883f27-af58-4791-9e1a-a70b83e1cead","name":"OpenAI Chat Model1","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-180,740],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"OizdHUANhz9NIHyd","name":"OpenAi account"}},"typeVersion":1.2},{"id":"aeefdd4b-bf7d-4824-97d8-0afc356fb7d6","name":"If","type":"n8n-nodes-base.if","position":[120,540],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"8a5dda0c-a567-4305-83a3-68d6fb573dd3","operator":{"type":"array","operation":"empty","singleValue":true},"leftValue":"={{ $json.output.parameters.extract }}","rightValue":""}]}},"typeVersion":2.2},{"id":"9e3f8a1b-ccd8-4f4d-91cb-b99cc46f412f","name":"Google Sheets","type":"n8n-nodes-base.googleSheets","position":[840,420],"parameters":{"columns":{"value":{"Item":"={{ $json.item }}","Quantity":"={{ $json.quantity }}","Table No":"={{ $json.table }}","Timestamp":"={{ $now }}"},"schema":[{"id":"Timestamp","type":"string","display":true,"required":false,"displayName":"Timestamp","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Table No","type":"string","display":true,"required":false,"displayName":"Table No","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Item","type":"string","display":true,"required":false,"displayName":"Item","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Quantity","type":"string","display":true,"required":false,"displayName":"Quantity","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/16fXaxEcfnq_-oif9tp94-3uTeHSFWoSnuBPNTljuW-k/edit#gid=0","cachedResultName":"Order log"},"documentId":{"__rl":true,"mode":"url","value":"https://docs.google.com/spreadsheets/d/16fXaxEcfnq_-oif9tp94-3uTeHSFWoSnuBPNTljuW-k/edit?usp=sharing"}},"credentials":{"googleSheetsOAuth2Api":{"id":"0RSJGMBcFzxY9GkS","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"4cc1818f-1585-42e1-a111-7b55557aebcb","name":"Code","type":"n8n-nodes-base.code","position":[380,560],"parameters":{"language":"python","pythonCode":"# Input from n8n\ninput_data = items\n\n# Get the extracted list\nextract_data = input_data[0].get('json', {}).get('output', {}).get('parameters', {}).get('extract', [])\n\n# Prepare variables\norder_items = []\ntable_number = None\n\n# Separate entries by type\nitems_list = []\nquantities = []\n\n# Parse all entries\nfor entry in extract_data:\n    if entry['name'] == 'table number':\n        table_number = entry['pattern']\n    elif entry['name'] == 'item':\n        items_list.append(entry['pattern'])\n    elif entry['name'] == 'quantity':\n        quantities.append(int(entry['pattern']))\n\n# Pair items and quantities\nfor i in range(len(items_list)):\n    item_data = {\n        'item': items_list[i],\n        'quantity': quantities[i] if i < len(quantities) else None,\n        'table': table_number\n    }\n    order_items.append(item_data)\n\n# Set final output\noutput = [{'json': item} for item in order_items]\n\nreturn output"},"typeVersion":2},{"id":"a92d2745-148b-4e2a-b8f7-82d3993ff34f","name":"Loop Over Items","type":"n8n-nodes-base.splitInBatches","position":[620,500],"parameters":{"options":{}},"typeVersion":3},{"id":"aea89e6c-37a9-4859-adc8-b7e449701503","name":"Replace Me","type":"n8n-nodes-base.noOp","position":[800,660],"parameters":{},"typeVersion":1},{"id":"b31dba52-b27e-4267-be32-a7730b4d08a8","name":"No Operation, do nothing","type":"n8n-nodes-base.noOp","position":[440,400],"parameters":{},"typeVersion":1},{"id":"d7f9a381-6bc2-44d0-81ac-6e0fbe77d70a","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-260,220],"parameters":{"color":3,"width":340,"height":680,"content":"## JSON PARSER\n\n1.converts the textual data final order like\nitem name \nquantity \nand table name in a json.\n\n2.if the data doesn't include the above it returns null."},"typeVersion":1},{"id":"acc7a528-f767-4576-b08d-6fc386f57648","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[100,220],"parameters":{"color":2,"width":460,"height":680,"content":"## Refine/Split the jsons into multiple items\n\nIf the data from previous item is not null the custom code block splits the data into multiple json items in a list."},"typeVersion":1},{"id":"857a3102-f5e1-4db5-afb4-154544414701","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[580,220],"parameters":{"color":4,"width":440,"height":680,"content":"## Send each item as a record in Google sheet\n\n\n**Each item is looped over and produce a batch of 1 item and appended as row in sheet with timestamp.\n"},"typeVersion":1},{"id":"a1ff2b0f-0b48-4ea2-8121-4e2d72197ef7","name":"Triggered on Restaurant Chat workflow","type":"n8n-nodes-base.executeWorkflowTrigger","position":[-440,500],"parameters":{"inputSource":"passthrough"},"typeVersion":1.1},{"id":"8689b773-a1c4-4de4-a66e-fab8c9eb6244","name":"When chat message received","type":"@n8n/n8n-nodes-langchain.chatTrigger","position":[-140,-280],"webhookId":"d931c4a7-02f5-4359-918f-7ad3fae7b144","parameters":{"public":true,"options":{}},"typeVersion":1.1},{"id":"de310ce2-3868-4a0f-aa9b-38253e75dbda","name":"AI Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[100,-260],"parameters":{"options":{"systemMessage":"\n\nYou are a polite and efficient restaurant assistant.\n\nYour job is to take customer orders, verify the order details, correct any mistakes, and confirm the order.\n\nFollow these steps:\n\nGreeting and Asking for the Order\n\nIf the customer greets you (e.g., \"Hello\", \"Hi\", \"Good evening\"), respond with:\n\n\"Hello! How can I assist you today? What would you like to order?\"\n\nOrder Parsing and Understanding\n\nAccept orders in flexible formats, such as:\n\n\"1 latte, 2 coffee, table number 5\"\n\n\"latte 2, pepsi 1, table 3\"\n\n\"1 cappucino\"\n\n\"1 tea table no 4\"\n\nYour goal is to extract the following:\n\nItem names (e.g., latte, coffee, chocolate, tea, pepsi)\n\nQuantities (must be numeric)\n\nTable number (must be numeric)\n\nVerify and Handle Missing or Incorrect Information\n\nFor each item in the order:\n\nIf the item name is missing, respond:\n\"Sorry, the item name is missing. What would you like to order?\"\n\nIf the quantity is missing, respond:\n\"How many [item] would you like?\"\n\nIf the table number is missing, respond:\n\"Could you please provide a table number?\"\n\nIf there are spelling mistakes in the item name, suggest corrections. Example:\n\"Did you mean chocolate instead of chocolat? Please confirm.\"\n\nUse fuzzy matching to detect common variations and typos.\n\nFinal Confirmation\n\nOnce all necessary details are collected, present an order summary like this:\n\nHere’s your order summary:\n\n1 latte\n\n2 coffee\n\nTable number: 5\nShall I confirm this order?\n\nOn Confirmation: Use the Tool\n\nWhen the user confirms, use the tool ConfirmOrder to send the final confirmation message as plain text in this format:\n\nThank you for confirming! Your order will be prepared shortly. Enjoy your time with us!\n\nOrder details are following:\nitem quantity\nlatte 1\ncoffee 2\n\nAdded to table number 5\n\nEnsure numeric values (quantities and table numbers) are correctly extracted, even if they appear at the start or end. Always confirm with the user if there is any uncertainty.\n\n\n\n\n\n\n\n\n"}},"typeVersion":1.9},{"id":"9dda45ee-0a92-448c-8a7e-8daa99282cda","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[-20,20],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{"responseFormat":"text"}},"credentials":{"openAiApi":{"id":"OizdHUANhz9NIHyd","name":"OpenAi account"}},"typeVersion":1.2},{"id":"0c0189d5-8fb4-4679-b2e2-221a3e2a4c88","name":"Call n8n Workflow Tool","type":"@n8n/n8n-nodes-langchain.toolWorkflow","position":[360,20],"parameters":{"workflowId":{"__rl":true,"mode":"list","value":"wgaJ0eJQtYA8oKSC","cachedResultName":"Restaurant POS workflow"},"description":"This tool sends the text output generated by the AI Agent node to another n8n workflow for additional handling or automation.","workflowInputs":{"value":{},"schema":[],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false}},"notesInFlow":false,"typeVersion":2.2},{"id":"9292db7f-6ffc-486e-b31a-bcbd6ef7ab98","name":"Last 5 conversations Memory","type":"@n8n/n8n-nodes-langchain.memoryBufferWindow","position":[140,40],"parameters":{},"typeVersion":1.3},{"id":"2782d5b6-d33b-4c89-ac79-90bf380f0828","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[60,-380],"parameters":{"width":340,"height":300,"content":"## Restaurant Order Chat bot\n** It chats with the user and refines the order for the pos system in another workflow."},"typeVersion":1},{"id":"7c298718-e9e3-40d3-a612-94c578bd3100","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[500,-20],"parameters":{"color":5,"content":"## Call the subworkflow\nit passes the data to the subworkflow for further process\n"},"typeVersion":1}],"pinData":{},"connections":{"If":{"main":[[{"node":"No Operation, do nothing","type":"main","index":0}],[{"node":"Code","type":"main","index":0}]]},"Code":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"AI Agent":{"main":[[]]},"Replace Me":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"Loop Over Items":{"main":[[{"node":"Google Sheets","type":"main","index":0}],[{"node":"Replace Me","type":"main","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"AI Agent","type":"ai_languageModel","index":0}]]},"OpenAI Chat Model1":{"ai_languageModel":[[{"node":"Information Extractor","type":"ai_languageModel","index":0}]]},"Information Extractor":{"main":[[{"node":"If","type":"main","index":0}]]},"Call n8n Workflow Tool":{"ai_tool":[[{"node":"AI Agent","type":"ai_tool","index":0}]]},"When chat message received":{"main":[[{"node":"AI Agent","type":"main","index":0}]]},"Last 5 conversations Memory":{"ai_memory":[[{"node":"AI Agent","type":"ai_memory","index":0}]]},"Triggered on Restaurant Chat workflow":{"main":[[{"node":"Information Extractor","type":"main","index":0}]]}}}


name: Generate Logos and Images with Consistent Visual Styles using Imagen 3.0
author_name: Jimleuk
author_username: jimleuk
nodes_used: ["n8n-nodes-base.httpRequest","n8n-nodes-base.gmail","n8n-nodes-base.html"]
categories: []
created_at: 2025-05-09T08:58:19.404Z
updated_at: 2025-05-09T08:58:19.404Z
url: https://n8n.io/workflows/3954
scraped_at: 2025-07-06 21:16:30
description:
  This n8n template allows you to use AI to generate logos or images which mimic visual styles of other logos or images. The model used to generate the images is Google's Imagen 3.0.

  With this template, users will be able to automate design and marketing tasks such as creating variants of existing designs, remixing existing assets to validate different styles and explore a range of designs which would have been otherwise too expensive and time-consming previously.

  How it works
  A form trigger is used to capture the source image to reference styles from and a prompt for the target image to generate.
  The source image is passed to Gemini 2.0 to be analysed and its visual style and tone extracted as a detailed description.
  This visual style description is then combined with the user's initial target image prompt. This final prompt is given to Imagen 3.0 to generate the images.
  A quick webpage is put together with the generated images to present back to the user.
  If the user provided an email address, a copy of this HTML page will be sent.

  How to use
  Ensure the workflow is live to share the form publicly.
  The source image must be accessible to your n8n instance - either a public image of the internet or within your network.
  For best results, select a source image which has strong visual identity as these will allow the LLM to better describe it.
  For your prompt, refer to the imagen prompt guide found here: https://ai.google.dev/gemini-api/docs/image-generation#imagen-prompt-guide

  Requirements
  Gemini for LLM and Imagen model.
  Cloudinary for image CDN.
  Gmail for email sending.

  Customising this workflow
  Feel free to swap any of these out for tools and services you prefer.
  Want to fully automate? Switch the form trigger for a webhook trigger!

workflow_json:
  {"meta":{"instanceId":"408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9","templateCredsSetupCompleted":true},"nodes":[{"id":"ed993205-977a-43cd-8d0b-4faef216d766","name":"Split Out","type":"n8n-nodes-base.splitOut","position":[360,-120],"parameters":{"options":{},"fieldToSplitOut":"predictions"},"typeVersion":1},{"id":"67f2bb16-ee38-4bc8-9cc7-50a44614cc3b","name":"Imagen 3.0","type":"n8n-nodes-base.httpRequest","position":[140,-120],"parameters":{"url":"https://generativelanguage.googleapis.com/v1beta/models/imagen-3.0-generate-002:predict","method":"POST","options":{},"jsonBody":"={{\n{\n  \"instances\": [\n    {\n      \"prompt\": [\n        $json.candidates[0].content.parts[0].text,\n       `Generate the following image: ${$('Variables').first().json.targetPrompt}`\n      ].join(' ')\n    }\n  ],\n  \"parameters\": {\n    \"sampleCount\": $('Variables').first().json.numberSamples.toNumber()\n  }\n}\n}}","sendBody":true,"specifyBody":"json","authentication":"predefinedCredentialType","nodeCredentialType":"googlePalmApi"},"credentials":{"googlePalmApi":{"id":"dSxo6ns5wn658r8N","name":"Google Gemini(PaLM) Api account"}},"typeVersion":4.2},{"id":"b1730c97-8c5c-48a5-90cf-7940f6c9e2d0","name":"Variables","type":"n8n-nodes-base.set","position":[-940,-120],"parameters":{"options":{},"assignments":{"assignments":[{"id":"7616f991-76d0-4dd0-9385-f08ed14e8dfa","name":"sourceStyleUrl","type":"string","value":"={{ $json.SourceImage }}"},{"id":"126cb06e-4d69-4163-ba76-c694103bf5bb","name":"targetPrompt","type":"string","value":"={{ $json.TargetPrompt }}"},{"id":"055d247b-586d-4bb8-a319-262c241df48c","name":"numberSamples","type":"string","value":"={{\n(function(numSamples) {\n  if (!numSamples) return 1;\n  if (numSamples < 0) return 1;\n  if (numSamples > 4) return 4;\n  return numSamples;\n}($json['Number of Images']))\n}}"},{"id":"77a27e0e-24f4-4358-8cdc-84552be6c0b5","name":"email","type":"string","value":"={{ $json['Your Email (Optional)'] }}"}]}},"typeVersion":3.4},{"id":"5c26062c-74cf-4140-8dcb-8688c8daec67","name":"Download Image","type":"n8n-nodes-base.httpRequest","position":[-620,-120],"parameters":{"url":"={{ $json.sourceStyleUrl }}","options":{}},"typeVersion":4.2},{"id":"20744adb-cdf5-4558-b6e6-4206d7c1f356","name":"On form submission","type":"n8n-nodes-base.formTrigger","position":[-1400,-120],"webhookId":"51f74db1-ffb4-491f-83b0-a44a7124be12","parameters":{"options":{"path":"style-copy-with-imagen3","ignoreBots":true,"buttonLabel":"Generate!","appendAttribution":true},"formTitle":"Style Copy with Imagen 3.0","formFields":{"values":[{"fieldLabel":"SourceImage","placeholder":"The image URL to copy the style from","requiredField":true},{"fieldLabel":"TargetPrompt","placeholder":"The new image to generate","requiredField":true},{"fieldType":"number","fieldLabel":"Number of Images","placeholder":"Default 1. Max. 4 images"},{"fieldType":"email","fieldLabel":"Your Email (Optional)","placeholder":"The results will be sent to this email"}]},"responseMode":"lastNode","formDescription":"Use this form to generate an image using another image as a style reference."},"typeVersion":2.2},{"id":"917db247-1be3-4814-a96d-145957aa5db3","name":"Form Validation","type":"n8n-nodes-base.if","position":[-1180,-120],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"1b440b81-06c9-4133-bfd2-8ec07c7c3734","operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.SourceImage.isUrl() }}","rightValue":""}]}},"typeVersion":2.2},{"id":"65a8b617-318c-429a-b37e-45ead00dbb7e","name":"Retry Form","type":"n8n-nodes-base.form","position":[-940,60],"webhookId":"0b4c88ed-d28b-4df4-abe6-4579e17c672d","parameters":{"options":{"formTitle":"Retry Submission","buttonLabel":"Generate!","formDescription":"Please enter a URL for the source image."},"formFields":{"values":[{"fieldLabel":"SourceImage","placeholder":"The image URL to copy the style from","requiredField":true},{"fieldLabel":"TargetPrompt","placeholder":"The new image to generate","requiredField":true},{"fieldType":"number","fieldLabel":"Number of Images","placeholder":"Max. 4 images"},{"fieldLabel":"Your Email (Optional)","placeholder":"The results will be sent to this email"}]},"limitWaitTime":true},"typeVersion":1},{"id":"8a0e8dae-9f6f-488e-9977-fd89e885f30e","name":"Upload to Cloudinary","type":"n8n-nodes-base.httpRequest","position":[880,-120],"parameters":{"url":"https://api.cloudinary.com/v1_1/daglih2g8/image/upload","method":"POST","options":{},"sendBody":true,"sendQuery":true,"contentType":"multipart-form-data","authentication":"genericCredentialType","bodyParameters":{"parameters":[{"name":"file","parameterType":"formBinaryData","inputDataFieldName":"data"}]},"genericAuthType":"httpQueryAuth","queryParameters":{"parameters":[{"name":"upload_preset","value":"n8n-workflows-preset"}]}},"credentials":{"httpQueryAuth":{"id":"sT9jeKzZiLJ3bVPz","name":"Cloudinary API"}},"typeVersion":4.2},{"id":"525725ea-effe-410b-8f39-ad01ae755d1a","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-1480,-340],"parameters":{"color":7,"width":760,"height":640,"content":"## 1. Ask for Source Style and Target Image\n[Learn more about the Form Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.formtrigger/)\n\nWe'll use a form interface for this template which allows the users to specify an image whose style we'll use as reference and a prompt to generate the target image. Form validation loop can be achieved by combining another form node with the IF node."},"typeVersion":1},{"id":"ba9d4bdc-b695-4acf-9593-2ce979053744","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-700,-340],"parameters":{"color":7,"width":720,"height":440,"content":"## 2. Visual Style Description using Gemini 2.0\n[Read more about Gemini Image Understanding](https://ai.google.dev/gemini-api/docs/image-understanding)\n\nThe trick to copying the style of an image is to get a multimodal LLM to describe it in detail. Using Gemini's image understanding capabilities, it can do a pretty good job to provide the comprehensive description we need."},"typeVersion":1},{"id":"455f7956-6e39-4d84-af7e-d5908d4e5307","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[40,-340],"parameters":{"color":7,"width":720,"height":440,"content":"## 3. Image Generation using Imagen 3.0\n[Read more about Imagen Image Generation](https://ai.google.dev/gemini-api/docs/image-generation#imagen)\n\nTo generate the image, we'll be using Google's Imagen 3.0 model. We'll combine the visual style description generated in the previous Gemini model with the user's target image prompt and pass this to Imagen to do its magic! The result is something very close to style transfer which produces quite convincing and impressive results."},"typeVersion":1},{"id":"0c1d5a7f-9102-4558-a830-ed558f72c086","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[780,-340],"parameters":{"color":7,"width":980,"height":600,"content":"## 4. Render Results to HTML Page\n[Learn more about the HTML node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.html)\n\nFinally for presentation, we'll render the generated images as a webpage for easier viewing using the HTML node. This page can then be sent to the user's email if provided and downloaded as a file once we land on the form ending node."},"typeVersion":1},{"id":"ccc1ff65-0416-4dae-9557-ba2f98c5ac80","name":"Convert to File1","type":"n8n-nodes-base.convertToFile","position":[1320,60],"parameters":{"options":{"encoding":"utf8","fileName":"={{ $('Variables').item.json.targetPrompt.toSnakeCase().urlEncode() }}.html"},"operation":"toText","sourceProperty":"html"},"typeVersion":1.1},{"id":"0252d373-3678-4851-819d-d36efb40dfb2","name":"Generate HTML","type":"n8n-nodes-base.html","position":[1080,-120],"parameters":{"html":"<h1>\n  {{ $('Variables').item.json.targetPrompt.toSentenceCase() }}\n</h1>\n<div class=\"gallery\">\n{{\n$input.all()\n  .chunk(2)\n  .map(row =>\n    `<div class=\"gallery-row\">\n      ${row.map(item =>\n        `<a href=\"${item.json.url}\" target=\"_blank\">\n          <img src=\"${item.json.url}\" width=\"320px\" height=\"240px\"  />\n        </a>`).join('')}\n    </div>`\n  ).join('')\n}}\n</div>\n<div class=\"fineprint\">\n  Generated by Imagen 3.0 (imagen-3.0-generate-002) @ {{ $now.format('d MMM yyyy') }}.\n</div>\n<div class=\"fineprint\">\n  <h3>Style Prompt</h3>\n  <div style=\"display:flex;gap: 10px;\">\n    <div>\n      <img\n        src=\"{{ $('Download Image').item.json.sourceStyleUrl }}\"\n        width=\"100px\"\n        height=\"auto\"\n      />\n    </div>\n    <div>\n      {{ $('Gemini 2.0').first().json.candidates[0].content.parts[0].text }}\n    </div>\n  </div>\n</div>\n<style>\n  body { padding: 32px 64px; font-family: sans-serif; background-color: #fffaf2; }\n  h1 { max-width: 640px; }\n  .gallery { display: inline-block; padding: 5px; border: 1px solid #ccc; background-color: white; margin-bottom: 32px;}\n  .gallery-row { display: flex; }\n  .gallery-row img { padding: 5px; background-color: white }\n  .gallery-row img:hover { background-color: orange; } \n  .fineprint { font-size: 0.7rem; max-width: 480px; text-align: justify;}\n  .fineprint h3 { font-size: 0.8rem; }\n</style>"},"executeOnce":true,"typeVersion":1.2},{"id":"b712bd74-8615-4080-aad2-dc0b0df0b07e","name":"Convert to File","type":"n8n-nodes-base.convertToFile","position":[580,-120],"parameters":{"options":{"fileName":"={{ $execution.id }}_{{ $itemIndex }}.{{ $json.mimeType.split('/')[1] }}","mimeType":"={{ $json.mimeType }}"},"operation":"toBinary","sourceProperty":"bytesBase64Encoded"},"typeVersion":1.1},{"id":"ee3e168a-038c-4524-93fc-ced5d4956fa1","name":"Form Ending","type":"n8n-nodes-base.form","position":[1540,60],"webhookId":"c4bacbac-0347-4c35-9333-3704630b45ef","parameters":{"options":{"formTitle":"Generation Complete"},"operation":"completion","respondWith":"returnBinary","completionTitle":"Image Generation Successful","completionMessage":"Download has started.\nOpen the HTML file to view results."},"executeOnce":true,"typeVersion":1},{"id":"c4b1648a-c7d9-46cd-af28-527f9b169ab6","name":"Has Email?","type":"n8n-nodes-base.if","position":[1320,-120],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"07d04523-f81a-4efa-b46a-cb640bcc608a","operator":{"type":"string","operation":"notEmpty","singleValue":true},"leftValue":"={{ $('Variables').item.json.email }}","rightValue":""}]}},"typeVersion":2.2},{"id":"bc0a8188-27ff-4834-9c7a-8760531f4252","name":"Send Results to Email","type":"n8n-nodes-base.gmail","position":[1540,-120],"webhookId":"de5684ed-6aa4-4c29-aac3-62e21c54c6f0","parameters":{"sendTo":"={{ $('Variables').first().json.email }}","message":"={{ $json.html }}","options":{},"subject":"=#{{$execution.id}} - Image Generated Successfully!"},"credentials":{"gmailOAuth2":{"id":"Sf5Gfl9NiFTNXFWb","name":"Gmail account"}},"typeVersion":2.1},{"id":"f18ab53b-d8e2-4625-a8f6-8b97959a15d1","name":"Image to Base64","type":"n8n-nodes-base.extractFromFile","position":[-400,-120],"parameters":{"options":{},"operation":"binaryToPropery"},"typeVersion":1},{"id":"ac6c1353-035c-4045-aed9-46285b757b98","name":"Gemini 2.0","type":"n8n-nodes-base.httpRequest","position":[-180,-120],"parameters":{"url":"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent","method":"POST","options":{},"jsonBody":"={\n    \"contents\": [{\n      \"parts\": [\n        {\n          \"inline_data\": {\n            \"mime_type\":\"{{ $('Download Image').item.binary.data.mimeType }}\",\n            \"data\": \"{{ $json.data }}\"\n          }\n        },\n        {\"text\": \"Describe the visual style of this image. Do not include any character names or IP in the description. Include names of any famous artists associated with this style if known.\"}\n      ]\n    }]\n  }","sendBody":true,"specifyBody":"json","authentication":"predefinedCredentialType","nodeCredentialType":"googlePalmApi"},"credentials":{"googlePalmApi":{"id":"dSxo6ns5wn658r8N","name":"Google Gemini(PaLM) Api account"}},"typeVersion":4.2},{"id":"fae8c5f0-9d6f-4e45-8f7a-82f361bf9b1b","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-1920,-840],"parameters":{"width":400,"height":1140,"content":"## Try It Out!\n### This n8n template allows you to use AI to generate logos or images which mimic visual styles of other logos or images. The model used to generate the images is Google's Imagen 3.0.\n\nWith this template, users will be able to automate design and marketing tasks such as creating variants of existing designs, remixing existing assets to validate different styles and explore a range of designs which would have been otherwise too expensive and time-consming previously.\n\n### How it works\n* A form trigger is used to capture the source image to reference styles from and a prompt for the target image to generate.\n* The source image is passed to Gemini 2.0 to be analysed and its visual style and tone extracted as a detailed description.\n* This visual style description is then combined with the user's initial target image prompt. This final prompt is given to Imagen 3.0 to generate the images.\n* A quick webpage is put together with the generated images to present back to the user.\n* If the user provided an email address, a copy of this HTML page will be sent.\n\n### How to use\n* Ensure the workflow is live to share the form publicly.\n* The source image must be accessible to your n8n instance - either a public image of the internet or within your network.\n\n### Requirements\n* Gemini for LLM and Imagen model.\n* Cloudinary for image CDN.\n* Gmail for email sending.\n\n### Customising this workflow\n* Feel free to swap any of these out for tools and services you prefer.\n* Want to fully automate? Switch the form trigger for a webhook trigger!\n\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n\nHappy Hacking!"},"typeVersion":1}],"pinData":{},"connections":{"Split Out":{"main":[[{"node":"Convert to File","type":"main","index":0}]]},"Variables":{"main":[[{"node":"Download Image","type":"main","index":0}]]},"Gemini 2.0":{"main":[[{"node":"Imagen 3.0","type":"main","index":0}]]},"Has Email?":{"main":[[{"node":"Send Results to Email","type":"main","index":0}]]},"Imagen 3.0":{"main":[[{"node":"Split Out","type":"main","index":0}]]},"Retry Form":{"main":[[{"node":"Form Validation","type":"main","index":0}]]},"Generate HTML":{"main":[[{"node":"Convert to File1","type":"main","index":0},{"node":"Has Email?","type":"main","index":0}]]},"Download Image":{"main":[[{"node":"Image to Base64","type":"main","index":0}]]},"Convert to File":{"main":[[{"node":"Upload to Cloudinary","type":"main","index":0}]]},"Form Validation":{"main":[[{"node":"Variables","type":"main","index":0}],[{"node":"Retry Form","type":"main","index":0}]]},"Image to Base64":{"main":[[{"node":"Gemini 2.0","type":"main","index":0}]]},"Convert to File1":{"main":[[{"node":"Form Ending","type":"main","index":0}]]},"On form submission":{"main":[[{"node":"Form Validation","type":"main","index":0}]]},"Upload to Cloudinary":{"main":[[{"node":"Generate HTML","type":"main","index":0}]]},"Send Results to Email":{"main":[[]]}}}


name: Summarise Slack Channel Activity for Weekly Reports with AI
author_name: Jimleuk
author_username: jimleuk
categories: []
created_at: 2025-05-10T08:37:45.163Z
updated_at: 2025-05-10T08:37:45.163Z
url: https://n8n.io/workflows/3969
scraped_at: 2025-07-06 21:16:30
description:
  This n8n template lets you summarize team member activity on Slack for the past week and generates a report.

  For remote teams, chat is a crucial communication tool to ensure work gets done but with so many conversations happening at once and in multiple threads, ideas, information and decisions usually live in the moment and get lost just as quickly - and all together forgotten by the weekend!

  Using this template, this doesn't have to be the case. Have AI crawl through last week's activity, summarize all threads and generate a casual and snappy report to bring the team back into focus for the current week. A project manager's dream!

  How it works
  A scheduled trigger is set to run every Monday at 6am to gather all team channel messages within the last week.
  Each message thread are grouped by user and data mined for replies.
  Combined, an AI analyses the raw messages to pull out interesting observations and highlights.
  The summarized threads of the user are then combined together and passed to another AI agent to generate a higher level overview of their week. These are referred to as the individual reports.
  Next, all individual reports are summarized together into a team weekly report. This allows understanding of group and similar activities.
  Finally, the team weekly report is posted back to the channel. The timing is important as it should be the first message of the week and ready for the team to glance over coffee.

  How to use
  Ideally works best per project and where most of the comms happens on a single channel. Avoid combining channels and instead duplicate this workflow for more channels.
  You may need to filter for specific team members if you want specific team updates.
  Customise the report to suit your organisation, team or the channel. You may prefer to be more formal if clients or external stakeholders are also present.

  Requirements
  Slack for chat platform
  Gemini for LLM (or switch for other models)

  Customising this workflow
  If the slack channel is busy enough already, consider posting the final report to email.
  Pull in project metrics to include in your report. As extra context, it may be interesting to tie the messages to production performance.
  Use an AI Agent to query for knowledgebase or tickets relevant to the messages. This may be useful for attaching links or references to add context.
  Channel not so busy or way too busy for 1 week? Play with the scheduled trigger and set an interval which works for your team. 

nodes_used:
  ["n8n-nodes-base.slack","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.lmChatGoogleGemini"]

workflow_json:
  {"meta":{"instanceId":"408f9fb9940c3cb18ffdef0e0150fe342d6e655c3a9fac21f0f644e8bedabcd9","templateCredsSetupCompleted":true},"nodes":[{"id":"f4322829-1799-4954-a75a-b40e95f41c10","name":"Get Last Week's Messages","type":"n8n-nodes-base.slack","position":[-2200,-160],"webhookId":"8078218a-7edc-4e0b-9b4d-9860bd309877","parameters":{"filters":{"oldest":"={{ $now.minus('1', 'week') }}","inclusive":false},"resource":"channel","channelId":{"__rl":true,"mode":"list","value":"C06RS1WPUQ6","cachedResultName":"general"},"operation":"history","returnAll":true},"credentials":{"slackApi":{"id":"VfK3js0YdqBdQLGP","name":"Slack account"}},"typeVersion":2.3},{"id":"f0e89c19-ee1f-4a4d-8176-c222c18e0514","name":"Simplify Message","type":"n8n-nodes-base.set","position":[-1320,380],"parameters":{"options":{},"assignments":{"assignments":[{"id":"547e8934-e6f2-47f0-b8a0-c60bd9d8a0c3","name":"ts","type":"string","value":"={{ $json.ts }}"},{"id":"22473b44-b1d9-4b85-b0d9-1a54c5511ff4","name":"userId","type":"string","value":"={{ $('Get User').first().json.id }}"},{"id":"2059b147-8b12-42c9-bee8-488dc11a0bf7","name":"userName","type":"string","value":"={{ $('Get User').first().json.name }}"},{"id":"34440ea6-ee99-4cd4-9e1c-cf561d335180","name":"type","type":"string","value":"={{ $json.type }}"},{"id":"ff1155c5-43e1-4e0e-82a8-9e013a7f1db1","name":"text","type":"string","value":"={{ $json.text.replace(/(<@[^>]+>)/ig, '').trim() }}"}]}},"typeVersion":3.4},{"id":"1293a7cf-1467-432f-b7ed-606146618808","name":"Group By User","type":"n8n-nodes-base.code","position":[-2000,-160],"parameters":{"jsCode":"const keyByUser = $input.all()\n  .map(item => item.json)\n  .reduce((acc, message) => {\n    return {\n      ...acc,\n      [message.user]: Array.isArray(acc[message.user])\n        ?  acc[message.user].concat(message)\n        : [message]\n    }\n  }, {});\n\nreturn {\n  data: Object\n    .keys(keyByUser)\n    .map(key => keyByUser[key])\n};"},"typeVersion":2},{"id":"681a2368-9688-4ebd-bb88-f48c7ccb3e54","name":"Split Out","type":"n8n-nodes-base.splitOut","position":[-1800,-160],"parameters":{"options":{},"fieldToSplitOut":"data"},"typeVersion":1},{"id":"38a5e6b0-ba4a-4aaa-93f2-ec2a73e5e1af","name":"Messages to Items","type":"n8n-nodes-base.code","position":[-1540,380],"parameters":{"jsCode":"return Object.values($('Switch').first().json.data)"},"typeVersion":2},{"id":"066e40ef-91d7-4db0-95bb-2027c9251a23","name":"Get User","type":"n8n-nodes-base.slack","position":[-1760,380],"webhookId":"042e9c13-5038-433a-98dc-8b6d83c015de","parameters":{"user":{"__rl":true,"mode":"id","value":"={{ $json.data['0'].user }}"},"resource":"user"},"credentials":{"slackApi":{"id":"VfK3js0YdqBdQLGP","name":"Slack account"}},"typeVersion":2.3},{"id":"c5d0b4d1-94eb-4e14-9985-85d384d6d96f","name":"Aggregate","type":"n8n-nodes-base.aggregate","position":[-1100,380],"parameters":{"options":{},"aggregate":"aggregateAllItemData","destinationFieldName":"messages"},"typeVersion":1},{"id":"47537a27-90d9-4edc-b9f4-66205bc4a4c2","name":"Split Out1","type":"n8n-nodes-base.splitOut","position":[-1760,780],"parameters":{"options":{},"fieldToSplitOut":"data.messages"},"typeVersion":1},{"id":"6fdd0fc0-c563-46a3-afb2-48853d3e6cef","name":"Get Thread","type":"n8n-nodes-base.slack","position":[-1100,780],"webhookId":"c3ef27dc-2648-4f91-b329-89a7fa833797","parameters":{"ts":"={{ $json.ts }}","filters":{},"resource":"channel","channelId":{"__rl":true,"mode":"list","value":"C06RS1WPUQ6","cachedResultName":"general"},"operation":"replies"},"credentials":{"slackApi":{"id":"VfK3js0YdqBdQLGP","name":"Slack account"}},"typeVersion":2.3},{"id":"0fc6664f-9076-4525-acaa-0f5009de2611","name":"Aggregate1","type":"n8n-nodes-base.aggregate","position":[-440,860],"parameters":{"options":{},"aggregate":"aggregateAllItemData","destinationFieldName":"replies"},"typeVersion":1},{"id":"caf963e5-3d5b-42d8-88ce-1fb5bf03a528","name":"Simplify Thread Comments","type":"n8n-nodes-base.set","position":[-660,780],"parameters":{"options":{},"assignments":{"assignments":[{"id":"82bc8cbe-c606-4717-b29d-2d8acc149271","name":"ts","type":"string","value":"={{ $json.ts }}"},{"id":"8fcc957d-aa9f-47df-99e8-560228fde30f","name":"userId","type":"string","value":"={{ $json.user }}"},{"id":"e6c6deb3-c3ba-4452-be7c-1a0c42c5dc2c","name":"userName","type":"string","value":""},{"id":"31d1206d-ecbd-48d3-a00a-845fd53d1cfa","name":"type","type":"string","value":"={{ $json.type }}"},{"id":"da126e6c-8dfc-41aa-991a-231b3cb3004b","name":"text","type":"string","value":"={{ $json.text }}"}]}},"typeVersion":3.4},{"id":"aab0ae1c-50da-49e5-a373-c32b39108041","name":"Filter","type":"n8n-nodes-base.filter","position":[-880,780],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"a6d43072-380e-40f2-985b-faeffdaffdce","operator":{"type":"string","operation":"notEquals"},"leftValue":"={{ $('Split Out1').item.json.ts }}","rightValue":"={{ $json.ts }}"}]}},"typeVersion":2.2,"alwaysOutputData":true},{"id":"35cdb470-a9eb-4544-999c-5360dda0f1a3","name":"Message Ref","type":"n8n-nodes-base.noOp","position":[-1320,780],"parameters":{},"typeVersion":1},{"id":"95500787-7965-4951-a729-615feb636021","name":"Split Out2","type":"n8n-nodes-base.splitOut","position":[-1320,1080],"parameters":{"options":{},"fieldToSplitOut":"replyUsers"},"typeVersion":1},{"id":"250d61cc-120d-4c0c-8220-f9a68a90b667","name":"Map Reply UserIds","type":"n8n-nodes-base.set","position":[-1760,1160],"parameters":{"options":{},"assignments":{"assignments":[{"id":"dda6e3d8-0097-4621-9619-07cf39e93018","name":"replyUsers","type":"array","value":"={{\n$json.data.messages\n  .flatMap(item => item.replies.flatMap(reply => reply.userId))\n  .compact()\n  .unique()\n}}"}]}},"typeVersion":3.4},{"id":"3358736b-fc6e-4e18-9a58-4ffc59308055","name":"Get Reply Users","type":"n8n-nodes-base.slack","position":[-1100,1080],"webhookId":"c9ad7c7e-2c48-4c24-9255-e04ab26252ab","parameters":{"user":{"__rl":true,"mode":"id","value":"={{ $json.replyUsers }}"},"resource":"user"},"credentials":{"slackApi":{"id":"VfK3js0YdqBdQLGP","name":"Slack account"}},"typeVersion":2.3},{"id":"e98acd0f-f1e3-47f4-ae9c-7259462cf231","name":"Google Gemini Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[-120,1380],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash"},"credentials":{"googlePalmApi":{"id":"dSxo6ns5wn658r8N","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"0ffb9b87-43db-4417-8c37-384a33cbb830","name":"Summarise Threads","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-220,1160],"parameters":{"text":"=## Message\n{{ $json.userName }} (<@{{ $json.userId }}>) says at {{ new DateTime(parseFloat($json.ts)*1000).format('d MMM HH:mma') }}:\n> {{ $json.text }}\n\n## {{ ($json.replies ?? []).compact().length }} Replies\n{{\n($json.replies ?? [])\n  .compact()\n    .map(reply => ({\n      ...reply,\n      userName: $('Reply Users').item.json.data\n        .find(user => user.id === reply.userId)?.name\n    }))\n    .map(reply =>\n      `* ${new DateTime(parseFloat($json.ts)*1000).format('d MMM HH:mma')}, ${reply.userName} (<@${reply.userId}>) replies: ${reply.text}`\n)\n    .join('\\n')\n  \n}}","messages":{"messageValues":[{"message":"=Summarize the topic of the slack message and the resulting conversation from the replies (if any). Highlight any achievements, accomplishments, attempts or challenges mentioned."}]},"promptType":"define"},"typeVersion":1.6},{"id":"678a48ec-acb1-4c42-b8c9-d4cd762e4a2a","name":"Aggregate2","type":"n8n-nodes-base.aggregate","position":[160,1160],"parameters":{"options":{},"aggregate":"aggregateAllItemData"},"typeVersion":1},{"id":"ab39b117-e1bd-495f-a92d-fb79973b3601","name":"Aggregate Reply Users","type":"n8n-nodes-base.aggregate","position":[-880,1080],"parameters":{"options":{},"aggregate":"aggregateAllItemData"},"typeVersion":1},{"id":"c71b7ca6-8245-4262-b2f1-abea511390d6","name":"Reply Users","type":"n8n-nodes-base.set","position":[-660,1160],"parameters":{"options":{},"assignments":{"assignments":[{"id":"9f721cde-2d36-40ee-b7d8-a920695157a9","name":"data","type":"array","value":"={{ $json.data ?? [] }}"}]}},"typeVersion":3.4},{"id":"4b2c452b-4e68-4536-aa58-a85fd586c606","name":"Google Gemini Chat Model1","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[-460,0],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash"},"credentials":{"googlePalmApi":{"id":"dSxo6ns5wn658r8N","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"d65b4f27-52ab-4c29-8692-ee2835fddd17","name":"Loop Over Items","type":"n8n-nodes-base.splitInBatches","position":[-1540,780],"parameters":{"options":{}},"typeVersion":3},{"id":"cfb55c7f-a89d-4ce4-8709-31e5e119c6ee","name":"Aggregate3","type":"n8n-nodes-base.set","position":[-1320,580],"parameters":{"mode":"raw","options":{},"jsonOutput":"={{\n{\n  ...$('Split Out1').item.json,\n  replies: $json.replies.filter(reply => reply.ts)\n}\n}}\n"},"typeVersion":3.4},{"id":"8b70e30c-99d5-4086-85aa-e6cfcc7f14e7","name":"Aggregate4","type":"n8n-nodes-base.aggregate","position":[-1100,580],"parameters":{"options":{},"aggregate":"aggregateAllItemData"},"typeVersion":1},{"id":"1cef5853-d301-49cb-9f58-c1a9128b8b33","name":"When Executed by Another Workflow","type":"n8n-nodes-base.executeWorkflowTrigger","position":[-2200,780],"parameters":{"workflowInputs":{"values":[{"name":"action"},{"name":"data","type":"object"}]}},"typeVersion":1.1},{"id":"b30c2433-3bfe-480f-a4bd-8c41900802a2","name":"Switch","type":"n8n-nodes-base.switch","position":[-1980,780],"parameters":{"rules":{"values":[{"outputKey":"users","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"fa924990-9f6e-40c4-aaec-50d4f5927414","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.action }}","rightValue":"users"}]},"renameOutput":true},{"outputKey":"message_replies","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"26ce01b2-9e5b-43e8-926d-9d726c9ca74d","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.action }}","rightValue":"message_replies"}]},"renameOutput":true},{"outputKey":"message_summarize","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"45fd7264-6ac3-4bbd-8a91-c4cfb33b4545","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.action }}","rightValue":"message_summarize"}]},"renameOutput":true}]},"options":{}},"typeVersion":3.2},{"id":"b05735c3-4beb-4a80-8297-85e952e81270","name":"Map Users to Messages","type":"n8n-nodes-base.executeWorkflow","position":[-1520,-160],"parameters":{"mode":"each","options":{},"workflowId":{"__rl":true,"mode":"id","value":"={{ $workflow.id }}"},"workflowInputs":{"value":{"data":"={{ $json }}","action":"users"},"schema":[{"id":"action","type":"string","display":true,"required":false,"displayName":"action","defaultMatch":false,"canBeUsedToMatch":true},{"id":"data","type":"object","display":true,"required":false,"displayName":"data","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":true}},"typeVersion":1.2},{"id":"28ed52b2-b0c3-4f19-b394-347c8ff9e323","name":"Get User Info","type":"n8n-nodes-base.set","position":[-880,380],"parameters":{"options":{},"assignments":{"assignments":[{"id":"17344879-1e10-4738-8db0-6e0daddea920","name":"user","type":"object","value":"={{\n{\n  id: $('Get User').item.json.id,\n  team_id: $('Get User').item.json.team_id,\n  name: $('Get User').item.json.name,\n  is_bot: $('Get User').item.json.is_bot\n}\n}}"}]},"includeOtherFields":true},"typeVersion":3.4},{"id":"bbd7c77e-2405-4e63-ae38-f064beafab9c","name":"Fetch Message Replies","type":"n8n-nodes-base.executeWorkflow","position":[-1300,-160],"parameters":{"mode":"each","options":{},"workflowId":{"__rl":true,"mode":"id","value":"={{ $workflow.id }}"},"workflowInputs":{"value":{"data":"={{ $json }}","action":"message_replies"},"schema":[{"id":"action","type":"string","display":true,"removed":false,"required":false,"displayName":"action","defaultMatch":false,"canBeUsedToMatch":true},{"id":"data","type":"object","display":true,"removed":false,"required":false,"displayName":"data","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":true}},"typeVersion":1.2},{"id":"edf34e72-04b4-4fed-a3af-42dec1c7ed17","name":"Has ReplyUsers?","type":"n8n-nodes-base.if","position":[-1540,1160],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"813d9fea-9de0-4151-aa45-d38a42f808b8","operator":{"type":"array","operation":"notEmpty","singleValue":true},"leftValue":"={{ $json.replyUsers }}","rightValue":""}]}},"typeVersion":2.2},{"id":"dc9c6cf0-c627-4311-9160-62204e9b67e0","name":"Messages to Items1","type":"n8n-nodes-base.code","position":[-440,1160],"parameters":{"jsCode":"return $('Switch').first().json.data.messages"},"typeVersion":2},{"id":"0b830a49-c77e-41f3-8d70-47a26bfe0a0e","name":"Aggregate Results","type":"n8n-nodes-base.set","position":[-760,-160],"parameters":{"mode":"raw","options":{},"jsonOutput":"={{\n{\n  ...$('Map Users to Messages').item.json,\n  messages: $('Fetch Message Replies').item.json.data\n  .map((message,idx) => ({\n    ...message,\n    summary: $json.data[idx].text,\n  }))\n}\n}}"},"typeVersion":3.4},{"id":"b0c66c7f-0fed-465c-8933-7b803c9b3b64","name":"Team Member Weekly Report Agent","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[-560,-160],"parameters":{"text":"={{\n$json.messages\n  .map((message,idx) =>\n    `${message.userName} (<@${message.userId}>) posted on ${new Date(parseFloat(message.ts) * 1000).format('d MMM')}:\\n> \\\"${message.text}\\\".\\nThe summary of this thread is as follows:\\n${message.summary.replaceAll('\\n', ' ')}`\n  )\n  .join('\\n---\\n')\n}}","messages":{"messageValues":[{"message":"=Your are energetic assistant who produces weekly mini-reports on team members by analysing their slack messages from last week and posts these reports on the following Monday.\nThere has already been some work done to collect and summarise each thread made by the user within the last week.\nYour task is to summarize all the threads by this user and any interactions with other users involved and produce a mini report to share with other team members.\nFocus on wins and challenges.\nAim to motivate and call out any outstanding concerns where appropriate.\nWelcome any new team members who may have joined and say good bye to those who may have left."}]},"promptType":"define"},"typeVersion":1.6},{"id":"e4a487ae-8d71-4fe6-a760-7a0fb95a8fac","name":"Merge with Results","type":"n8n-nodes-base.set","position":[-60,-160],"parameters":{"mode":"raw","options":{},"jsonOutput":"={{\n{\n  ...$('Aggregate Results').item.json,\n  report: $json.text,\n}\n}}"},"typeVersion":3.4},{"id":"06736a5c-7450-406a-ad3a-08a368d1addf","name":"Team Weekly Report Agent","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[160,-160],"parameters":{"text":"={{\n$input.all()\n  .map(item => item.json)\n  .map(item =>\n`user: ${item.user.name} <@${item.user.id}>\nmessage count: ${item.messages.length}\nreport: ${item.report.replaceAll('\\n', ' ')}`\n  ).join('\\n---\\n')\n}}","messages":{"messageValues":[{"message":"=Your are energetic assistant who produces a team-wide weekly report from all activity of all team members in the prior last week and posts this single report on the following Monday.\nThere has already been some work done to collect individual reports from team members.\nYour task is generate a report covering the team to prepare and motivate them for the upcoming week.\nFocus on wins and challenges if available.\nLook out for similar activities between members and make a connection if possible.\nAim to motivate and call out any outstanding concerns where appropriate.\nWelcome any new team members who may have joined and say good bye to those who may have left."}]},"promptType":"define"},"executeOnce":true,"typeVersion":1.6},{"id":"eef36957-9bf0-4be3-95a8-73bbefdc0c85","name":"Google Gemini Chat Model2","type":"@n8n/n8n-nodes-langchain.lmChatGoogleGemini","position":[240,0],"parameters":{"options":{},"modelName":"models/gemini-2.0-flash"},"credentials":{"googlePalmApi":{"id":"dSxo6ns5wn658r8N","name":"Google Gemini(PaLM) Api account"}},"typeVersion":1},{"id":"bfa5c99f-cd8f-4d34-9e6d-9ed476c87d22","name":"Post Report in Team Channel","type":"n8n-nodes-base.slack","position":[820,-160],"webhookId":"3613b3ca-fc98-427f-8903-a5996ff7552e","parameters":{"text":"={{ $json.text }}","select":"channel","channelId":{"__rl":true,"mode":"list","value":"C06RS1WPUQ6","cachedResultName":"general"},"otherOptions":{}},"credentials":{"slackApi":{"id":"VfK3js0YdqBdQLGP","name":"Slack account"}},"typeVersion":2.3},{"id":"b9a11c72-de41-4a45-85a0-672cf54ef152","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-2460,-440],"parameters":{"color":7,"width":820,"height":520,"content":"## 1. Fetch All Activity from Last Week\n[Learn more about the Slack node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack)\n\nWe'll start by fetching all activity in our team channel over the last 7 days and group them  by the message author. We can do this using the Slack node with a DateTime filter. This will give us the raw data to pick apart and analyse for reporting purposes."},"typeVersion":1},{"id":"8afc048f-ce06-46c3-916f-cbcf14bcfe2b","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-1620,-440],"parameters":{"color":7,"width":760,"height":520,"content":"## 2. Summarise Messages Threads & Conversations\n[Learn more about the Execute Workflow node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflow)\n\nWe'll do some more data mining by fetching all replies for each of these top level channel messages. By doing so, we get the full context of the conversation and can hopefully pick up some decisions, discoveries or concerns to add to our report. This data mining does require juggling a lot of \"items\" which becomes hard to manage so we'll use subworkflows to simplify this work.\n\nOnce the data mining is complete, we can summarize each thread with AI and ensure we're capturing only the significant events which are report-worthy."},"typeVersion":1},{"id":"c9a7358c-fbe7-435a-b435-d7b07599bdc6","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-840,-440],"parameters":{"color":7,"width":660,"height":620,"content":"## 3. Generate Activity Reports for Each Team Member\n[Learn more about the Basic LLM node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm)\n\nWith our summarized threads which are grouped per user, we can aggregate them and give them to the AI again to generate a weekly report for the team member. This could include their wins, challenges, learnings or decisions - it really is up to you as to what the report looks like.\n\nA fun part of this output is getting to decide the tone of voice and delivery of the report. Don't be a bore and consider adding some personality and flair!"},"typeVersion":1},{"id":"add32ef0-b515-44e6-a234-0a0fa77f4e84","name":"Summarize Message Threads","type":"n8n-nodes-base.executeWorkflow","position":[-1080,-160],"parameters":{"mode":"each","options":{},"workflowId":{"__rl":true,"mode":"id","value":"={{ $workflow.id }}"},"workflowInputs":{"value":{"data":"={{\n{\n  ...$('Map Users to Messages').item.json,\n  messages: $json.data\n}\n}}","action":"message_summarize"},"schema":[{"id":"action","type":"string","display":true,"removed":false,"required":false,"displayName":"action","defaultMatch":false,"canBeUsedToMatch":true},{"id":"data","type":"object","display":true,"removed":false,"required":false,"displayName":"data","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":true}},"typeVersion":1.2},{"id":"17f2f45e-2c95-4b3c-b6db-a2881ae88964","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[-160,-440],"parameters":{"color":7,"width":680,"height":620,"content":"## 4. Generate Final Report for Whole Team\n[Learn more about the Basic LLM node](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm)\n\nIn this step, we go one level higher and aggregate all individual team member reports together into a big team report. In this way, the overview can group similar activities and generalise activities in a broader sense. The message output will also be shorter which some may find easier to digest."},"typeVersion":1},{"id":"18cc7fa7-603c-4165-97c6-80d72fd4a9a6","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[540,-440],"parameters":{"color":7,"width":680,"height":620,"content":"## 5. Post Report on Team Channel (on Monday Morning!)\n[Learn more about the Slack node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.slack)\n\nFinally, we can post the weekly report in the team channel. This is a great way to automate quick recaps for the team after the weekend break, get others back on track if they've been away or update client team who may pop in now and again to collaborate."},"typeVersion":1},{"id":"9cd8bdd6-5fc7-4e44-bcd0-058bc5d11335","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-2460,360],"parameters":{"color":7,"width":560,"height":340,"content":"## 5. SubWorkflows\n[Read more about Execute Workflow Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.executeworkflowtrigger)\n\nIncorporating Subworkflows into your main workflow is an advanced technique and sometimes necessary if you're working with a lot of nested items or loops.\n\nIn this scenario, we perform quite a few lookups to get the data we need; users, messages and replies, which in template terms would require many loop nodes to string together. However, when you nest loops nodes within loop nodes, item reference becomes difficult to keep track of.\n\nUsing subworkflows, we can break down each loop into a separate execution which handles items and item references in a simpler, linear way. The result is predictable data flow throughout our template. "},"typeVersion":1},{"id":"6f6fc730-5fc8-4dcc-b86d-e3b2f0e792a0","name":"Monday @ 6am","type":"n8n-nodes-base.scheduleTrigger","position":[-2400,-160],"parameters":{"rule":{"interval":[{"field":"cronExpression","expression":"0 6 * * 1"}]}},"typeVersion":1.2},{"id":"ab94557c-debb-425c-ac83-62e39e43d28b","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[-2940,-1380],"parameters":{"width":420,"height":1460,"content":"## Try It Out!\n### This n8n template lets you summarize individual team member activity on slack for the past week and generates a report.\n\nFor remote teams, chat is a crucial communication tool to ensure work gets done but with so many conversations happening at once and in multiple threads, ideas, information and decisions usually live in the moment and get lost just as quickly - and all together forgotten by the weekend!\n\nUsing this template, this doesn't have to be the case. Have AI crawl through last week's activity, summarize all threads and generate a casual and snappy report to bring the team back into focus for the current week. A project manager's dream!\n\n### How it works\n* A scheduled trigger is set to run every Monday at 6am to gather all team channel messages within the last week.\n* Each message thread are grouped by user and data mined for replies.\n* Combined, an AI analyses the raw messages to pull out interesting observations and highlights.\n* The summarized threads of the user are then combined together and passed to another AI agent to generate a higher level overview of their week. These are referred to as the individual reports.\n* Next, all individual reports are summarized together into a team weekly report. This allows understanding of group and similar activities.\n* Finally, the team weekly report is posted back to the channel. The timing is important as it should be the first message of the week and ready for the team to glance over coffee.\n\n### How to use\n* Ideally works best per project and where most of the comms happens on a single channel. Avoid combining channels and instead duplicate this workflow for more channels.\n* You may need to filter for specific team members if you want specific team updates.\n* Customise the report to suit your organisation, team or the channel. You may prefer to be more formal if clients or external stakeholders are also present.\n\n### Requirements\n* Slack for chat platform\n* Gemini for LLM\n\n### Customising this workflow\n* If the slack channel is busy enough already, consider posting the final report to email.\n* Pull in project metrics to include in your report. As extra context, it may be interesting to tie the messages to production performance.\n* Use an AI Agent to query for knowledgebase or tickets relevant to the messages. This may be useful for attaching links or references to add context.\n\n### Need Help?\nJoin the [Discord](https://discord.com/invite/XPKeKXeB7d) or ask in the [Forum](https://community.n8n.io/)!\n\nHappy Hacking!"},"typeVersion":1}],"pinData":{},"connections":{"Filter":{"main":[[{"node":"Simplify Thread Comments","type":"main","index":0}]]},"Switch":{"main":[[{"node":"Get User","type":"main","index":0}],[{"node":"Split Out1","type":"main","index":0}],[{"node":"Map Reply UserIds","type":"main","index":0}]]},"Get User":{"main":[[{"node":"Messages to Items","type":"main","index":0}]]},"Aggregate":{"main":[[{"node":"Get User Info","type":"main","index":0}]]},"Split Out":{"main":[[{"node":"Map Users to Messages","type":"main","index":0}]]},"Aggregate1":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"Aggregate2":{"main":[[]]},"Aggregate3":{"main":[[{"node":"Aggregate4","type":"main","index":0}]]},"Aggregate4":{"main":[[]]},"Get Thread":{"main":[[{"node":"Filter","type":"main","index":0}]]},"Split Out1":{"main":[[{"node":"Loop Over Items","type":"main","index":0}]]},"Split Out2":{"main":[[{"node":"Get Reply Users","type":"main","index":0}]]},"Message Ref":{"main":[[{"node":"Get Thread","type":"main","index":0}]]},"Reply Users":{"main":[[{"node":"Messages to Items1","type":"main","index":0}]]},"Monday @ 6am":{"main":[[{"node":"Get Last Week's Messages","type":"main","index":0}]]},"Group By User":{"main":[[{"node":"Split Out","type":"main","index":0}]]},"Get Reply Users":{"main":[[{"node":"Aggregate Reply Users","type":"main","index":0}]]},"Has ReplyUsers?":{"main":[[{"node":"Split Out2","type":"main","index":0}],[{"node":"Reply Users","type":"main","index":0}]]},"Loop Over Items":{"main":[[{"node":"Aggregate3","type":"main","index":0}],[{"node":"Message Ref","type":"main","index":0}]]},"Simplify Message":{"main":[[{"node":"Aggregate","type":"main","index":0}]]},"Aggregate Results":{"main":[[{"node":"Team Member Weekly Report Agent","type":"main","index":0}]]},"Map Reply UserIds":{"main":[[{"node":"Has ReplyUsers?","type":"main","index":0}]]},"Messages to Items":{"main":[[{"node":"Simplify Message","type":"main","index":0}]]},"Summarise Threads":{"main":[[{"node":"Aggregate2","type":"main","index":0}]]},"Merge with Results":{"main":[[{"node":"Team Weekly Report Agent","type":"main","index":0}]]},"Messages to Items1":{"main":[[{"node":"Summarise Threads","type":"main","index":0}]]},"Aggregate Reply Users":{"main":[[{"node":"Reply Users","type":"main","index":0}]]},"Fetch Message Replies":{"main":[[{"node":"Summarize Message Threads","type":"main","index":0}]]},"Map Users to Messages":{"main":[[{"node":"Fetch Message Replies","type":"main","index":0}]]},"Get Last Week's Messages":{"main":[[{"node":"Group By User","type":"main","index":0}]]},"Google Gemini Chat Model":{"ai_languageModel":[[{"node":"Summarise Threads","type":"ai_languageModel","index":0}]]},"Simplify Thread Comments":{"main":[[{"node":"Aggregate1","type":"main","index":0}]]},"Team Weekly Report Agent":{"main":[[{"node":"Post Report in Team Channel","type":"main","index":0}]]},"Google Gemini Chat Model1":{"ai_languageModel":[[{"node":"Team Member Weekly Report Agent","type":"ai_languageModel","index":0}]]},"Google Gemini Chat Model2":{"ai_languageModel":[[{"node":"Team Weekly Report Agent","type":"ai_languageModel","index":0}]]},"Summarize Message Threads":{"main":[[{"node":"Aggregate Results","type":"main","index":0}]]},"Team Member Weekly Report Agent":{"main":[[{"node":"Merge with Results","type":"main","index":0}]]},"When Executed by Another Workflow":{"main":[[{"node":"Switch","type":"main","index":0}]]}}}


name: Personalized AI Tech Newsletter Using RSS, OpenAI and Gmail
author_name: Miha
author_username: miha
categories: []
created_at: 2025-05-11T19:47:23.634Z
updated_at: 2025-05-11T19:47:23.634Z
url: https://n8n.io/workflows/3986
scraped_at: 2025-07-06 21:16:30
description:
  Combine Tech News in a Personalized Weekly Newsletter

  This n8n template automates the collection, storage, and summarization of technology news from top sites, turning it into a concise, personalized weekly newsletter.

  If you like staying informed but want to reduce daily distractions, this workflow is perfect for you. It leverages RSS feeds, vector databases, and LLMs to read and curate tech content on your behalf—so you only receive what truly matters.

  How it works

  A daily scheduled trigger fetches articles from multiple popular tech RSS feeds like Wired, TechCrunch, and The Verge.
  Fetched articles are:

    Normalized to extract titles, summaries, and publish dates.
    Converted to vector embeddings via OpenAI and stored in memory for fast semantic querying.
  A weekly scheduled trigger activates the AI summarization flow:

    The AI is provided with your interests (e.g., AI, games, gadgets) and the desired number of items (e.g., 15).
    It queries the vector store to retrieve relevant articles and summarizes the most newsworthy stories.
    The summary is converted into a clean, email-friendly format and sent to your inbox.

  How to use

  Connect your OpenAI and Gmail accounts to n8n.
  Customize the list of RSS feeds in the “Set Tech News RSS Feeds” node.
  Update your interests and number of desired news items in the “Your Topics of Interest” node.
  Activate the workflow and let the automation run on schedule.

  Requirements

  OpenAI** credentials for embeddings and summarization
  Gmail** (or another email service) for sending the newsletter

  Customizing this workflow

  Want to use different sources? Swap in your own RSS feeds, or use an API-based news aggregator.
  Replace the in-memory vector store with Pinecone, Weaviate, or another persistent vector DB for longer-term storage.
  Adjust the agent's summarization style to suit internal updates, industry-specific briefings, or even entertainment recaps.
  Prefer chat over email? Replace the email node with a Telegram bot to receive your personalized tech newsletter directly in a Telegram chat.

nodes_used:
  ["n8n-nodes-base.gmail","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.embeddingsOpenAi","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter","@n8n/n8n-nodes-langchain.vectorStoreInMemory","@n8n/n8n-nodes-langchain.documentDefaultDataLoader"]

workflow_json:
  {"id":"ni6SfqC3kthAlPtX","meta":{"instanceId":"a2eaba9e45ad7aab18b25cf863df1e910fb6dd3b85279bde97d9bae4a72f6862","templateCredsSetupCompleted":true},"name":"Personalized AI Tech Newsletter Using RSS, OpenAI and Gmail","tags":[],"nodes":[{"id":"5cc6bfe1-dbaa-4196-ac52-27e3d5b7e91d","name":"Split Out","type":"n8n-nodes-base.splitOut","position":[420,-300],"parameters":{},"typeVersion":1},{"id":"c696de41-aeb1-4e2c-9e7e-8b04f7800bdb","name":"Embeddings OpenAI","type":"@n8n/n8n-nodes-langchain.embeddingsOpenAi","position":[960,-100],"parameters":{},"typeVersion":1.2},{"id":"4b127a8f-14b3-4a0e-86f6-3157c59bc09c","name":"Default Data Loader","type":"@n8n/n8n-nodes-langchain.documentDefaultDataLoader","position":[1120,-140],"parameters":{},"typeVersion":1},{"id":"c32d87cd-28ee-4b28-ad53-43320169b6df","name":"Recursive Character Text Splitter","type":"@n8n/n8n-nodes-langchain.textSplitterRecursiveCharacterTextSplitter","position":[1260,0],"parameters":{},"typeVersion":1},{"id":"c912148b-1142-4713-9769-1588ff308c62","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[400,520],"parameters":{},"typeVersion":1.2},{"id":"ba7aef6b-efec-4c35-a9d6-b2b8afb6b6c4","name":"Get Articles Daily","type":"n8n-nodes-base.scheduleTrigger","position":[-20,-300],"parameters":{},"typeVersion":1.2},{"id":"0827bf1b-1322-4e4a-8c5b-0da90382b202","name":"Send Weekly Summary","type":"n8n-nodes-base.scheduleTrigger","position":[-40,360],"parameters":{},"typeVersion":1.2},{"id":"b1625ec0-fd2f-4098-ba79-1f522123cb86","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-100,-460],"parameters":{"content":""},"typeVersion":1},{"id":"7edbdba1-43ac-4754-91ae-d506ee38e8ff","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[-100,200],"parameters":{"content":""},"typeVersion":1},{"id":"e166715b-f579-4d22-bf2f-9318e4e86f2a","name":"News reader AI","type":"@n8n/n8n-nodes-langchain.agent","position":[420,300],"parameters":{},"typeVersion":1.9},{"id":"c88c6c60-493e-41cf-b08d-3eef48e7cbc4","name":"Send Newsletter","type":"n8n-nodes-base.gmail","position":[1020,360],"webhookId":"0de8b6e8-8611-48a9-ba25-1d023698f577","parameters":{},"typeVersion":2.1},{"id":"8e303102-f68c-4cf8-a2bb-4538830610e6","name":"Convert Response to an Email-Friendly Format","type":"n8n-nodes-base.markdown","position":[780,300],"parameters":{},"typeVersion":1},{"id":"3f90c79c-a04d-4537-b426-33900acfcb8a","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[120,300],"parameters":{"content":""},"typeVersion":1},{"id":"de315b7c-065c-45a7-be50-5d7a4eedeeaf","name":"Your topics of interest","type":"n8n-nodes-base.set","position":[180,360],"parameters":{},"typeVersion":3.4},{"id":"8a1d6ac3-6fda-4916-a021-3d5db7d413e0","name":"Store News Articles","type":"@n8n/n8n-nodes-langchain.vectorStoreInMemory","position":[1080,-300],"parameters":{},"typeVersion":1.1},{"id":"b7fd5c59-3ed7-4706-bdd7-a62c62cd65af","name":"Set Tech News RSS Feeds","type":"n8n-nodes-base.set","position":[200,-300],"parameters":{},"typeVersion":3.4},{"id":"77f5f3bc-8ecd-481a-a570-6e49e4fda01b","name":"Read RSS News Feeds","type":"n8n-nodes-base.rssFeedRead","position":[640,-300],"parameters":{},"typeVersion":1.1},{"id":"540f55b3-10d1-4f7e-bbdf-793ae6524fd7","name":"Get News Articles","type":"@n8n/n8n-nodes-langchain.vectorStoreInMemory","position":[540,500],"parameters":{},"typeVersion":1.1},{"id":"f5e37288-ef4c-41ea-87bd-1e9ee1e9ab0f","name":"Embeddings OpenAI2","type":"@n8n/n8n-nodes-langchain.embeddingsOpenAi","position":[740,620],"parameters":{},"typeVersion":1.2},{"id":"f6e050de-8dc1-41dd-a18f-225a2f5f68ad","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[140,-360],"parameters":{"content":""},"typeVersion":1},{"id":"4d773ce7-cbca-4568-bd40-0f9914e835bb","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[-580,-460],"parameters":{"content":""},"typeVersion":1},{"id":"796c2a13-c168-4bc9-b79b-fc80c31274c1","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[900,580],"parameters":{"content":""},"typeVersion":1},{"id":"4bd264b8-b088-413d-b44b-dae3b7cc3e77","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[960,300],"parameters":{"content":""},"typeVersion":1},{"id":"6d2b402d-22e0-4cc5-a070-8b4169f18a99","name":"Set and Normalize Fields","type":"n8n-nodes-base.set","position":[860,-300],"parameters":{},"typeVersion":3.4}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"9b875084-83f0-43e6-968a-caec8fb29d7b","connections":{"Split Out":{"main":[[{"node":"Read RSS News Feeds","type":"main","index":0}]]},"News reader AI":{"main":[[{"node":"Convert Response to an Email-Friendly Format","type":"main","index":0}]]},"Send Newsletter":{"main":[[]]},"Embeddings OpenAI":{"ai_embedding":[[{"node":"Store News Articles","type":"ai_embedding","index":0}]]},"Get News Articles":{"ai_tool":[[{"node":"News reader AI","type":"ai_tool","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"News reader AI","type":"ai_languageModel","index":0}]]},"Embeddings OpenAI2":{"ai_embedding":[[{"node":"Get News Articles","type":"ai_embedding","index":0}]]},"Get Articles Daily":{"main":[[{"node":"Set Tech News RSS Feeds","type":"main","index":0}]]},"Default Data Loader":{"ai_document":[[{"node":"Store News Articles","type":"ai_document","index":0}]]},"Read RSS News Feeds":{"main":[[{"node":"Set and Normalize Fields","type":"main","index":0}]]},"Send Weekly Summary":{"main":[[{"node":"Your topics of interest","type":"main","index":0}]]},"Set Tech News RSS Feeds":{"main":[[{"node":"Split Out","type":"main","index":0}]]},"Your topics of interest":{"main":[[{"node":"News reader AI","type":"main","index":0}]]},"Set and Normalize Fields":{"main":[[{"node":"Store News Articles","type":"main","index":0}]]},"Recursive Character Text Splitter":{"ai_textSplitter":[[{"node":"Default Data Loader","type":"ai_textSplitter","index":0}]]},"Convert Response to an Email-Friendly Format":{"main":[[{"node":"Send Newsletter","type":"main","index":0}]]}}}


name: GitLab Merge Request Review & Risk Analysis with Claude/GPT AI
author_name: Vishal Kumar
author_username: vishalquantana
categories: []
created_at: 2025-05-12T11:59:11.770Z
updated_at: 2025-05-12T11:59:11.770Z
url: https://n8n.io/workflows/3997
scraped_at: 2025-07-06 21:16:30
description:
  Trigger

  The workflow runs when a GitLab Merge Request (MR) is created or updated.

  Extract & Analyze

  It retrieves the code diff and sends it to Claude AI or GPT-4o for risk assessment and issue detection.

  Generate Report

  AI produces a structured summary with:
  Risk levels
  Identified issues
  Recommendations
  Test cases

  Notify Developers

  The report is:
  Emailed to developers and QA teams
  Posted as a comment on the GitLab MR

  Setup Guide

  Connect GitLab
  Add GitLab API credentials
  Select repositories to track

  Configure AI Analysis
  Enter Anthropic (Claude) or OpenAI (GPT-4o) API key

  Set Up Notifications
  Add Gmail credentials
  Update the email distribution list

  Test & Automate
  Create a test MR to verify analysis and email delivery

  Key Benefits

  Automated Code Review** – AI-driven risk assessment and recommendations
  Security & Compliance** – Identifies vulnerabilities before code is merged
  Integration with GitLab CI/CD** – Works within existing DevOps workflows
  Improved Collaboration** – Keeps developers and QA teams informed

  Developed by Quantana, an AI-powered automation and software development company.

nodes_used:
  ["n8n-nodes-base.httpRequest","n8n-nodes-base.gmail","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.lmChatAnthropic","@n8n/n8n-nodes-langchain.outputParserAutofixing","@n8n/n8n-nodes-langchain.outputParserStructured"]

workflow_json:
  {"id":"jzcvnlV8g6aseE4A","meta":{"instanceId":"1abe0e4c2be794795d12bf72aa530a426a6f87aabad209ed6619bcaf0f666fb0","templateCredsSetupCompleted":true},"name":"GitLab MR Auto-Review & Risk Assessment","tags":[{"id":"DOZBZVy35P0wB50k","name":"Quality Assurance (QA)","createdAt":"2025-02-04T06:46:12.267Z","updatedAt":"2025-02-04T06:46:12.267Z"},{"id":"ML7fy627V46ocsUS","name":"Development","createdAt":"2025-02-04T06:46:44.236Z","updatedAt":"2025-02-04T06:46:44.236Z"},{"id":"fX8hRnEv4D8sLSzF","name":"OpenAI","createdAt":"2025-01-09T09:18:12.757Z","updatedAt":"2025-01-09T09:18:12.757Z"},{"id":"xBTtGefXwPc4Bib6","name":"Engineering","createdAt":"2025-02-04T06:47:02.932Z","updatedAt":"2025-02-04T06:47:02.932Z"},{"id":"yy04JQqCaXepPdSa","name":"Project Management","createdAt":"2024-10-30T18:27:57.309Z","updatedAt":"2024-10-30T18:27:57.309Z"},{"id":"zJaZorWWcGpTp35U","name":"DevOps","createdAt":"2025-01-03T12:19:34.273Z","updatedAt":"2025-01-03T12:19:34.273Z"}],"nodes":[{"id":"a82f2b02-538b-4531-921c-d25f1edb97ef","name":"Extract Diff","type":"n8n-nodes-base.httpRequest","position":[860,320],"parameters":{"url":"=     https://gitlab.com/api/v4/projects/{{ encodeURIComponent($json.body.project.path_with_namespace) }}/merge_requests/{{ $json.body.object_attributes.iid }}/changes","options":{},"jsonHeaders":"{\n  \"Authorization\": \"Bearer YOUR_TOKEN_HERE\"\n}","sendHeaders":true,"specifyHeaders":"json"},"typeVersion":4.2},{"id":"cd028c77-23e7-46d6-964d-af5afe468176","name":"Distribution List Generator","type":"n8n-nodes-base.code","position":[2040,160],"parameters":{"jsCode":"const ProjectLeads = {\n  \"alpha_backend\": {\n    \"dev\": [\"dev1@example.com\", \"dev2@example.com\"],\n    \"qa\": [\"qa1@example.com\", \"qa2@example.com\"]\n  },\n  \"beta_webapp\": {\n    \"dev\": [\"dev3@example.com\", \"dev4@example.com\"],\n    \"qa\": [\"qa3@example.com\", \"qa4@example.com\"]\n  },\n  \"gamma_mobile\": {\n    \"dev\": [\"dev5@example.com\", \"dev6@example.com\"],\n    \"qa\": [\"qa5@example.com\", \"qa6@example.com\", \"qa7@example.com\"]\n  },\n  \"delta_api\": {\n    \"dev\": [\"dev7@example.com\", \"dev8@example.com\", \"dev9@example.com\"],\n    \"qa\": [\"qa8@example.com\", \"qa9@example.com\", \"qa10@example.com\"]\n  },\n  \"epsilon_service\": {\n    \"dev\": [\"dev10@example.com\"],\n    \"qa\": [\"qa11@example.com\", \"qa12@example.com\"]\n  },\n  \"zeta_scraper\": {\n    \"dev\": [\"dev11@example.com\"],\n    \"qa\": [\"qa13@example.com\", \"qa14@example.com\"]\n  },\n  \"theta_ui\": {\n    \"dev\": [\"dev12@example.com\", \"dev13@example.com\"],\n    \"qa\": [\"qa15@example.com\", \"qa16@example.com\"]\n  },\n  \"iota_backend\": {\n    \"dev\": [\"dev14@example.com\", \"dev15@example.com\"],\n    \"qa\": [\"qa17@example.com\", \"qa18@example.com\"]\n  },\n  \"kappa_admin\": {\n    \"dev\": [\"dev16@example.com\", \"dev17@example.com\"],\n    \"qa\": [\"qa19@example.com\", \"qa20@example.com\"]\n  }\n};\n\n// Define the GlobalList\nconst GlobalList = [\n  \"admin1@example.com\",\n  \"admin2@example.com\",\n  \"admin3@example.com\"\n];\n\n// Retrieve the project name from the input data and convert it to lowercase\nconst fullName = $('Merge').first().json.body.project.path_with_namespace.toLowerCase();\nconst projectName = fullName.split('/')[1];\n\nlet emails = [];\n\nif (projectName && ProjectLeads[projectName]) {\n  // Extract the emails for the given project\n  const projectEmails = [\n    ...ProjectLeads[projectName].dev,\n    ...ProjectLeads[projectName].qa\n  ];\n\n  // Combine project-specific emails with GlobalList\n  emails = [...projectEmails, ...GlobalList];\n} else {\n  // Default to GlobalList only if project name is not found or is undefined\n  emails = [...GlobalList];\n}\n\n// Handle sender email replacement\nconst senderemail = $('Merge').first().json.body.object_attributes.last_commit.author.email;\nconst oldEmail = \"86149715+user@users.noreply.github.com\";\nconst newEmail = \"user@example.com\";\nconst senderEmail = senderemail.replace(oldEmail, newEmail);\n\n// Add senderEmail to emails list\nemails.push(senderEmail);\n\n// Remove duplicate emails\nemails = [...new Set(emails)];\n\n// Join all emails into a single string\nconst emailsString = emails.join(\", \");\n\n// Return the result\nreturn {\n  json: {\n    emails: emailsString\n  }\n};\n"},"typeVersion":2},{"id":"922c9f5f-2db2-4034-8491-33208f04e580","name":"If Some Change","type":"n8n-nodes-base.if","position":[1100,320],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"loose"},"combinator":"and","conditions":[{"id":"3c3e0e8b-7469-4394-ab99-a9ac5053197a","operator":{"type":"array","operation":"lengthGt","rightType":"number"},"leftValue":"={{ $json.changes }}","rightValue":0}]},"looseTypeValidation":true},"typeVersion":2.2},{"id":"e41fc6b0-ccc7-46c9-9667-79b199aaefc8","name":"Merge","type":"n8n-nodes-base.merge","position":[600,320],"parameters":{},"executeOnce":true,"typeVersion":3},{"id":"1bf7f593-a7d9-48f5-be20-d73e97b030bb","name":"AI Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[1400,300],"parameters":{"text":"={\n  \"model\": \"claude-3-5-haiku-20241022\",\n  \"max_tokens\": 1000,\n  \"temperature\": 0.7,\n  \"tools\": [\n    {\n      \"name\": \"record_summary\",\n      \"description\": \"Record a structured summary of a git diff using well-defined JSON.\",\n      \"input_schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"RiskLevel\": {\n            \"type\": \"string\",\n            \"description\": \"Overall risk assessment of the changes: High/Medium/Low.\"\n          },\n          \"Summary\": {\n            \"type\": \"string\",\n            \"description\": \"One-line summary of the git diff analysis\"\n          },\n          \"Recommendations\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"Recommendation\": {\n                  \"type\": \"string\",\n                  \"description\": \"Specific recommendation in HTML format.\"\n                },\n                \"CodeSnippet\": {\n                  \"type\": \"string\",\n                  \"description\": \"Relevant code full snippet formatted in HTML.\"\n                }\n              },\n              \"required\": [\"Recommendation\", \"CodeSnippet\"]\n            }\n          },\n          \"Issues\": {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"File\": {\n                  \"type\": \"string\",\n                  \"description\": \"HTML-formatted name of the file where the issue exists.\"\n                },\n                \"PotentialIssue\": {\n                  \"type\": \"string\",\n                  \"description\": \"HTML-formatted description of the issue.\"\n                },\n                \"Severity\": {\n                  \"type\": \"string\",\n                  \"description\": \"Severity of the issue: High/Medium/Low in HTML.\"\n                }\n              },\n              \"required\": [\"File\", \"PotentialIssue\", \"Severity\"]\n            }\n          },\n          \"URL\": {\n            \"type\": \"string\",\n            \"description\": \"Link to the repository in HTML.\"\n          },\n          \"DiffTable\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"FileName\": {\n                \"type\": \"string\",\n                \"description\": \"HTML-formatted name of the file.\"\n              },\n              \"Changes\": {\n                \"type\": \"array\",\n                \"items\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"Line\": {\n                      \"type\": \"string\",\n                      \"description\": \"HTML-formatted line number.\"\n                    },\n                    \"Before\": {\n                      \"type\": \"string\",\n                      \"description\": \"Code before the change formatted in HTML.\"\n                    },\n                    \"After\": {\n                      \"type\": \"string\",\n                      \"description\": \"Code after the change formatted in HTML.\"\n                    }\n                  },\n                  \"required\": [\"Line\", \"Before\", \"After\"]\n                }\n              }\n            }\n          },\n          \"TestCases\": {\n            \"type\": \"array\",\n            \"description\": \"List of test cases the QA team must check.\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"TestFor\": {\n                  \"type\": \"string\",\n                  \"description\": \"Description of the functionality or area to test.\"\n                },\n                \"Steps\": {\n                  \"type\": \"string\",\n                  \"description\": \"HTML-formatted steps to test the functionality.\"\n                },\n                \"ExpectedOutcome\": {\n                  \"type\": \"string\",\n                  \"description\": \"HTML-formatted description of the expected result of the test.\"\n                }\n              },\n              \"required\": [\"TestFor\", \"Steps\", \"ExpectedOutcome\"]\n            }\n          }\n        },\n        \"required\": [\"RiskLevel\", \"Summary\", \"Recommendations\", \"Issues\", \"URL\", \"DiffTable\", \"TestCases\"]\n      }\n    }\n  ],\n  \"tool_choice\": {\n    \"type\": \"tool\",\n    \"name\": \"record_summary\"\n  },\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": [\n        {\n          \"type\": \"text\",\n          \"text\": \"Analyze the following git diff, highlight potential issues, provide recommendations with code snippets (using HTML formatting for all values), and include the URL. Ensure proper risk evaluation. Mark issues that may break the build or reveal a security risk as High':\"\n        },\n        {\n          \"type\": \"text\",\n          \"text\": \"{{ $json.changes.map(change => JSON.stringify(change.diff).slice(1, -1).replace(/[\\r]/g, '')).join(' ') }}\"\n        },\n        {\n          \"type\": \"text\",\n          \"text\": \"URL: {{ $json.web_url }}\"\n        },\n        {\n          \"type\": \"text\",\n          \"text\": \"Provide a table of test cases for the QA team under the 'TestCases' field.\"\n        }\n      ]\n    }\n  ]\n}","options":{},"promptType":"define","hasOutputParser":true},"typeVersion":1.7},{"id":"f888ea7c-04d4-4192-aa72-10c341da80c8","name":"Auto-fixing Output Parser","type":"@n8n/n8n-nodes-langchain.outputParserAutofixing","position":[1540,600],"parameters":{"options":{}},"typeVersion":1},{"id":"e52227f3-e606-4ec9-b338-c36fe7ca4b6d","name":"Structured Output Parser","type":"@n8n/n8n-nodes-langchain.outputParserStructured","position":[1760,940],"parameters":{"schemaType":"manual","inputSchema":"{\n  \"type\": \"object\",\n  \"properties\": {\n    \"RiskLevel\": {\n      \"type\": \"string\",\n      \"description\": \"Overall risk assessment of the changes: High/Medium/Low.\"\n    },\n    \"Summary\": {\n      \"type\": \"string\",\n      \"description\": \"One-line summary of the git diff analysis in HTML.\"\n    },\n    \"Recommendations\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"Recommendation\": {\n            \"type\": \"string\",\n            \"description\": \"Specific recommendation in HTML format.\"\n          },\n          \"CodeSnippet\": {\n            \"type\": \"string\",\n            \"description\": \"Relevant code snippet formatted in HTML.\"\n          }\n        },\n        \"required\": [\"Recommendation\", \"CodeSnippet\"]\n      }\n    },\n    \"Issues\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"File\": {\n            \"type\": \"string\",\n            \"description\": \"HTML-formatted name of the file where the issue exists.\"\n          },\n          \"PotentialIssue\": {\n            \"type\": \"string\",\n            \"description\": \"HTML-formatted description of the issue.\"\n          },\n          \"Severity\": {\n            \"type\": \"string\",\n            \"description\": \"Severity of the issue: High/Medium/Low in HTML.\"\n          }\n        },\n        \"required\": [\"File\", \"PotentialIssue\", \"Severity\"]\n      }\n    },\n    \"URL\": {\n      \"type\": \"string\",\n      \"description\": \"Link to the repository in HTML.\"\n    },\n    \"DiffTable\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"FileName\": {\n          \"type\": \"string\",\n          \"description\": \"HTML-formatted name of the file.\"\n        },\n        \"Changes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"Line\": {\n                \"type\": \"string\",\n                \"description\": \"HTML-formatted line number.\"\n              },\n              \"Before\": {\n                \"type\": \"string\",\n                \"description\": \"Code before the change formatted in HTML.\"\n              },\n              \"After\": {\n                \"type\": \"string\",\n                \"description\": \"Code after the change formatted in HTML.\"\n              }\n            },\n            \"required\": [\"Line\", \"Before\", \"After\"]\n          }\n        }\n      }\n    },\n    \"TestCases\": {\n      \"type\": \"array\",\n      \"description\": \"List of test cases the QA team must check.\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"TestFor\": {\n            \"type\": \"string\",\n            \"description\": \"Description of the functionality or area to test.\"\n          },\n          \"Steps\": {\n            \"type\": \"string\",\n            \"description\": \"HTML-formatted steps to test the functionality.\"\n          },\n          \"ExpectedOutcome\": {\n            \"type\": \"string\",\n            \"description\": \"HTML-formatted description of the expected result of the test.\"\n          }\n        },\n        \"required\": [\"TestFor\", \"Steps\", \"ExpectedOutcome\"]\n      }\n    }\n  },\n  \"required\": [\"RiskLevel\", \"Summary\", \"Recommendations\", \"Issues\", \"URL\", \"DiffTable\", \"TestCases\"]\n}"},"typeVersion":1.2},{"id":"360985e5-1195-4411-ab5e-daf182f3be9b","name":"Anthropic Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatAnthropic","position":[1560,940],"parameters":{"model":"claude-3-5-haiku-20241022","options":{}},"credentials":{"anthropicApi":{"id":"9ZxBT7yu9DmfOCQi","name":"Anthropic account Vishal"}},"typeVersion":1.2},{"id":"e912886a-2a4c-4f82-b14b-71db3ee2b616","name":"Anthropic Chat Model1","type":"@n8n/n8n-nodes-langchain.lmChatAnthropic","position":[1400,940],"parameters":{"model":"claude-3-5-haiku-20241022","options":{}},"credentials":{"anthropicApi":{"id":"9ZxBT7yu9DmfOCQi","name":"Anthropic account Vishal"}},"typeVersion":1.2},{"id":"320b0fa4-f864-4a3c-8d25-f2cac2512fbf","name":"Comment Back on MR","type":"n8n-nodes-base.httpRequest","position":[2040,480],"parameters":{"url":"=     https://gitlab.com/api/v4/projects/{{ encodeURIComponent( $('Merge').item.json.body.project.path_with_namespace) }}/merge_requests/{{ $('Merge').item.json.body.object_attributes.iid }}/notes","method":"POST","options":{},"sendBody":true,"jsonHeaders":"{\n  \"Authorization\": \"Bearer YOUR_TOKEN_HERE -1234567890abcdef1234567890demo\"\n}\n","sendHeaders":true,"bodyParameters":{"parameters":[{"name":"body","value":"=# CodeSnape by Quantana\n\n**Summary** | {{ $('AI Agent').item.json.output.Summary }}\n--- | ---\n**Risk Level** | <span style=\"font-weight: bold; font-size: 1.5em; color: {{ $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') === 'Low' ? '#6B7280' : $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') === 'Medium' ? '#F59E0B' : '#EF4444' }};\">{{ $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') }}</span>\n\n## Recommendations\n\n| Recommendation | Code Snippet |\n| --- | --- |\n{{ $('AI Agent').item.json.output.Recommendations.map(rec => `| ${rec.Recommendation} | \\`${rec.CodeSnippet}\\` |`).join('\\n') }}\n\n## Test Cases\n\n| Test For | Steps | Expected Outcome |\n| --- | --- | --- |\n{{ $('AI Agent').item.json.output.TestCases.map(test => `| ${test.TestFor} | ${test.Steps} | ${test.ExpectedOutcome} |`).join('\\n') }}\n\n## Issues\n\n| File | Potential Issue | Severity |\n| --- | --- | --- |\n{{ $('AI Agent').item.json.output.Issues.map(issue => `| ${issue.File} | ${issue.PotentialIssue} | ${issue.Severity} |`).join('\\n') }}\n\n## Diff Table\n\n| Line | Before | After |\n| --- | --- | --- |\n{{ $('AI Agent').item.json.output.DiffTable.Changes.map(change => `| ${change.Line} | \\`${change.Before}\\` | \\`${change.After}\\` |`).join('\\n') }}"}]},"specifyHeaders":"json"},"typeVersion":4.2},{"id":"0f8237ca-f3fb-46cc-b173-d42ea4cd447e","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-60,140],"parameters":{"width":220,"height":360,"content":"- Triggers workflow when a merge request (MR) is created or updated.  \n\n- Add GitLab credentials. Select merge_requests event."},"typeVersion":1},{"id":"2ddc9aa2-3673-4b64-8ffb-ba7428b67dbe","name":"GitLab Trigger","type":"n8n-nodes-base.gitlabTrigger","position":[0,300],"webhookId":"fb878391-270b-47d8-addf-2917c71a3e09","parameters":{"owner":"vishalkumar1","events":["merge_requests"],"repository":"istorefront_server"},"credentials":{"gitlabApi":{"id":"tRWT5f3LnLNRPulP","name":"Gitlab overall"}},"typeVersion":1},{"id":"f751cca3-781e-4eaa-bd90-da329ed53a7f","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[800,160],"parameters":{"height":320,"content":"- Fetches code changes (diffs) from GitLab using API.  \n\n- Replace Authorization token with your GitLab API key."},"typeVersion":1},{"id":"ade683bb-8608-44bb-8047-f6c1b7de8f00","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[1060,160],"parameters":{"width":220,"height":320,"content":"- Ensures that MR contains changes before proceeding.\n\n- No setup required."},"typeVersion":1},{"id":"281db918-d9a9-43bc-ae51-c3631e2ac494","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[1320,160],"parameters":{"width":380,"height":320,"content":"- Calls Claude AI to analyze the diff and generate: Risk Level, Issues, Recommendations, Test Cases.\n\n- Add Anthropic API Key (Claude AI)."},"typeVersion":1},{"id":"a7a1e7f4-1504-49be-8199-f929fc83f65e","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[1480,500],"parameters":{"width":360,"height":220,"content":"- Cleans and refines AI output for structured reporting.\n- No setup required."},"typeVersion":1},{"id":"fdbc74ea-a9cd-4811-b5b4-4eeadff28dff","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[1700,900],"parameters":{"width":220,"height":320,"content":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n- Cleans and refines AI output for structured reporting.\n- No setup required."},"typeVersion":1},{"id":"e402a3ff-daac-4bae-bbb6-f9696c9adab5","name":"Sticky Note7","type":"n8n-nodes-base.stickyNote","position":[1980,0],"parameters":{"width":220,"height":320,"content":"- Creates a list of developers & QA testers for email notifications.\n\n- Update email mappings for your team."},"typeVersion":1},{"id":"21818092-87db-42c6-af12-cbe1632c79ab","name":"Sticky Note8","type":"n8n-nodes-base.stickyNote","position":[2220,0],"parameters":{"width":220,"height":320,"content":"- Sends an HTML-formatted MR Report to developers & QA teams.\n\n- Add Gmail credentials."},"typeVersion":1},{"id":"13e5b3fa-c3a0-4399-baf7-84b99865907c","name":"Sticky Note9","type":"n8n-nodes-base.stickyNote","position":[1980,340],"parameters":{"width":220,"height":300,"content":"- Posts AI-generated review report as a GitLab MR comment.\n- Replace Authorization token with your GitLab API key."},"typeVersion":1},{"id":"REPLACE_WITH_GMAIL_OAUTH_ID","name":"Send to DL ( Email Notification)","type":"n8n-nodes-base.gmail","position":[2280,140],"webhookId":"REPLACE_WITH_WEBHOOK_ID","parameters":{"sendTo":"={{ $json.emails }}","message":"=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>CodeSnape by Quantana</title>\n  <style>\n    body {\n      margin: 0;\n      padding: 0;\n      font-family: Arial, sans-serif;\n    }\n    table {\n      border-spacing: 0;\n      border-collapse: collapse;\n      width: 100%;\n      max-width: 960px;\n      margin: 0 auto;\n    }\n    .container {\n      width: 100%;\n      max-width: 960px;\n      margin: 0 auto;\n    }\n    .section {\n      margin-bottom: 20px;\n      padding: 10px;\n    }\n    .header {\n      background-color: #f2f2f2;\n      padding: 20px;\n      text-align: center;\n    }\n    .table-container {\n      background-color: #f9f9f9;\n      padding: 10px;\n      border-radius: 5px;\n      border: 1px solid #ddd;\n    }\n    .report-summary-table, .table-container table {\n      width: 100%;\n      border-collapse: collapse;\n    }\n    .report-summary-table th, .report-summary-table td, \n    .table-container th, .table-container td {\n      border: 1px solid #ddd;\n      padding: 8px;\n      text-align: left;\n    }\n    .report-summary-table th, .table-container th {\n      background-color: #f2f2f2;\n    }\n    .report-summary-table tbody td, .table-container tbody td {\n      background-color: #fff; /* White background for table body */\n    }\n    .summary-text {\n      color: #1D4ED8;\n    }\n    .risk-YOUR_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE {\n      color: #6B7280;\n    }\n    .risk-YOUR_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE {\n      color: #F59E0B;\n    }\n    .risk-YOUR_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE_OPENAI_KEY_HERE {\n      color: #EF4444;\n    }\n    code {\n      background-color: #f4f4f4;\n      padding: 2px 4px;\n      border-radius: 3px;\n      font-family: \"Courier New\", Courier, monospace;\n    }\n  </style>\n</head>\n<body>\n  <table class=\"container\">\n    <tr>\n      <td class=\"header\">\n        <h1>CodeSnape by Quantana</h1>\n      </td>\n    </tr>\n    <tr>\n      <td>\n        <table class=\"report-summary-table\">\n          <tr>\n            <td><strong>Summary</strong></td>\n            <td class=\"summary-text\">{{ $('AI Agent').item.json.output.Summary }}</td>\n            <td><strong>Risk Level</strong></td>\n  <td style=\"\n    font-weight: bold;\n    font-size: 1.5em;\n    color: \n      {{ $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') === 'Low' ? '#6B7280' : \n         $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') === 'Medium' ? '#F59E0B' : \n         '#EF4444' }};\n    \">\n    {{ $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') }}\n  </td>\n          </tr>\n          <tr>\n            <td><strong>Project Name</strong></td>\n            <td>{{ $('Merge').first().json.body.project.name }}</td>\n            <td><strong>User Name</strong></td>\n            <td>{{ $('Merge').first().json.body.user.name }}</td>\n          </tr>\n          <tr>\n            <td><strong>Created Date</strong></td>\n            <td colspan=\"3\">\n              {{ $('Merge').first().json.body.object_attributes.created_at }} (UTC)\n              <br />\n{{ new Date($('Merge').first().json.body.object_attributes.created_at).toLocaleString('en-IN', { \n  timeZone: 'Asia/Kolkata', \n  day: '2-digit', \n  month: 'short', \n  year: 'numeric', \n  hour: '2-digit', \n  minute: '2-digit', \n  second: '2-digit', \n  hour12: true \n}).replace(',', '') }} (IST)\n              </span>\n            </td>\n          </tr>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td class=\"section\">\n        <h2>Recommendations</h2>\n        <table class=\"table-container\">\n          <thead>\n            <tr>\n              <th>Recommendation</th>\n              <th>Code Snippet</th>\n            </tr>\n          </thead>\n          <tbody>\n            {{ $('AI Agent').item.json.output.Recommendations.map(rec => `\n              <tr>\n                <td>${rec.Recommendation}</td>\n                <td><code>${rec.CodeSnippet}</code></td>\n              </tr>\n            `).join('') }}\n          </tbody>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td class=\"section\">\n        <h2>Test Cases</h2>\n        <table class=\"table-container\">\n          <thead>\n            <tr>\n              <th>Test For</th>\n              <th>Steps</th>\n              <th>Expected Outcome</th>\n            </tr>\n          </thead>\n          <tbody>\n            {{ $('AI Agent').item.json.output.TestCases.map(test => `\n              <tr>\n                <td>${test.TestFor}</td>\n                <td>${test.Steps}</td>\n                <td>${test.ExpectedOutcome}</td>\n              </tr>\n            `).join('') }}\n          </tbody>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td class=\"section\">\n        <h2>Issues</h2>\n        <table class=\"table-container\">\n          <thead>\n            <tr>\n              <th>File</th>\n              <th>Potential Issue</th>\n              <th>Severity</th>\n            </tr>\n          </thead>\n          <tbody>\n            {{ $('AI Agent').item.json.output.Issues.map(issue => `\n              <tr>\n                <td>${issue.File}</td>\n                <td>${issue.PotentialIssue}</td>\n                <td>${issue.Severity}</td>\n              </tr>\n            `).join('') }}\n          </tbody>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td class=\"section\">\n        <h2>Diff Table</h2>\n        <table class=\"table-container\">\n          <thead>\n            <tr>\n              <th>Line</th>\n              <th>Before</th>\n              <th>After</th>\n            </tr>\n          </thead>\n          <tbody>\n            {{ $('AI Agent').item.json.output.DiffTable.Changes.map(change => `\n              <tr>\n                <td>${change.Line}</td>\n                <td><code>${change.Before}</code></td>\n                <td><code>${change.After}</code></td>\n              </tr>\n            `).join('') }}\n          </tbody>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td class=\"section\">\n        <h2>URL</h2>\n        <p><a href=\"{{ $('Extract Diff').item.json.web_url }}\" target=\"_blank\">{{ $('Extract Diff').item.json.web_url }}</a></p>\n      </td>\n    </tr>\n<tr><td>Email Generated by CodeSnape from <a href=“https://quantana.com.au/ai”>Quantana</a> </td></tr>\n  </table>\n</body>\n</html>","options":{"appendAttribution":true},"subject":"=[{{ $('AI Agent').item.json.output.RiskLevel.replace(/<\\/?b>/g, '') }}] - CodeSnape for [{{ $('Merge').first().json.body.project.name }}] by [{{ $('Merge').first().json.body.user.name }}] at\n{{ $('Merge').first().json.body.object_attributes.created_at }}\n"},"credentials":{"gmailOAuth2":{"id":"REPLACE_WITH_GMAIL_OAUTH_ID","name":"Vishal Gmail"}},"typeVersion":2.1},{"id":"REPLACE_WITH_GITLAB_CRED_ID","name":"GitLab Trigger1","type":"n8n-nodes-base.gitlabTrigger","position":[0,540],"webhookId":"REPLACE_WITH_WEBHOOK_ID","parameters":{"owner":"vishalkumar1","events":["merge_requests"],"repository":"istorefront_server"},"credentials":{"gitlabApi":{"id":"tRWT5f3LnLNRPulP","name":"Gitlab overall"}},"typeVersion":1},{"id":"REPLACE_WITH_GITLAB_CRED_ID","name":"GitLab Trigger2","type":"n8n-nodes-base.gitlabTrigger","position":[0,-20],"webhookId":"REPLACE_WITH_WEBHOOK_ID","parameters":{"owner":"vishalkumar1","events":["merge_requests"],"repository":"istorefront_server"},"credentials":{"gitlabApi":{"id":"tRWT5f3LnLNRPulP","name":"Gitlab overall"}},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"f1d633b3-d4d8-43aa-8a6b-7c4acbc67679","connections":{"Merge":{"main":[[{"node":"Extract Diff","type":"main","index":0}]]},"AI Agent":{"main":[[{"node":"Distribution List Generator","type":"main","index":0},{"node":"Comment Back on MR","type":"main","index":0}]]},"Extract Diff":{"main":[[{"node":"If Some Change","type":"main","index":0}]]},"GitLab Trigger":{"main":[[{"node":"Merge","type":"main","index":0}]]},"If Some Change":{"main":[[{"node":"AI Agent","type":"main","index":0}]]},"GitLab Trigger1":{"main":[[{"node":"Merge","type":"main","index":0}]]},"GitLab Trigger2":{"main":[[{"node":"Merge","type":"main","index":0}]]},"Anthropic Chat Model":{"ai_languageModel":[[{"node":"Auto-fixing Output Parser","type":"ai_languageModel","index":0}]]},"Anthropic Chat Model1":{"ai_languageModel":[[{"node":"AI Agent","type":"ai_languageModel","index":0}]]},"Structured Output Parser":{"ai_outputParser":[[{"node":"Auto-fixing Output Parser","type":"ai_outputParser","index":0}]]},"Auto-fixing Output Parser":{"ai_outputParser":[[{"node":"AI Agent","type":"ai_outputParser","index":0}]]},"Distribution List Generator":{"main":[[{"node":"Send to DL ( Email Notification)","type":"main","index":0}]]}}}


name: AI-Generated LinkedIn Posts with OpenAI, Google Sheets & Email Approval Workflow
author_name: Aryan Shinde
author_username: shindearyan
categories: []
created_at: 2025-05-12T23:11:35.083Z
updated_at: 2025-05-12T23:11:35.083Z
url: https://n8n.io/workflows/4005
scraped_at: 2025-07-06 21:16:30
description:
  How it works
  This workflow automates the process of creating, approving, and optionally posting LinkedIn content from a Google Sheet. Here's a high-level overview:
  Scheduled Trigger: Runs automatically based on your defined time interval (daily, weekly, etc.).
  Fetch Data from Google Sheets: Pulls the first row from your sheet where Status is marked as Pending.
  Generate LinkedIn Post Content: Uses OpenAI to create a professional LinkedIn post using the Post Description and Instructions from the sheet.
  Format & Prepare Data: Formats the generated content along with the original instruction and post description for email.
  Send for Approval: Sends an email to a predefined user (e.g., marketing team) with a custom form for approval, including a dropdown to accept/reject and an optional field for edits.
  (Optional) Image Fetch: Downloads an image from a URL (if provided in the sheet) for future use in post visuals.

  Set up steps
  You’ll need the following before you start:

  A Google Sheet with the following columns: Post Description, Instructions, Image (URL), Status
  Access to an OpenAI API key
  A connected Gmail account for sending approval emails
  Your own Google Sheets and Gmail credentials added in n8n

  Steps:
  Google Sheet Preparation:
  Create a new Google Sheet with the mentioned columns (Post Description, Instructions, Image, Status, Output, Post Link).
  Add a row with test data and set Status to Pending.

  Credentials:
  In n8n, create OAuth2 credentials for:
  a. Google Sheets
  b. Gmail
  c. OpenAI (API Key)
  Assign these credentials to the respective nodes in the JSON.

  OpenAI Model:
  Choose a model like gpt-4o-mini (used here) or any other available in your plan.
  Adjust the prompt in the "Generate Post Content" node if needed.

  Email Configuration:
  In the Gmail node, set the recipient email to your own or your team’s address.
  Customize the email message template if necessary.

  Schedule the Workflow:
  Set the trigger interval (e.g., every morning at 9 AM).

  Testing:
  Run the workflow manually first to confirm everything works.
  Check Gmail for the approval form, respond, and verify the results.

nodes_used:
  ["n8n-nodes-base.googleSheets","n8n-nodes-base.httpRequest","n8n-nodes-base.gmail","n8n-nodes-base.linkedIn","@n8n/n8n-nodes-langchain.chainLlm","@n8n/n8n-nodes-langchain.lmChatOpenAi"]

workflow_json:
  {"id":"w9YVsuUtlNgXOEAQ","meta":{"instanceId":"58a9e858b82757a75629d435ec0da2448ad5d9e906a9ce3b7dcbb64c90fda9fe","templateCredsSetupCompleted":true},"name":"LinkedIn Post Generation & Approval Automation","tags":[{"id":"3bzbXbpZW83y48aK","name":"content-approval","createdAt":"2025-05-13T18:45:19.225Z","updatedAt":"2025-05-13T18:45:19.225Z"},{"id":"9SfWdyKRFVbPxrNL","name":"post-generator","createdAt":"2025-05-13T18:45:09.255Z","updatedAt":"2025-05-13T18:45:09.255Z"},{"id":"VWnkUEKQKJB2WmbL","name":"linkedin","createdAt":"2025-05-13T18:45:04.175Z","updatedAt":"2025-05-13T18:45:04.175Z"}],"nodes":[{"id":"d0f86395-aeec-4284-a2d5-26db2dfebd53","name":"Get Image","type":"n8n-nodes-base.httpRequest","position":[1592,2120],"parameters":{"url":"={{ $('Get Data from Sheets').item.json.Image }}","options":{}},"typeVersion":4.2,"alwaysOutputData":true},{"id":"b8ca88a6-aaf1-4191-8b75-9a63b746af77","name":"Schedule Trigger","type":"n8n-nodes-base.scheduleTrigger","position":[-260,2595],"parameters":{"rule":{"interval":[{}]}},"typeVersion":1.2},{"id":"8e4e294f-56d4-4317-aea8-ea258527f55a","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[268,2815],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4o-mini"},"options":{}},"credentials":{"openAiApi":{"id":"7OVQwPA6RZRwFmwI","name":"OpenAI Account"}},"typeVersion":1.2},{"id":"d262684e-7671-4602-ba77-4270441a69fb","name":"Get Data from Sheets","type":"n8n-nodes-base.googleSheets","position":[-40,2595],"parameters":{"options":{"returnFirstMatch":true},"filtersUI":{"values":[{"lookupValue":"Pending","lookupColumn":"Status"}]},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8/edit?usp=drivesdk","cachedResultName":"LinkedIn Post Automation"}},"credentials":{"googleSheetsOAuth2Api":{"id":"Gs7mfT220sbLQ7PS","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"93d3e57f-a22d-4c04-9ccc-7ee655bedbdb","name":"Generate Post Content","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[180,2595],"parameters":{"text":"=# LinkedIn Post Generation\n\n## Post Description:\n{{ $json[\"Post Description\"] }}\n\n## Instructions:\n{{ $json[\"Instructions\"] }}\n\n---\n\n**Task:**\nUsing the information above, generate the content for a LinkedIn post:\n- Use the Description and Instructions to create a new post.\n- Ensure your output is positive, professional, clear, and follows all provided instructions and feedback.\n- Do not include any explanations, just the final post content only, ready to publish on LinkedIn.\n- Limit to 1300 characters.\n- If the user demands to keep the same post as the Post Descrioption (in the instructions), then keep the same post content as provided in the Post Description, and output it.","messages":{"messageValues":[{"message":"=You are an expert social media and LinkedIn content writer.\n\nYou will be provided with:\n- A brief post description\n- Specific instructions from the user\n\nPlease follow these steps:\n\n1. Initial Creation:\nIf you are given a post description and instructions, write a polished, professionally worded LinkedIn post suitable for sharing. Strictly follow the instructions and ensure the message is engaging and succinct.\nIf instructed, add a call to action or particular phrase (for example, \"Connect with me\" at the bottom).\n\n2. Formatting:\nKeep the tone positive, inclusive, and professional.\nAdd relevant hashtags in small case.\nLimit the content to within 1300 characters.\nPlace the call to action or special instruction at the end if requested.\nOutput ONLY the final LinkedIn post content. Do NOT include any explanations, markdown, headings, or commentary—just the post text, ready to copy and share on LinkedIn."}]},"promptType":"define"},"typeVersion":1.6},{"id":"6ea121f4-661c-4dd4-a0bc-7edb8a0b884a","name":"Data Formatting 1","type":"n8n-nodes-base.set","position":[556,2595],"parameters":{"options":{},"assignments":{"assignments":[{"id":"bded6f56-99e2-4f1a-be41-27a8dd417844","name":"Post Content","type":"string","value":"={{ $json.text }}"},{"id":"c222c42e-b639-4a74-a1b0-7a3e6e141d55","name":"Post Description","type":"string","value":"={{ $('Get Data from Sheets').item.json['Post Description'] }}"},{"id":"14775ff7-e005-4a86-9623-c322365f7d3a","name":"Instructions","type":"string","value":"={{ $('Get Data from Sheets').item.json.Instructions }}"}]}},"typeVersion":3.4},{"id":"a00ec081-07c0-4f8a-9139-6606a65236ff","name":"Send Content Confirmation","type":"n8n-nodes-base.gmail","position":[776,2495],"webhookId":"29614d41-05c1-44af-93a6-4831a27f9331","parameters":{"sendTo":"youremail@gmail.com","message":"=Generated Post:\n{{ $json['Post Content'] }}\n\n----------\n\nPost Description:\n{{ $json['Post Description'] }}\n\n----------\n\nInstructions:\n{{ $json.Instructions }}","options":{},"subject":"Approval for LinkedIn Post","operation":"sendAndWait","formFields":{"values":[{"fieldType":"dropdown","fieldLabel":"Confirm  Content?","fieldOptions":{"values":[{"option":"Yes"},{"option":"No"},{"option":"Cancel"}]},"requiredField":true},{"fieldType":"textarea","fieldLabel":"Any Changes?"}]},"responseType":"customForm"},"credentials":{"gmailOAuth2":{"id":"n47Kn2N5BwXYv6eA","name":"Gmail account"}},"typeVersion":2.1},{"id":"5a1e8061-ef79-4974-888d-8670e60a1b0c","name":"Content Confirmation Logic","type":"n8n-nodes-base.switch","position":[996,2495],"parameters":{"rules":{"values":[{"outputKey":"Yes","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"99ec185e-80ac-451d-bb69-662f84a7cf52","operator":{"type":"string","operation":"equals"},"leftValue":"={{ $json.data['Confirm  Content?'] }}","rightValue":"Yes"}]},"renameOutput":true},{"outputKey":"No","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"77031007-a912-4b9b-9cca-846c57ffaec8","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.data['Confirm  Content?'] }}","rightValue":"No"}]},"renameOutput":true},{"outputKey":"Cancel","conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"e94de530-6451-48aa-892c-924a9c41cfb0","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"={{ $json.data['Confirm  Content?'] }}","rightValue":"Cancel"}]},"renameOutput":true}]},"options":{}},"typeVersion":3.2},{"id":"c139fda0-4be1-4ad7-bb28-802b7150365d","name":"Regenerate Post Content","type":"@n8n/n8n-nodes-langchain.chainLlm","position":[1216,2670],"parameters":{"text":"=Apply the modification requests on the following LinkedIn post. Besides applying the reqested modifications, return the same linkedin post.\n\nLinkedIn post:\n```\n{{ $('Data Formatting 1').item.json['Post Content'] }}\n```\n\nChange requests:\n{{ $('Send Content Confirmation').item.json.data['Any Changes?'] }}\n\n**Task:**\nUsing the information above, update the LinkedIn post content:\n- Do not include any explanations, just the final post content only (with all the change requests included in the post), ready to publish on LinkedIn.\n- Limit to 1300 characters.\n- If the user demands to keep the same post as the Post Description (in the instructions), then keep the same post content as provided in the Post Description, and output it.","promptType":"define"},"typeVersion":1.6},{"id":"b8587578-03a6-4975-8907-667628dc958a","name":"If Image Provided","type":"n8n-nodes-base.if","position":[1294,2220],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"9a78220d-35f5-48b6-9ce3-faecaac24b74","operator":{"type":"string","operation":"notEmpty","singleValue":true},"leftValue":"={{ $('Get Data from Sheets').item.json.Image }}","rightValue":""}]}},"typeVersion":2.2},{"id":"30bf76c8-8420-4d77-bc2b-8a1c27fbf36a","name":"Post With Image","type":"n8n-nodes-base.linkedIn","position":[1812,2120],"parameters":{"text":"={{ $('Data Formatting 1').item.json['Post Content'] }}","person":"pM247vR8Se","additionalFields":{},"shareMediaCategory":"IMAGE"},"credentials":{"linkedInOAuth2Api":{"id":"Fg8i5E17dzZcPtMH","name":"LinkedIn account 2"}},"typeVersion":1},{"id":"62bf9597-3e38-45ec-853c-aa2d77c40bc2","name":"Post Without Image","type":"n8n-nodes-base.linkedIn","position":[1812,2320],"parameters":{"text":"={{ $('Data Formatting 1').item.json['Post Content'] }}","person":"pM247vR8Se","additionalFields":{}},"credentials":{"linkedInOAuth2Api":{"id":"Fg8i5E17dzZcPtMH","name":"LinkedIn account 2"}},"typeVersion":1},{"id":"0ddd307d-5e54-42cf-b65a-25f111fb4bea","name":"Update Google Sheet","type":"n8n-nodes-base.googleSheets","position":[2032,2320],"parameters":{"columns":{"value":{"Output":"={{ $json.urn }}","Status":"=Completed","Post Link":"={{ $json.urn }}","row_number":"={{ $('Get Data from Sheets').item.json.row_number }}"},"schema":[{"id":"Post Description","type":"string","display":true,"removed":true,"required":false,"displayName":"Post Description","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Instructions","type":"string","display":true,"removed":true,"required":false,"displayName":"Instructions","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Image","type":"string","display":true,"removed":true,"required":false,"displayName":"Image","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Status","type":"string","display":true,"removed":false,"required":false,"displayName":"Status","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Output","type":"string","display":true,"required":false,"displayName":"Output","defaultMatch":false,"canBeUsedToMatch":true},{"id":"Post Link","type":"string","display":true,"required":false,"displayName":"Post Link","defaultMatch":false,"canBeUsedToMatch":true},{"id":"row_number","type":"string","display":true,"removed":false,"readOnly":true,"required":false,"displayName":"row_number","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":["row_number"],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"update","sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8/edit#gid=0","cachedResultName":"Sheet1"},"documentId":{"__rl":true,"mode":"list","value":"1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1EAdLU9-l0ATGDa5_xwTwFO-rPhvZurM2BOSKjH2P-W8/edit?usp=drivesdk","cachedResultName":"LinkedIn Post Automation"}},"credentials":{"googleSheetsOAuth2Api":{"id":"Gs7mfT220sbLQ7PS","name":"Google Sheets account"}},"typeVersion":4.5},{"id":"c4a55beb-8459-4faa-b072-51c500562ae3","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[-780,2480],"parameters":{"width":420,"height":380,"content":"## 1. Schedule & Sheet Data Retrieval\n\nThis workflow starts automatically on a defined schedule (e.g., daily or hourly).\n\nIt retrieves the next Google Sheet row marked as ‘Pending’.\n\nThe sheet should include columns like:\n1. Post Description\n2. Instructions\n3. Image\n4. Status\n5. row_number (required for updates)\n\nEnsure your Google Sheets credentials are correctly configured."},"typeVersion":1},{"id":"09dade5b-e26a-4b97-9b8a-a5c807cff937","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[-220,3040],"parameters":{"width":420,"height":240,"content":"## 2. AI-Powered Post Generation & Formatting\n\nUses OpenAI GPT to generate a LinkedIn post based on the sheet’s Post Description and Instructions.\n\nYou can modify the prompt if needed.\nThe generated post is then formatted along with relevant data for easy reference and consistency."},"typeVersion":1},{"id":"f373dfd9-f971-4925-8e74-15201c8a47c4","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[440,3040],"parameters":{"width":440,"height":240,"content":"## 3. Gmail Approval Workflow\n\nSends the formatted post to an approver via Gmail.\n\nThe approver can respond with:\n✅ Yes – Approve\n✏️ No – Request changes\n❌ Cancel – Abort the post\n\nSet Gmail credentials and recipient email in the node."},"typeVersion":1},{"id":"66d51a35-262e-469b-bcd8-aca3cf60452c","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[1120,3040],"parameters":{"width":440,"height":240,"content":"## 4. Approval Handling & Regeneration\n\nHandles all approval responses:\n- If Yes, proceed to post.\n- If No, regenerate content based on the feedback and resend.\n- If Cancel, update the Google Sheet as Cancelled.\n\nThis ensures a complete review cycle before posting."},"typeVersion":1},{"id":"ad4023af-0e8b-4ec1-be1d-9313ec4f5ca7","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[1800,3040],"parameters":{"width":460,"height":320,"content":"## 5. Image Check, Posting & Sheet Update\n\nChecks if an image URL is provided.\n- If present: Downloads the image and posts with it.\n- If not: Posts content without an image.\n\nAfter posting, it updates the Google Sheet with:\n- Status = Completed or Cancelled\n- LinkedIn post link/output\n\nUses row_number for precise sheet updates."},"typeVersion":1}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"65685909-274a-4d41-8f8a-fbba9442597b","connections":{"Get Image":{"main":[[{"node":"Post With Image","type":"main","index":0}]]},"Post With Image":{"main":[[{"node":"Update Google Sheet","type":"main","index":0}]]},"Schedule Trigger":{"main":[[{"node":"Get Data from Sheets","type":"main","index":0}]]},"Data Formatting 1":{"main":[[{"node":"Send Content Confirmation","type":"main","index":0}]]},"If Image Provided":{"main":[[{"node":"Get Image","type":"main","index":0}],[{"node":"Post Without Image","type":"main","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"Generate Post Content","type":"ai_languageModel","index":0},{"node":"Regenerate Post Content","type":"ai_languageModel","index":0}]]},"Post Without Image":{"main":[[{"node":"Update Google Sheet","type":"main","index":0}]]},"Get Data from Sheets":{"main":[[{"node":"Generate Post Content","type":"main","index":0}]]},"Generate Post Content":{"main":[[{"node":"Data Formatting 1","type":"main","index":0}]]},"Regenerate Post Content":{"main":[[{"node":"Data Formatting 1","type":"main","index":0}]]},"Send Content Confirmation":{"main":[[{"node":"Content Confirmation Logic","type":"main","index":0}]]},"Content Confirmation Logic":{"main":[[{"node":"If Image Provided","type":"main","index":0}],[{"node":"Regenerate Post Content","type":"main","index":0}],[{"node":"Update Google Sheet","type":"main","index":0}]]}}}


name: Generate Knowledge Base Articles with GPT & Perplexity AI for Contentful CMS
author_name: Varritech
author_username: varritech
categories: []
created_at: 2025-05-13T11:31:19.601Z
updated_at: 2025-05-13T11:31:19.601Z
url: https://n8n.io/workflows/4012
scraped_at: 2025-07-06 21:16:30
description:
  Workflow: Auto Knowledge Base Article Generator

  ⚡ About the Creators
  This workflow was created by Varritech Technologies, an innovative agency that leverages AI to engineer, design, and deliver software development projects 500% faster than traditional agencies. Based in New York City, we specialize in custom software development, web applications, and digital transformation solutions. If you need assistance implementing this workflow or have questions about content management solutions, please reach out to our team.

  🏗️ Architecture Overview  
  This workflow automates the end-to-end creation of a structured knowledge-base article from a simple chat prompt:

  Chat Trigger → Receives user request  
  AI Drafting Loop → Generates & refines JSON article via AI agents  
  Perplexity Research Call → Deep-dive content generation  
  Editorial Loop → Up to 3 AI-driven revisions  
  Contentful Publish → Pushes final JSON into CMS  

  📦 Node-by-Node Breakdown

  flowchart LR
    A[Webhook: Chat Trigger] --&gt; B[AI Writer Agent]
    B --&gt; C[HTTP Request: Perplexity Content]
    C --&gt; D[Function: Format Output & Citations]
    D --&gt; E[Loop Start: Initialize Count]
    E --&gt; F[AI Editor Agent]
    F --&gt; G{action == "rewrite"?}
    G -- yes --&gt; H[Function: Merge Improvements]
    H --&gt; I[Increment Count] --&gt; F
    G -- no --&gt; J[Stop Loop]
    J --&gt; K[HTTP Request: Publish to Contentful]

  Webhook: Chat Trigger

  Type: HTTP Webhook (POST /webhook/knowledge-article)

  Payload:

  { 
  	"chatInput": "What topics should I write about?" 
  }

  Purpose: Kicks off the workflow on that chat prompt.

  AI Writer Agent

  Inputs: chatInput or existing article JSON

  Outputs:

  {
    "title": "...",
    "slug": "...",
    "category.id": "...",
    "description": "...",
    "keywords": [...],
    "content": "...",
    "metaTitle": "...",
    "metaDescription": "...",
    "readingTime": 5,
    "difficulty": "intermediate"
  }

  Purpose: Generates the article skeleton (metadata + initial content).

  HTTP Request: Perplexity Content

  Method: POST

  URL: https://api.perplexity.ai/research

  Body:
  {
    "model": "sonar-deep-research",
    "query": "{{ $json.title }}",
    "length": 1000
  }

  Purpose: Retrieves a long-form, deeply researched draft for the article body.

  Function: Format Output & Citations

  Logic:

  Parse raw Perplexity response
  Extract source URIs
  Append them under a sources markdown list

  Editorial Loop

  Initialize Counter to 0
  AI Editor Agent
     Reads draft JSON
     Returns either:
       action: "rewrite" + improvements: [...]
       or action: "submit"
  Merge Improvements (if rewriting)
     Applies suggested updates to JSON fields
  Limit Check
     Stops after 3 iterations or on "submit"

  HTTP Request: Publish to Contentful

  Method: PUT

  URL: https://cdn.contentful.com/spaces/{space}/environments/master/entries/{entryId}

  Headers:
  Authorization: Bearer &lt;token&gt;
  Content-Type: application/vnd.contentful.management.v1+json

  Body: Maps JSON → Contentful entry fields

  Outcome: Publishes the finalized article live.

  🔍 Design Rationale & Best Practices

  Separation of Concerns
  Writer vs. Editor agents isolate creative drafting from quality review.

  Idempotent Loop
  Counter + action flags prevent infinite retries.

  Extensibility
  Swap in different research APIs or CMS targets with minimal changes.

  Structured JSON
  Ensures predictable input/output for each node.

nodes_used:
  ["n8n-nodes-base.httpRequest","n8n-nodes-base.code","@n8n/n8n-nodes-langchain.agent","@n8n/n8n-nodes-langchain.lmChatOpenAi","@n8n/n8n-nodes-langchain.toolWorkflow"]

workflow_json:
  {"id":"WwEFqNK4YP6UJcg2","meta":{"instanceId":"6bcff5ef8a06e8086902526a05c2a4c7bf5da8101f89e582301ed78094606f40","templateCredsSetupCompleted":true},"name":"Auto Knowledge Base Article Generator","tags":[],"nodes":[{"id":"17900021-8da3-4bd9-9d79-5d20d879ddc7","name":"Create Perplexity Content","type":"n8n-nodes-base.httpRequest","position":[1220,380],"parameters":{"url":"https://api.perplexity.ai/chat/completions","method":"POST","options":{},"jsonBody":"={\n  \"model\": \"sonar-deep-research\",\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": \"Conduct an in-depth research on '{{ $json.output.parseJson().title }}', covering essential topics, recommended resources, and best practices. Additionally, please address these improvements: '{{ $json.output.parseJson().improvements }}'. The returned data should be at least 1000 words and use a combination of headers, tables, bold, and italics\"\n    }\n  ],\n  \"max_tokens\": 60000,\n  \"temperature\": 0.7,\n  \"top_p\": 0.9,\n  \"top_k\": 50,\n  \"stream\": false,\n  \"presence_penalty\": 0,\n  \"frequency_penalty\": 0\n}","sendBody":true,"jsonHeaders":"{\n  \"Authorization\": \"Bearer YOUR_TOKEN_HERE\",\n  \"Content-Type\": \"application/json\"\n}","sendHeaders":true,"specifyBody":"json","specifyHeaders":"json"},"typeVersion":4.2},{"id":"0cb3f3a7-92bd-4ff3-8a89-b6fc29513e65","name":"AI Writer Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[720,380],"parameters":{"text":"=Please create or revise an article about \"{{ $json.chatInput }}\".\n\nOr an article is already written because title: {{ $json.title }} is defined. Reference it to rewrite the {{ $json.content }} field\n\nDo not change the title, either the chatinput or the input article title. This is the title, do not change it.\n\nIf an article is NOT given, pick new categories in:\n- getting-started\n- learning-paths\n- certifications\n- programming\n- applications\n- career\n\nDo not make up a category, it should be the same in the exact case as above\n\nIf an article is given, only make ammendments to the content based on these specific improvements to include: \"{{ $json.improvements }}\".\n\nInclude the improvements key only if it is an input, and in that case don't change it\n\nRemember to return valid JSON with the fields:\n{\n  \"title\": \"...\",\n  \"slug\": \"...\",\n  \"category\": {\n    \"id\": \"...\"\n  },\n  \"description\": \"...\",\n  \"keywords\": [...],\n  \"content\": \"...\",\n  \"metaTitle\": \"...\",\n  \"metaDescription\": \"...\",\n  \"readingTime\": \"...\",\n  \"difficulty\": \"...\"\n  \"content\": \"...\"\n}","options":{"systemMessage":"You are a writing assistant. You will receive instructions to create or update an article in JSON format with the following structure:\n\n{\n  \"title\": \"<string>\",\n  \"slug\": \"<string>\",\n  \"category\": {\n    \"id\": \"<string>\" // e.g., \"getting-started\", \"learning-paths\", etc.\n  },\n  \"description\": \"<string>\",\n  \"keywords\": [\"<string>\", \"<string>\", ...],\n  \"content\": \"<string>\",\n  \"metaTitle\": \"<string>\",\n  \"metaDescription\": \"<string>\",\n  \"readingTime\": \"<number or string>\",\n  \"difficulty\": \"<string>\"\n}\n\nYour task:\n1. Produce a complete article in the above format, or revise the existing article if provided.\n2. Make sure the text is clear, specific, and helpful to the reader.\n3. Return valid JSON only – do not include extra commentary or fields beyond the above structure.\n4. If any information is missing from the user instructions, make reasonable assumptions.\n"},"promptType":"define"},"typeVersion":1.7},{"id":"bc62facb-c5cb-465d-89b1-a65a893c3ce1","name":"AI Editor Agent","type":"@n8n/n8n-nodes-langchain.agent","position":[2280,380],"parameters":{"text":"={{ $json }}","options":{"maxIterations":5,"systemMessage":"=You are an editorial AI assistant. Your role is to review and evaluate a draft article represented as a JSON object.\n\nCategory IDs:\n- \"getting-started\"\n- \"learning-paths\"\n- \"certifications\"\n- \"programming\"\n- \"applications\"\n- \"career\"\n\nInput Format:\n\n{\n  \"title\": \"<string>\",\n  \"slug\": \"<string>\",\n  \"category\": { \"id\": \"<string>\" },\n  \"description\": \"<string>\",\n  \"keywords\": [\"<string>\", \"...\"],\n  \"content\": \"<string>\",\n  \"metaTitle\": \"<string>\",\n  \"metaDescription\": \"<string>\",\n  \"readingTime\": \"<string | number>\",\n  \"difficulty\": \"<string>\"\n}\n\nYour Tasks:\n\n1. Evaluate the quality of the article — especially the title, description, content, and metadata.\n2. Comment on clarity, specificity, usefulness, and overall quality.\n3. If improvements are needed, add an \"improvements\" field describing exactly what to fix.\n4. Set the \"action\" field:\n- \"rewrite\" if improvements are needed.\n- \"submit\" if the article is high quality.\n5. Include all fields from the original input in your output.\n6. If \"action\" is \"submit\", set \"improvements\" to null.\n7. Avoid repeating feedback across iterations.\n8. After 2 iterations, automatically call the accept-and-publish tool and set the \"action\" to \"submit\".\n9. VERY IMPORTANT: Do NOT modify any of the input fields\n10. VERY IMPORTANT: Do NOT truncate the sources or modify the content field in any way\n\n✅ Output Format:\n\n{\n  \"title\": \"...\",\n  \"action\": \"rewrite | submit\",\n  \"improvements\": \"... | null\",\n  \"slug\": \"...\",\n  \"category\": {\n    \"id\": \"...\"\n  },\n  \"description\": \"...\",\n  \"keywords\": [\"...\"],\n  \"content\": \"...\",\n  \"metaTitle\": \"...\",\n  \"metaDescription\": \"...\",\n  \"readingTime\": \"...\",\n  \"difficulty\": \"...\"\n}\n\nTone: Be concise, direct, and constructive. Focus on maximizing clarity, usefulness, and readability for the end reader."},"promptType":"define","hasOutputParser":true},"retryOnFail":true,"typeVersion":1.7},{"id":"8c08d67a-b916-4cfd-89cd-16b34250bcb2","name":"OpenAI Chat Model","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[680,640],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-3.5-turbo","cachedResultName":"gpt-3.5-turbo"},"options":{}},"credentials":{"openAiApi":{"id":"KLN8ZfDzv8mW6pyu","name":"OpenAi account"}},"typeVersion":1.2},{"id":"97bb1374-f502-4152-a5a3-bc5d46a18171","name":"OpenAI Chat Model1","type":"@n8n/n8n-nodes-langchain.lmChatOpenAi","position":[2200,660],"parameters":{"model":{"__rl":true,"mode":"list","value":"gpt-4.5-preview","cachedResultName":"gpt-4.5-preview"},"options":{}},"credentials":{"openAiApi":{"id":"KLN8ZfDzv8mW6pyu","name":"OpenAi account"}},"typeVersion":1.2},{"id":"1c422e76-5b1a-4615-b379-ab39d4bd13b4","name":"Accept and Publish","type":"@n8n/n8n-nodes-langchain.toolWorkflow","position":[2420,660],"parameters":{"name":"submit","workflowId":{"__rl":true,"mode":"list","value":"uIREtTV8TRuF3lru","cachedResultName":"Publish to Contentful"},"description":"Call this tool when the article quality is above the threshold we need","workflowInputs":{"value":{"slug":"=  {{ $json.slug }}","title":"={{ $('Format').item.json.title }}","content":"={{ $json.content }}","category":"={{ $json.category }}","keywords":"={{ $json.keywords }}","metaTitle":"={{ $json.metaTitle }}","difficulty":"={{ $json.difficulty }}","description":"={{ $json.description }}","readingTime":"={{ $json.readingTime }}","metaDescription":"={{ $json.metaDescription }}"},"schema":[{"id":"title","type":"string","display":true,"removed":false,"required":false,"displayName":"title","defaultMatch":false,"canBeUsedToMatch":true},{"id":"action","type":"string","display":true,"removed":true,"required":false,"displayName":"action","defaultMatch":false,"canBeUsedToMatch":true},{"id":"improvements","type":"string","display":true,"removed":true,"required":false,"displayName":"improvements","defaultMatch":false,"canBeUsedToMatch":true},{"id":"slug","type":"string","display":true,"removed":false,"required":false,"displayName":"slug","defaultMatch":false,"canBeUsedToMatch":true},{"id":"category","type":"object","display":true,"removed":false,"required":false,"displayName":"category","defaultMatch":false,"canBeUsedToMatch":true},{"id":"description","type":"string","display":true,"removed":false,"required":false,"displayName":"description","defaultMatch":false,"canBeUsedToMatch":true},{"id":"keywords","type":"array","display":true,"removed":false,"required":false,"displayName":"keywords","defaultMatch":false,"canBeUsedToMatch":true},{"id":"content","type":"string","display":true,"removed":false,"required":false,"displayName":"content","defaultMatch":false,"canBeUsedToMatch":true},{"id":"metaTitle","type":"string","display":true,"removed":false,"required":false,"displayName":"metaTitle","defaultMatch":false,"canBeUsedToMatch":true},{"id":"metaDescription","type":"string","display":true,"removed":false,"required":false,"displayName":"metaDescription","defaultMatch":false,"canBeUsedToMatch":true},{"id":"readingTime","type":"string","display":true,"removed":false,"required":false,"displayName":"readingTime","defaultMatch":false,"canBeUsedToMatch":true},{"id":"difficulty","type":"string","display":true,"removed":false,"required":false,"displayName":"difficulty","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false}},"typeVersion":2},{"id":"b6807e84-900a-48fc-a869-862824c62ba1","name":"When chat message received","type":"@n8n/n8n-nodes-langchain.chatTrigger","position":[-60,660],"webhookId":"7ed20abc-d8bc-4426-95f1-b9778c075ddf","parameters":{"public":true,"options":{},"initialMessages":"What topics should I write about?"},"typeVersion":1.1},{"id":"e3021ac4-9444-4689-af0c-a4bbd4729c35","name":"JSON.parse1","type":"n8n-nodes-base.code","position":[1320,120],"parameters":{"mode":"runOnceForEachItem","jsCode":"const outputText = $json.output;\n\n// Parse JSON from ChatGPT response\nconst parsedOutput = JSON.parse(outputText);\n\n// Return parsed object for next nodes\nreturn parsedOutput;"},"typeVersion":2},{"id":"d6a5eccd-c8e3-4ee0-beb0-7b8fc8428b91","name":"Merge","type":"n8n-nodes-base.merge","position":[1840,380],"parameters":{"numberInputs":3},"typeVersion":3},{"id":"8461a1a0-a984-4ec1-bc93-3a1a312caf55","name":"Format","type":"n8n-nodes-base.code","position":[2060,380],"parameters":{"jsCode":"// Get all items passed into this node as an array\nconst items = $input.all();\n\n// If you always have at least two items:\nconst firstItem = items[0].json;\nconst secondItem = items[1].json;\nconst thirdItem = items[2].json;\n\n// Overwrite the first item’s “content” with the second item’s “content”\nfirstItem.content = secondItem.content;\nfirstItem.iterationCount = thirdItem.iterationCount\n\n// Return a single new item containing the merged result\nreturn [\n  {\n    json: firstItem\n  }\n];"},"typeVersion":2},{"id":"9b9a82fb-990c-4be0-aee9-ed80f0631c28","name":"JSON.parse","type":"n8n-nodes-base.code","position":[3300,840],"parameters":{"mode":"runOnceForEachItem","jsCode":"const outputText = $json.output;\n\n// Parse JSON from ChatGPT response\nconst parsedOutput = JSON.parse(outputText);\n\n// Return parsed object for next nodes\nreturn parsedOutput;"},"typeVersion":2},{"id":"066dfd10-e97a-44a8-89e0-5b2b5c4a6244","name":"Format Perplexity Output & Add Citations","type":"n8n-nodes-base.code","position":[1480,380],"parameters":{"mode":"runOnceForEachItem","jsCode":"const data = { ...$json };\n\n// Clean out <think> block if present\ndata.content = $json.choices[0].message.content.replace(/<think>[\\s\\S]*?<\\/think>/g, '').trim();\n\n// Convert citations array to markdown link list\nconst citations = $json.citations\n  .map((url, i) => `- [${i + 1}](${url})`)\n  .join('\\n');\n\ndata.content += `\\n\\n---\\n\\n### Sources\\n${citations}`;\n\nreturn data;"},"typeVersion":2},{"id":"fc9d9e93-568a-41af-a295-8736617b157e","name":"Initialize Count","type":"n8n-nodes-base.set","position":[340,660],"parameters":{"values":{"number":[{"name":"iterationCount"}]},"options":{}},"typeVersion":1},{"id":"b0e636dd-f572-4ee2-832f-4bb4167b011b","name":"Increment Count","type":"n8n-nodes-base.function","position":[1400,740],"parameters":{"functionCode":"const current = $json.iterationCount || 0;\n\nreturn [{ iterationCount: current + 1 }];"},"typeVersion":1},{"id":"86689f0e-9607-4eda-bcd6-36241d0cbe63","name":"Check Limit","type":"n8n-nodes-base.if","position":[2660,380],"parameters":{"conditions":{"number":[{"value1":"={{ $json.iterationCount }}","value2":3,"operation":"largerEqual"}]}},"typeVersion":1},{"id":"0f42b0f3-0b96-40df-9c06-2a6d31e142d9","name":"Stop Here","type":"n8n-nodes-base.noOp","position":[2980,300],"parameters":{},"typeVersion":1},{"id":"1e61b3ba-e9bd-43c7-8217-d4a22f707318","name":"Execute Workflow","type":"n8n-nodes-base.executeWorkflow","position":[3680,320],"parameters":{"options":{},"workflowId":{"__rl":true,"mode":"list","value":"uIREtTV8TRuF3lru","cachedResultName":"Publish to Contentful"},"workflowInputs":{"value":{"slug":"={{ $json.slug }}","title":"={{ $json.title }}","content":"={{ $json.content }}","category":"={{ $json.category }}","keywords":"={{ $json.keywords }}","metaTitle":"={{ $json.metaTitle }}","difficulty":"={{ $json.difficulty }}","description":"={{ $json.description }}","readingTime":"={{ $json.readingTime }}","metaDescription":"={{ $json.metaDescription }}"},"schema":[{"id":"title","type":"string","display":true,"required":false,"displayName":"title","defaultMatch":false,"canBeUsedToMatch":true},{"id":"slug","type":"string","display":true,"required":false,"displayName":"slug","defaultMatch":false,"canBeUsedToMatch":true},{"id":"category","type":"object","display":true,"required":false,"displayName":"category","defaultMatch":false,"canBeUsedToMatch":true},{"id":"description","type":"string","display":true,"required":false,"displayName":"description","defaultMatch":false,"canBeUsedToMatch":true},{"id":"keywords","type":"array","display":true,"required":false,"displayName":"keywords","defaultMatch":false,"canBeUsedToMatch":true},{"id":"content","type":"string","display":true,"required":false,"displayName":"content","defaultMatch":false,"canBeUsedToMatch":true},{"id":"metaTitle","type":"string","display":true,"required":false,"displayName":"metaTitle","defaultMatch":false,"canBeUsedToMatch":true},{"id":"metaDescription","type":"string","display":true,"required":false,"displayName":"metaDescription","defaultMatch":false,"canBeUsedToMatch":true},{"id":"readingTime","type":"string","display":true,"required":false,"displayName":"readingTime","defaultMatch":false,"canBeUsedToMatch":true},{"id":"difficulty","type":"string","display":true,"required":false,"displayName":"difficulty","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":true}},"typeVersion":1.2},{"id":"04526224-e48c-4032-9c92-475c6bf9cd0a","name":"JSON.parse3","type":"n8n-nodes-base.code","position":[3280,300],"parameters":{"mode":"runOnceForEachItem","jsCode":"const outputText = $json.output;\n\n// Parse JSON from ChatGPT response\nconst parsedOutput = JSON.parse(outputText);\n\n// Return parsed object for next nodes\nreturn parsedOutput;"},"typeVersion":2},{"id":"0303ab91-b788-4074-a15d-239565528dec","name":"should submit","type":"n8n-nodes-base.if","position":[2980,680],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"3c1f6cb2-a556-4c74-885e-05e4f757997b","operator":{"name":"filter.operator.equals","type":"string","operation":"equals"},"leftValue":"submit","rightValue":"={{ $json.output.parseJson().action }}"}]}},"typeVersion":2.2},{"id":"4f68e1ad-0b7c-41ca-a9cc-d220017cc1bd","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[540,80],"parameters":{"width":940,"height":680,"content":"## Writer Agent\n\n- Focuses on writing for all the fields in contentful\n- Has a specified format for input and output\n- Handles implementing feedback from editor agent"},"typeVersion":1},{"id":"f5e9968a-0681-4b72-9bb3-98750f55565e","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[2620,120],"parameters":{"width":860,"height":880,"content":"## Count Incrementer\n\n- Tracks a variable count to ensure the flow hits a max number of feedback iterations.\n- This is critical for feedback to avoid hitting an infinite loop."},"typeVersion":1},{"id":"462e6c9a-162a-4f66-b846-14b29517454c","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[2120,160],"parameters":{"width":460,"height":640,"content":"## Editor Agent\n\n- Sole purpose is to look at the quality of output for the previous combo of perplexity & openAI Agent.\n- Determines if it is publishable or not."},"typeVersion":1},{"id":"27149a0a-ab95-4f73-a20c-34167ac56d2a","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[3520,60],"parameters":{"width":460,"height":480,"content":"## Publish To Contentful\n\n- Publishes to Contentful by converting the fields to the appropriate fields for the contentful POST create content API.\n- Converts the article to Rich Text formatting specifically for Contentful by using another AI formatter trained on it's specs.\n\nemail us if you want that flow too: christian@varritech.com"},"typeVersion":1},{"id":"3b6386cc-7965-47ae-88e3-8841ada43579","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[-220,-180],"parameters":{"color":3,"width":720,"height":780,"content":"# Varritech Free Workflows for n8n\n\nAccess our collection of **free, ready-to-use n8n workflows** designed to streamline your data operations and automation needs.\n\n## What You'll Get:\n- Pre-built data integration workflows\n- Time-saving automation templates\n- Step-by-step implementation guides\n- Regular updates with new workflows\n\n## How to Use:\n1. Download our free workflows\n2. Import into your n8n instance\n3. Configure with your credentials\n4. Start automating immediately\n\n**Note:** For additional workflows, tutorials, and technical support, visit our website at [varritech.com](https://varritech.com)\n\n*Unlock the full potential of n8n with Varritech's workflow solutions!*"},"typeVersion":1},{"id":"376c9b0d-8ef6-4681-900d-745d4353cc83","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[3520,580],"parameters":{"color":4,"width":820,"height":600,"content":"# Need Help Maximizing Your Knowledge Base?\n\nThank you for using our **Auto Knowledge Base Article Generator** workflow! While this automation handles content creation, building a truly effective knowledge base requires strategic planning:\n\n## Looking for Expert Support With:\n- **Database Setup** - Need help structuring your content database for optimal performance and scalability?\n- **Keyword Strategy** - Want to identify high-value search terms that attract qualified traffic?\n- **SEO Optimization** - Need assistance implementing technical SEO best practices for maximum visibility?\n- **Backlink Outreach** - Looking for strategies to build authoritative backlinks to your knowledge base?\n\n**Varritech is here to help!** Our team specializes in content automation, technical SEO, and strategic content distribution to maximize the impact of your knowledge base.\n\nVisit [varritech.com](https://varritech.com) for expert assistance with turning your automated content into a powerful business asset."},"typeVersion":1}],"active":true,"pinData":{},"settings":{"callerPolicy":"workflowsFromSameOwner","errorWorkflow":"AKus9UvKRJfvTZBA","executionOrder":"v1","executionTimeout":1800},"versionId":"b4259335-d41f-4671-b103-5b4e9609089b","connections":{"Merge":{"main":[[{"node":"Format","type":"main","index":0}]]},"Format":{"main":[[{"node":"AI Editor Agent","type":"main","index":0}]]},"Stop Here":{"main":[[{"node":"JSON.parse3","type":"main","index":0}]]},"JSON.parse":{"main":[[{"node":"AI Writer Agent","type":"main","index":0},{"node":"Increment Count","type":"main","index":0}]]},"Check Limit":{"main":[[{"node":"Stop Here","type":"main","index":0}],[{"node":"should submit","type":"main","index":0}]]},"JSON.parse1":{"main":[[{"node":"Merge","type":"main","index":0}]]},"JSON.parse3":{"main":[[{"node":"Execute Workflow","type":"main","index":0}]]},"should submit":{"main":[[{"node":"JSON.parse3","type":"main","index":0}],[{"node":"JSON.parse","type":"main","index":0}]]},"AI Editor Agent":{"main":[[{"node":"Check Limit","type":"main","index":0}]]},"AI Writer Agent":{"main":[[{"node":"Create Perplexity Content","type":"main","index":0},{"node":"JSON.parse1","type":"main","index":0}]]},"Increment Count":{"main":[[{"node":"Merge","type":"main","index":2}]]},"Initialize Count":{"main":[[{"node":"AI Writer Agent","type":"main","index":0},{"node":"Increment Count","type":"main","index":0}]]},"OpenAI Chat Model":{"ai_languageModel":[[{"node":"AI Writer Agent","type":"ai_languageModel","index":0}]]},"Accept and Publish":{"ai_tool":[[{"node":"AI Editor Agent","type":"ai_tool","index":0}]]},"OpenAI Chat Model1":{"ai_languageModel":[[{"node":"AI Editor Agent","type":"ai_languageModel","index":0}]]},"Create Perplexity Content":{"main":[[{"node":"Format Perplexity Output & Add Citations","type":"main","index":0}]]},"When chat message received":{"main":[[{"node":"Initialize Count","type":"main","index":0}]]},"Format Perplexity Output & Add Citations":{"main":[[{"node":"Merge","type":"main","index":1}]]}}}


