This workflow corresponds to n8n.io template #12714 — we link there as the canonical source.
This workflow follows the Agent → Gmail recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "X4idZqm7o2a1BYH_6hM_d",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Curate and send AI newsletters with Tavily and Gemini",
"tags": [],
"nodes": [
{
"id": "016c5451-563a-477e-8e22-b9d99e660ca5",
"name": "Weekly schedule trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1680,
624
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": 7,
"triggerAtHour": 9,
"triggerAtMinute": 30
}
]
}
},
"typeVersion": 1.3
},
{
"id": "e2e4c91f-492d-41f0-9780-6004205ea4fe",
"name": "Set newsletter config",
"type": "n8n-nodes-base.set",
"position": [
1888,
624
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "60bd8e76-aad8-46ad-9e4a-499ea78b75aa",
"name": "topic",
"type": "string",
"value": "AI Sales Agents"
},
{
"id": "dfa69b2c-2ff1-4a29-9dff-535630af7aa6",
"name": "company_blog_url",
"type": "string",
"value": "https://aisalesagenthq.scoot.app/"
},
{
"id": "e1618443-25be-4035-a588-358a87d0d499",
"name": "logo_url",
"type": "string",
"value": "https://aisalesagenthq.scoot.app/content/images/size/w256h256/2025/10/Preciate-AppIcon-256x256.png"
},
{
"id": "d21e1fd4-0826-403b-b893-5ba443c04b5c",
"name": "subscribe_url",
"type": "string",
"value": "https://aisalesagenthq.scoot.app/"
},
{
"id": "62741640-54c4-4a87-8499-cd0482b75f56",
"name": "newsletter_name",
"type": "string",
"value": "AI Sales Agent HQ"
},
{
"id": "7ba3ed5d-e943-4ace-97ed-313e0909ef58",
"name": "author_name",
"type": "string",
"value": "The AI Team"
},
{
"id": "9b6e6400-3061-44f3-9d2b-501520c9e2ce",
"name": "email_title",
"type": "string",
"value": "Weekly Update: AI Sales Agents"
},
{
"id": "ffeac650-26a9-4887-99c6-64d224c83259",
"name": "header_internal",
"type": "string",
"value": "New this Week from AI Sales Agent HQ"
},
{
"id": "ef1783d7-7427-4185-9c61-1d9baa65123c",
"name": "header_external",
"type": "string",
"value": "AI News"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "030cd0b3-9f5e-4c75-a489-b76f75bc097b",
"name": "Generate newsletter content",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
3520,
624
],
"parameters": {
"text": "=Raw Search Data: {{ JSON.stringify($('Aggregate all articles').first().json.research_data) }}\n\nTask: I have provided a list of search results.\n\nIdentify Internal Updates: Look for results from the domain {{ $('Set newsletter config').item.json.company_blog_url }}. Summarize these into the product_section JSON field.\n\nIdentify Market News: Look for all other results (NOT from that domain). Summarize these into the news_section JSON field.\"",
"options": {
"systemMessage": "=You are the editor of '{{ $('Set newsletter config').item.json.newsletter_name }}'. You write in a professional, narrative style.\n\n**Output Format:**\nReturn a SINGLE valid JSON object. Do not wrap in markdown.\n\n**1. intro_text**\nA warm, 2-paragraph introduction. Wrap each paragraph in <p> tags.\n\n**2. product_section**\nA HTML string containing your internal updates.\n- Start with the header: <h3>{{ $('Set newsletter config').item.json.header_internal }}</h3>\n- If internal updates are found, format them as a numbered list:\n <h4>1. [Product Name]</h4>\n <p>[Description]</p>\n <p><strong>Why it matters:</strong> [Benefit]</p>\n- If NO internal updates are found, return an empty string \"\".\n\n**3. news_section**\nA HTML string containing market news.\n- Start with the header: <h3>{{ $('Set newsletter config').item.json.header_external }}</h3>\n- List 3-4 key stories using a clean unordered list:\n <ul>\n <li><strong>[Headline]:</strong> [Summary].</li>\n </ul>"
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "1ea0bd18-8885-4812-b389-63e0bf557c7e",
"name": "Send newsletter (Gmail)",
"type": "n8n-nodes-base.gmail",
"position": [
4144,
624
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{ $json.html_email }}",
"options": {},
"subject": "=Weekly AI Update: {{ $('Set newsletter config').item.json.topic }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "fae4711b-db62-4865-a3b3-7de0c80a62a4",
"name": "Load email template",
"type": "n8n-nodes-base.code",
"position": [
3264,
624
],
"parameters": {
"jsCode": "// 1. GET TEMPLATE\nconst template = `\n<!doctype html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width\">\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n <style>\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background-color: #ffffff; color: #15212A; line-height: 1.6; margin: 0; padding: 0; }\n .main-container { max-width: 600px; margin: 0 auto; padding: 20px; }\n .header { text-align: center; padding-bottom: 30px; }\n .site-title { color: #15212A; font-weight: 700; text-transform: uppercase; font-size: 16px; text-decoration: none; letter-spacing: -0.1px; }\n .post-title { font-size: 32px; line-height: 1.2; font-weight: 700; color: #000000; margin-top: 10px; margin-bottom: 10px; text-align: center; }\n .meta { color: rgba(0, 0, 0, 0.6); font-size: 13px; text-align: center; margin-bottom: 40px; }\n .content { font-size: 17px; color: #15212A; }\n .content p { margin: 0 0 1.5em 0; }\n .content a { color: #0c58c6; text-decoration: underline; }\n .content h3 { font-size: 24px; font-weight: 700; margin-top: 40px; margin-bottom: 20px; color: #15212A; }\n .content h4 { font-size: 18px; font-weight: 700; margin-top: 25px; margin-bottom: 10px; color: #15212A; }\n .content ul { padding-left: 20px; margin-bottom: 1.5em; }\n .content li { margin-bottom: 10px; }\n .btn { display: inline-block; background-color: #0c58c6; color: #ffffff; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 600; font-size: 16px; margin-top: 20px; }\n .footer { text-align: center; font-size: 13px; color: rgba(0, 0, 0, 0.6); margin-top: 60px; border-top: 1px solid #e0e7eb; padding-top: 30px; }\n </style>\n</head>\n<body>\n <div class=\"main-container\">\n <div class=\"header\">\n <img src=\"{{LOGO_URL}}\" width=\"48\" style=\"width: 48px; max-width: 48px; height: auto; border-radius: 4px; margin-bottom: 12px; display: block; margin-left: auto; margin-right: auto;\">\n <br>\n <a href=\"#\" class=\"site-title\">{{NEWSLETTER_NAME}}</a>\n \n <div class=\"post-title\">{{EMAIL_TITLE}}</div>\n \n <div class=\"meta\">By {{AUTHOR}} \u2022 {{DATE}}</div>\n </div>\n <div class=\"content\">\n {{INTRO_TEXT}}\n <hr style=\"border: 0; border-top: 1px solid #e0e7eb; margin: 40px 0;\">\n {{PRODUCT_SECTION}}\n {{NEWS_SECTION}}\n <div style=\"text-align: center; margin-top: 50px; margin-bottom: 30px;\">\n <a href=\"{{SUBSCRIBE_URL}}\" class=\"btn\" style=\"color: #ffffff !important; text-decoration: none;\">Subscribe</a>\n </div>\n </div>\n <div class=\"footer\">\n <p>{{NEWSLETTER_NAME}} \u00a9 2026</p>\n <p><a href=\"#\" style=\"color: inherit; text-decoration: underline;\">Unsubscribe</a></p>\n </div>\n </div>\n</body>\n</html>\n`;\n\n// 2. PASS THE TEMPLATE\nreturn [{ json: { html_template: template } }];"
},
"typeVersion": 2
},
{
"id": "c421ab4e-b401-4430-ae32-d6eb84ae32c5",
"name": "Build final email",
"type": "n8n-nodes-base.code",
"position": [
3872,
624
],
"parameters": {
"jsCode": "// 1. GET TEMPLATE & CONFIG\nconst template = $('Load email template').first().json.html_template;\nconst config = $('Set newsletter config').first().json;\n\n// 2. CLEAN AI OUTPUT\nlet aiRaw = $('Generate newsletter content').first().json.output;\nif (typeof aiRaw === 'string') {\n aiRaw = aiRaw.replace(/```json/g, '').replace(/```/g, '').trim();\n}\n\nlet aiData;\ntry {\n aiData = typeof aiRaw === 'object' ? aiRaw : JSON.parse(aiRaw);\n} catch (e) {\n aiData = { \n intro_text: \"<p>Here is your weekly update.</p>\", \n product_section: \"\",\n news_section: aiRaw \n };\n}\n\n// 3. GET DATE\nconst today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });\n\n// 4. MERGE VARIABLES (The Magic Step)\nlet finalEmail = template\n .replace('{{LOGO_URL}}', config.logo_url)\n .replace('{{SUBSCRIBE_URL}}', config.subscribe_url)\n .replace('{{DATE}}', today)\n \n // NEW: Replace the 3 Text Fields\n .replace(/{{NEWSLETTER_NAME}}/g, config.newsletter_name) // Global replace in header & footer\n .replace('{{EMAIL_TITLE}}', config.email_title)\n .replace('{{AUTHOR}}', config.author_name)\n \n // Content Sections\n .replace('{{INTRO_TEXT}}', aiData.intro_text || \"\")\n .replace('{{PRODUCT_SECTION}}', aiData.product_section || \"\") \n .replace('{{NEWS_SECTION}}', aiData.news_section || \"\");\n\n// 5. OUTPUT\nreturn [{ json: { html_email: finalEmail } }];"
},
"typeVersion": 2
},
{
"id": "b00176eb-82f0-45e3-af6a-11226a14a12c",
"name": "Search company blog (Tavily)",
"type": "@tavily/n8n-nodes-tavily.tavily",
"position": [
2224,
768
],
"parameters": {
"query": "=site:{{ $('Set newsletter config').item.json.company_blog_url }} \"news\" OR \"update\" OR \"launch\" OR \"release\" OR \"template\" OR \"New\"",
"options": {
"days": 7,
"topic": "general",
"max_results": 5,
"search_depth": "advanced"
}
},
"credentials": {
"tavilyApi": {
"name": "<your credential>"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "e51124a2-db62-4151-99d7-79387729c1ca",
"name": "Search external news (Tavily)",
"type": "@tavily/n8n-nodes-tavily.tavily",
"position": [
2224,
512
],
"parameters": {
"query": "={{ $json.topic }}",
"options": {
"days": 7,
"topic": "news",
"max_results": 5,
"search_depth": "advanced"
}
},
"credentials": {
"tavilyApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ac850d71-fe32-4c12-bc37-d28633304ff5",
"name": "Combine search results",
"type": "n8n-nodes-base.merge",
"position": [
2736,
624
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": false
},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "d6d88dc5-b524-4f33-9e5d-616ad729503a",
"name": "Aggregate all articles",
"type": "n8n-nodes-base.code",
"position": [
2944,
624
],
"parameters": {
"jsCode": "// DIRECT ACCESS SCRIPT\n// This ignores the Merge/Rename nodes and grabs clean data directly from the source.\n\nlet allArticles = [];\n\n// 1. Grab Global News directly from the search node\ntry {\n // We access the 'Search external news (Tavily)' node directly\n const newsItems = $('Search external news (Tavily)').all();\n \n // Loop through results in case there are multiple items\n for (const item of newsItems) {\n if (item.json.results && Array.isArray(item.json.results)) {\n allArticles = allArticles.concat(item.json.results);\n }\n }\n} catch (error) {\n // If the node didn't run, just skip it\n console.log(\"News search data not found\");\n}\n\n// 2. Grab Internal Blog Posts directly from the search node\ntry {\n // We access the 'Search company blog (Tavily)' node directly\n const blogItems = $('Search company blog (Tavily)').all();\n \n for (const item of blogItems) {\n if (item.json.results && Array.isArray(item.json.results)) {\n allArticles = allArticles.concat(item.json.results);\n }\n }\n} catch (error) {\n // If the node didn't run, just skip it\n console.log(\"Blog search data not found\");\n}\n\n// 3. Output the combined list\nreturn [{ json: { research_data: allArticles } }];"
},
"typeVersion": 2
},
{
"id": "fbc79854-e759-4886-9e0e-c3911d7aee4d",
"name": "Gemini 1.5 Flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
3392,
832
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "79cb9b73-39aa-4e3a-94c0-dd5890270186",
"name": "Extract news results",
"type": "n8n-nodes-base.set",
"position": [
2448,
512
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3dd7c376-09e7-4729-ad71-ebcffc4b8e0e",
"name": "news_results",
"type": "string",
"value": "={{ $json.results }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3af5b005-ad64-4742-8c28-68dbd07b62ac",
"name": "Extract blog results",
"type": "n8n-nodes-base.set",
"position": [
2448,
768
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "3dd7c376-09e7-4729-ad71-ebcffc4b8e0e",
"name": "blog_results",
"type": "string",
"value": "={{ $json.results }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ff3e458e-8fe7-44c6-881e-f73c07200344",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
448
],
"parameters": {
"color": 5,
"width": 400,
"height": 560,
"content": "## How it works\nThis workflow automatically curates and sends a weekly AI newsletter by combining internal blog posts with external news.\n\n1. **Trigger** - Runs weekly on a schedule (configurable)\n2. **Research** - Tavily searches your company blog and external news for your topic\n3. **AI Writing** - Gemini generates a professional newsletter with intro, internal updates, and market news\n4. **Send** - Gmail delivers the formatted HTML email to your subscribers\n\n## Setup steps\n1. Open **Set newsletter config** node to customize: topic, newsletter name, logo URL, blog URL\n2. Add your **Tavily API** credentials (get key at tavily.com)\n3. Add your **Google Gemini API** credentials\n4. Add your **Gmail OAuth** credentials\n5. Update the recipient email in **Send newsletter** node\n6. Test with manual execution before enabling the schedule"
},
"typeVersion": 1
},
{
"id": "582729d7-79ae-424a-b802-23ff191cb9de",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2176,
400
],
"parameters": {
"color": 7,
"width": 480,
"height": 80,
"content": "**Research Phase**\nSearches your blog and external news sources in parallel using Tavily"
},
"typeVersion": 1
},
{
"id": "5f3e80ea-6206-4127-989e-2520f4a08d10",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3376,
512
],
"parameters": {
"color": 7,
"width": 320,
"height": 80,
"content": "**AI Generation**\nGemini writes the newsletter content in structured HTML format"
},
"typeVersion": 1
},
{
"id": "cf72ee23-9ebf-4b2c-81f5-82daa592c642",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
3776,
512
],
"parameters": {
"color": 7,
"width": 400,
"height": 80,
"content": "**Email Output**\nMerges AI content with HTML template and sends via Gmail"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "eba523d8-705d-470d-9c76-0180b11476c9",
"connections": {
"Gemini 1.5 Flash": {
"ai_languageModel": [
[
{
"node": "Generate newsletter content",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Build final email": {
"main": [
[
{
"node": "Send newsletter (Gmail)",
"type": "main",
"index": 0
}
]
]
},
"Load email template": {
"main": [
[
{
"node": "Generate newsletter content",
"type": "main",
"index": 0
}
]
]
},
"Extract blog results": {
"main": [
[
{
"node": "Combine search results",
"type": "main",
"index": 1
}
]
]
},
"Extract news results": {
"main": [
[
{
"node": "Combine search results",
"type": "main",
"index": 0
}
]
]
},
"Set newsletter config": {
"main": [
[
{
"node": "Search external news (Tavily)",
"type": "main",
"index": 0
},
{
"node": "Search company blog (Tavily)",
"type": "main",
"index": 0
}
]
]
},
"Aggregate all articles": {
"main": [
[
{
"node": "Load email template",
"type": "main",
"index": 0
}
]
]
},
"Combine search results": {
"main": [
[
{
"node": "Aggregate all articles",
"type": "main",
"index": 0
}
]
]
},
"Weekly schedule trigger": {
"main": [
[
{
"node": "Set newsletter config",
"type": "main",
"index": 0
}
]
]
},
"Generate newsletter content": {
"main": [
[
{
"node": "Build final email",
"type": "main",
"index": 0
}
]
]
},
"Search company blog (Tavily)": {
"main": [
[
{
"node": "Extract blog results",
"type": "main",
"index": 0
}
]
]
},
"Search external news (Tavily)": {
"main": [
[
{
"node": "Extract news results",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
gmailOAuth2googlePalmApitavilyApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automatically curates and sends a weekly newsletter by combining your internal blog posts with external news. The workflow researches your topic using Tavily, has Gemini write professional newsletter content, and delivers a beautifully formatted HTML email via Gmail. Weekly…
Source: https://n8n.io/workflows/12714/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
LinkedIn_Job_Hunt_and_Cover_Letter. Uses outputParserStructured, outputParserAutofixing, googleDrive, agent. Scheduled trigger; 85 nodes.
The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe
Who Is This For?
V2 (2026) available! An intelligent, fully automated news aggregation system that collects articles from multiple sources (RSS feeds + Google Search), uses AI to classify and summarize the most import
How it works This workflow acts like your own personal AI assistant, automatically fetching and summarizing the most relevant Security, Privacy, and Compliance news from curated RSS feeds. It processe