{
  "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
          }
        ]
      ]
    }
  }
}