AutomationFlowsAI & RAG › Research Web Topics and Email a Claude Report via Gmail Using Serpapi,…

Research Web Topics and Email a Claude Report via Gmail Using Serpapi,…

Original n8n title: Research Web Topics and Email a Claude Report via Gmail Using Serpapi, Jina.ai and Firecrawl

BySurya Vardhan Yalavarthi @suryayalavarthi on n8n.io

Submit a research topic through a form and receive a professionally styled executive report in your inbox — fully automated, with built-in scraping resilience.

Event trigger★★★★★ complexityAI-powered35 nodesForm TriggerHTTP RequestChain LlmAnthropic ChatGmailError Trigger
AI & RAG Trigger: Event Nodes: 35 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #13608 — we link there as the canonical source.

This workflow follows the Chainllm → Form Trigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Research any topic and email an AI report with self-healing scraper",
  "tags": [],
  "nodes": [
    {
      "id": "p-main-sticky",
      "name": "Main Guide",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 380,
        "height": 532,
        "content": "## Research any topic and email an AI report with self-healing scraper\n\n## How it works\n1. Submit a research topic via the form (topic, number of sources, recipient email)\n2. SerpApi searches Google and returns results\n3. Junk domains (Reddit, YouTube, PDFs) are filtered out\n4. Each URL is processed **one at a time** in a loop:\n   - Jina.ai scrapes the page (free, no key needed)\n   - Claude analyzes the content and extracts key findings\n   - If a CAPTCHA or block is detected, Firecrawl retries automatically\n   - If still blocked after 2 attempts, the source is marked as unavailable\n5. All findings are aggregated and Claude writes a structured executive report\n6. The report is converted to styled HTML and emailed to the recipient\n\n## Setup steps\n1. **SerpApi** \u2014 sign up at serpapi.com, paste your API key into the `SerpApi Search` node (query param `api_key`)\n2. **Firecrawl** \u2014 sign up at firecrawl.dev, paste your key into the `Firecrawl` node (`Authorization: Bearer YOUR_KEY`)\n3. **Anthropic** \u2014 create an Anthropic API credential in n8n and connect it to the three Claude nodes\n4. **Gmail** \u2014 create a Gmail OAuth2 credential in n8n and connect it to `Send Gmail`"
      },
      "typeVersion": 1
    },
    {
      "id": "p-err-note",
      "name": "Error Handler Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        464
      ],
      "parameters": {
        "color": 5,
        "width": 556,
        "height": 388,
        "content": "## Error handler\nCatches any workflow failure and extracts the error message, node name, and execution URL.\n\n**To enable:** Workflow Settings \u2192 Error Workflow \u2192 select this workflow.\n\n**Extend:** Add a Slack or Gmail node after `Format Error` to receive failure alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "p-s1",
      "name": "S1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 804,
        "height": 80,
        "content": "**1 \u2014 Input & search**"
      },
      "typeVersion": 1
    },
    {
      "id": "p-s2",
      "name": "S2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 1480,
        "height": 80,
        "content": "**2 \u2014 Self-healing loop** (one URL at a time \u00b7 output[0]=done \u2192 Aggregate \u00b7 output[1]=loop body \u00b7 Enrich feeds back)"
      },
      "typeVersion": 1
    },
    {
      "id": "p-s3",
      "name": "S3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2464,
        -80
      ],
      "parameters": {
        "color": 5,
        "width": 1252,
        "height": 80,
        "content": "**3 \u2014 Firecrawl fallback** (only runs if Jina is blocked)"
      },
      "typeVersion": 1
    },
    {
      "id": "p-s4",
      "name": "S4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        -80
      ],
      "parameters": {
        "color": 7,
        "width": 1128,
        "height": 80,
        "content": "**4 \u2014 Synthesis & delivery**"
      },
      "typeVersion": 1
    },
    {
      "id": "p1",
      "name": "Research Form",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        32,
        128
      ],
      "parameters": {
        "options": {},
        "formTitle": "Self-Healing Research Agent",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Research Topic",
              "placeholder": "e.g. Best project management tools 2025",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Number of Sources",
              "fieldOptions": {
                "values": [
                  {
                    "option": "5"
                  },
                  {
                    "option": "6"
                  },
                  {
                    "option": "7"
                  }
                ]
              },
              "requiredField": true
            },
            {
              "fieldType": "email",
              "fieldLabel": "Recipient Email",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Enter a research topic to receive an AI-synthesized executive report by email."
      },
      "typeVersion": 2.5
    },
    {
      "id": "p2",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "topic",
              "type": "string",
              "value": "={{ $json['Research Topic'] }}"
            },
            {
              "id": "a2",
              "name": "numSources",
              "type": "number",
              "value": "={{ parseInt($json['Number of Sources']) || 5 }}"
            },
            {
              "id": "a3",
              "name": "recipientEmail",
              "type": "string",
              "value": "={{ $json['Recipient Email'] }}"
            },
            {
              "id": "a4",
              "name": "runId",
              "type": "string",
              "value": "={{ $now.toFormat('yyyyMMddHHmmss') }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "p3",
      "name": "SerpApi Search",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        464,
        128
      ],
      "parameters": {
        "url": "https://serpapi.com/search",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.topic }}"
            },
            {
              "name": "num",
              "value": "={{ $json.numSources * 2 + 3 }}"
            },
            {
              "name": "engine",
              "value": "google"
            },
            {
              "name": "api_key",
              "value": "YOUR_SERPAPI_KEY"
            }
          ]
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "p4",
      "name": "Parse & Filter URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        128
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst config = $('Set Config').first().json;\nconst SKIP = ['.pdf','twitter.com','x.com','facebook.com','instagram.com','tiktok.com','youtube.com','reddit.com'];\nconst results = (data.organic_results || []).filter(r => !SKIP.some(s => (r.link || '').toLowerCase().includes(s))).slice(0, config.numSources);\nif (results.length === 0) throw new Error('No valid URLs found for topic: ' + config.topic);\nreturn results.map((r, i) => ({json:{urlIndex:i+1,url:r.link,title:r.title||'',snippet:r.snippet||'',topic:config.topic,recipientEmail:config.recipientEmail,runId:config.runId}}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "p5",
      "name": "Loop Over URLs",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        912,
        128
      ],
      "parameters": {
        "options": {},
        "batchSize": 1
      },
      "typeVersion": 3
    },
    {
      "id": "p6",
      "name": "Jina Reader (Primary)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1120,
        128
      ],
      "parameters": {
        "url": "=https://r.jina.ai/{{ $json.url }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "text/plain"
            },
            {
              "name": "X-Return-Format",
              "value": "markdown"
            },
            {
              "name": "X-Timeout",
              "value": "25"
            }
          ]
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "p7",
      "name": "Normalize Jina Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1344,
        128
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst orig = $('Loop Over URLs').first().json;\nconst raw = item.body || item.data || '';\nreturn [{json:{...orig,content:raw.substring(0,14000),contentLength:raw.length,scrapeMethod:'jina',scrapeOk:!item.error&&raw.length>300}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p8",
      "name": "Page Analyzer (Round 1)",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1568,
        128
      ],
      "parameters": {
        "text": "=Analyze this webpage for a research report on: \"{{ $json.topic }}\"\n\nSOURCE URL: {{ $json.url }}\n\nPAGE CONTENT:\n{{ $json.content }}\n\nINSTRUCTIONS:\n- If this page is a CAPTCHA challenge, login wall, access denied page, or has fewer than 150 words of real content, respond with EXACTLY: RETRY_NEEDED\n- Otherwise respond with ONLY this JSON (no markdown wrapper):\n{\"status\":\"ok\",\"title\":\"page title\",\"summary\":\"2-3 sentence summary\",\"keyPoints\":[\"point1\",\"point2\",\"point3\"],\"dataPoints\":[\"stat or fact\"],\"relevance\":\"high|medium|low\"}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "p8a",
      "name": "Claude Extractor",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        1568,
        304
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "claude-sonnet-4-5"
        },
        "options": {
          "temperature": 0.1,
          "maxTokensToSample": 1024
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "pCA1",
      "name": "Combine Analysis R1",
      "type": "n8n-nodes-base.code",
      "position": [
        1856,
        128
      ],
      "parameters": {
        "jsCode": "const text = $input.first().json.text;\nconst orig = $('Normalize Jina Response').first().json;\nreturn [{json:{...orig,text}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p9",
      "name": "Retry Needed? (IF)",
      "type": "n8n-nodes-base.if",
      "position": [
        2000,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "retry-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ ($json.text || '').trim() }}",
              "rightValue": "RETRY_NEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "p16",
      "name": "Merge Page Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        2000,
        352
      ],
      "parameters": {
        "mode": "append"
      },
      "typeVersion": 3.2
    },
    {
      "id": "p17",
      "name": "Enrich & Forward",
      "type": "n8n-nodes-base.code",
      "position": [
        2224,
        352
      ],
      "parameters": {
        "jsCode": "const r = $input.first().json;\nreturn [{json:{text:r.text||'{\"status\":\"error\",\"summary\":\"Analysis unavailable\",\"keyPoints\":[],\"dataPoints\":[],\"relevance\":\"none\"}',url:r.url||'',urlIndex:r.urlIndex||0,title:r.title||'',snippet:r.snippet||'',topic:r.topic||'',recipientEmail:r.recipientEmail||'',runId:r.runId||'',scrapeMethod:r.scrapeMethod||'unknown'}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p10",
      "name": "Wait Before Retry",
      "type": "n8n-nodes-base.wait",
      "position": [
        2480,
        128
      ],
      "parameters": {
        "amount": 3
      },
      "typeVersion": 1.1
    },
    {
      "id": "p11",
      "name": "Firecrawl (Fallback)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        2704,
        128
      ],
      "parameters": {
        "url": "https://api.firecrawl.dev/v1/scrape",
        "method": "POST",
        "options": {
          "timeout": 45000
        },
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "url",
              "value": "={{ $json.url }}"
            },
            {
              "name": "formats",
              "value": "=[\"markdown\"]"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "p12",
      "name": "Normalize Firecrawl",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        128
      ],
      "parameters": {
        "jsCode": "const item = $input.first().json;\nconst orig = $('Combine Analysis R1').first().json;\nconst md = (item.data && item.data.markdown) || item.markdown || '';\nreturn [{json:{...orig,content:md.substring(0,14000),scrapeMethod:'firecrawl',scrapeOk:md.length>300&&!item.error}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p13",
      "name": "Page Analyzer (Round 2)",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        3152,
        128
      ],
      "parameters": {
        "text": "=Analyze this webpage for a research report on: \"{{ $json.topic }}\"\n\nSOURCE URL: {{ $json.url }}\n\nPAGE CONTENT (fallback scrape):\n{{ $json.content }}\n\nINSTRUCTIONS:\n- If this page is a CAPTCHA challenge, login wall, access denied page, or has fewer than 150 words of real content, respond with EXACTLY: RETRY_NEEDED\n- Otherwise respond with ONLY this JSON (no markdown wrapper):\n{\"status\":\"ok\",\"title\":\"page title\",\"summary\":\"2-3 sentence summary\",\"keyPoints\":[\"point1\",\"point2\",\"point3\"],\"dataPoints\":[\"stat or fact\"],\"relevance\":\"high|medium|low\"}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "p13a",
      "name": "Claude Re-Analyzer",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        3152,
        304
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "claude-sonnet-4-5"
        },
        "options": {
          "temperature": 0.1,
          "maxTokensToSample": 1024
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "pCA2",
      "name": "Combine Analysis R2",
      "type": "n8n-nodes-base.code",
      "position": [
        3440,
        128
      ],
      "parameters": {
        "jsCode": "const text = $input.first().json.text;\nconst orig = $('Normalize Firecrawl').first().json;\nreturn [{json:{...orig,text}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p14",
      "name": "Still Blocked? (IF)",
      "type": "n8n-nodes-base.if",
      "position": [
        3584,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "still-blocked-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ ($json.text || '').trim() }}",
              "rightValue": "RETRY_NEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "p15",
      "name": "Mark as Failed",
      "type": "n8n-nodes-base.set",
      "position": [
        3584,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "fail-text",
              "name": "text",
              "type": "string",
              "value": "{\"status\":\"unavailable\",\"summary\":\"Source could not be retrieved after 2 attempts.\",\"keyPoints\":[],\"dataPoints\":[],\"relevance\":\"none\"}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "p18",
      "name": "Aggregate Summaries",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        3920,
        128
      ],
      "parameters": {
        "include": "allFields",
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "pageSummaries"
      },
      "typeVersion": 1
    },
    {
      "id": "p19",
      "name": "Build Synthesis Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        4144,
        128
      ],
      "parameters": {
        "jsCode": "const agg = $input.first().json;\nconst summaries = agg.pageSummaries || [];\nconst topic = (summaries[0] || {}).topic || 'Unknown Topic';\nconst recipientEmail = (summaries[0] || {}).recipientEmail || '';\nconst runId = (summaries[0] || {}).runId || '';\nconst parsed = summaries.map(s => { try { return JSON.parse(s.text); } catch(e) { return {status:'parse_error',summary:String(s.text)}; } });\nconst ok = parsed.filter(p => p.status === 'ok').length;\nconst evidence = parsed.map((p, i) => {\n  const src = summaries[i];\n  if (p.status !== 'ok') return 'SOURCE ' + (i+1) + ': ' + src.url + '\\nSTATUS: Unavailable';\n  return 'SOURCE ' + (i+1) + ': ' + src.url + '\\nTITLE: ' + (p.title||'') + '\\nSUMMARY: ' + p.summary + '\\nKEY POINTS: ' + (p.keyPoints||[]).join(' | ') + '\\nDATA: ' + (p.dataPoints||[]).join(' | ') + '\\nRELEVANCE: ' + p.relevance;\n}).join('\\n\\n---\\n\\n');\nconst prompt = 'You are a senior research analyst.\\n\\nRESEARCH TOPIC: ' + topic + '\\nSOURCES: ' + summaries.length + ' analyzed, ' + ok + ' retrieved\\nDATE: ' + new Date().toLocaleDateString(\"en-US\",{year:\"numeric\",month:\"long\",day:\"numeric\"}) + '\\n\\nEVIDENCE:\\n' + evidence + '\\n\\nWrite a professional executive research report in Markdown with these sections:\\n# Executive Report: ' + topic + '\\n## Executive Summary\\n## Key Findings\\n## Detailed Analysis\\n## Data & Evidence\\n## Conclusions & Recommendations\\n## Sources\\n\\nRULES: Only include facts from the evidence. Do not invent statistics. Cite sources by number.';\nreturn [{json:{synthesisPrompt:prompt,topic,recipientEmail,runId,sourceCount:summaries.length,successCount:ok}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p20",
      "name": "Synthesize Report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        4368,
        128
      ],
      "parameters": {
        "text": "={{ $json.synthesisPrompt }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "p20a",
      "name": "Claude Synthesizer",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        4368,
        304
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "claude-sonnet-4-5"
        },
        "options": {
          "temperature": 0.3,
          "maxTokensToSample": 4096
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "p21",
      "name": "Build HTML Report",
      "type": "n8n-nodes-base.code",
      "position": [
        4656,
        128
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst synth = $('Build Synthesis Prompt').first().json;\nconst md = data.text || '';\nconst topic = synth.topic || 'Research Report';\nconst runId = synth.runId || '';\nconst date = new Date().toLocaleDateString('en-US',{year:'numeric',month:'long',day:'numeric'});\n\nconst toHtml = s => {\n  const lines = s.split('\\n');\n  const out = [];\n  let i = 0;\n  let inList = false;\n  while (i < lines.length) {\n    const line = lines[i];\n    if (i + 1 < lines.length && /^\\|/.test(line) && /^\\|[\\s\\-|:]+\\|/.test(lines[i+1])) {\n      if (inList) { out.push('</ul>'); inList = false; }\n      const headers = line.split('|').slice(1,-1).map(h => '<th>' + h.trim() + '</th>').join('');\n      out.push('<table><thead><tr>' + headers + '</tr></thead><tbody>');\n      i += 2;\n      while (i < lines.length && /^\\|/.test(lines[i])) {\n        const cells = lines[i].split('|').slice(1,-1).map(c => '<td>' + c.trim().replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>') + '</td>').join('');\n        out.push('<tr>' + cells + '</tr>');\n        i++;\n      }\n      out.push('</tbody></table>');\n      continue;\n    }\n    if (/^### /.test(line)) { if (inList) { out.push('</ul>'); inList = false; } out.push('<h3>' + line.slice(4) + '</h3>'); }\n    else if (/^## /.test(line)) { if (inList) { out.push('</ul>'); inList = false; } out.push('<h2>' + line.slice(3) + '</h2>'); }\n    else if (/^# /.test(line)) { if (inList) { out.push('</ul>'); inList = false; } out.push('<h1>' + line.slice(2) + '</h1>'); }\n    else if (/^[-*] /.test(line) || /^\\d+\\. /.test(line)) {\n      if (!inList) { out.push('<ul>'); inList = true; }\n      const content = line.replace(/^[-*] /,'').replace(/^\\d+\\. /,'').replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>').replace(/`(.+?)`/g,'<code>$1</code>');\n      out.push('<li>' + content + '</li>');\n    } else if (/^---+$/.test(line.trim())) { if (inList) { out.push('</ul>'); inList = false; } out.push('<hr>'); }\n    else if (line.trim() === '') { if (inList) { out.push('</ul>'); inList = false; } }\n    else { if (inList) { out.push('</ul>'); inList = false; } const content = line.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>').replace(/`(.+?)`/g,'<code>$1</code>').replace(/^> /,''); out.push('<p>' + content + '</p>'); }\n    i++;\n  }\n  if (inList) out.push('</ul>');\n  return out.join('\\n');\n};\n\nconst body = toHtml(md);\nconst html = `<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>${topic}</title><style>*{box-sizing:border-box;margin:0;padding:0}body{font-family:Georgia,serif;color:#1a1a2e;padding:40px;max-width:900px;margin:0 auto;font-size:14px}h1{font-size:24px;border-bottom:3px solid #0f3460;padding-bottom:10px;margin:0 0 20px}h2{font-size:18px;color:#0f3460;margin:24px 0 10px;border-left:4px solid #e94560;padding-left:10px}h3{font-size:15px;margin:16px 0 8px}p{line-height:1.7;margin-bottom:10px}ul{margin:10px 0 14px 20px;list-style:disc}li{line-height:1.7;margin-bottom:6px}ol{margin:10px 0 14px 20px}hr{border:none;border-top:1px solid #dee2e6;margin:20px 0}table{width:100%;border-collapse:collapse;margin:16px 0;font-size:13px}th{background:#0f3460;color:#fff;padding:8px 12px;text-align:left;font-weight:600}td{padding:7px 12px;border-bottom:1px solid #dee2e6}tr:nth-child(even) td{background:#f8f9fa}code{background:#f0f0f0;padding:1px 5px;border-radius:3px;font-size:12px}strong{font-weight:700}.meta{background:#f8f9fa;border:1px solid #dee2e6;border-radius:6px;padding:12px 18px;margin-bottom:24px;font-size:12px;color:#666}.footer{margin-top:40px;padding-top:14px;border-top:1px solid #dee2e6;font-size:11px;color:#999;text-align:center}</style></head><body><div class=\"meta\">Date: ${date} &nbsp;|&nbsp; Topic: ${topic} &nbsp;|&nbsp; Run: ${runId} &nbsp;|&nbsp; Sources: ${synth.sourceCount||'?'} (${synth.successCount||'?'} retrieved)</div>${body}<div class=\"footer\">Generated by Self-Healing Research Agent | n8n Workflow</div></body></html>`;\nreturn [{json:{html,topic,recipientEmail:synth.recipientEmail,runId,sourceCount:synth.sourceCount,successCount:synth.successCount}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "p23",
      "name": "Send Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        4800,
        128
      ],
      "parameters": {
        "sendTo": "={{ $json.recipientEmail }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "=Research Report: {{ $json.topic }} \u2014 {{ $now.toFormat('MMMM d, yyyy') }}",
        "resource": "message",
        "emailType": "html",
        "operation": "send"
      },
      "typeVersion": 2.2
    },
    {
      "id": "p24",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -336,
        672
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "p25",
      "name": "Format Error",
      "type": "n8n-nodes-base.set",
      "position": [
        -80,
        672
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "error_message",
              "type": "string",
              "value": "={{ $json.execution.error.message }}"
            },
            {
              "id": "e2",
              "name": "failed_node",
              "type": "string",
              "value": "={{ $json.execution.error.node.name }}"
            },
            {
              "id": "e3",
              "name": "workflow_name",
              "type": "string",
              "value": "={{ $json.workflow.name }}"
            },
            {
              "id": "e4",
              "name": "execution_url",
              "type": "string",
              "value": "={{ $json.execution.url }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Set Config": {
      "main": [
        [
          {
            "node": "SerpApi Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Format Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Research Form": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over URLs": {
      "main": [
        [
          {
            "node": "Aggregate Summaries",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Jina Reader (Primary)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Failed": {
      "main": [
        [
          {
            "node": "Merge Page Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SerpApi Search": {
      "main": [
        [
          {
            "node": "Parse & Filter URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Extractor": {
      "ai_languageModel": [
        [
          {
            "node": "Page Analyzer (Round 1)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Enrich & Forward": {
      "main": [
        [
          {
            "node": "Loop Over URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HTML Report": {
      "main": [
        [
          {
            "node": "Send Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Synthesize Report": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Retry": {
      "main": [
        [
          {
            "node": "Firecrawl (Fallback)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Re-Analyzer": {
      "ai_languageModel": [
        [
          {
            "node": "Page Analyzer (Round 2)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Claude Synthesizer": {
      "ai_languageModel": [
        [
          {
            "node": "Synthesize Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Merge Page Results": {
      "main": [
        [
          {
            "node": "Enrich & Forward",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Needed? (IF)": {
      "main": [
        [
          {
            "node": "Wait Before Retry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Page Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Summaries": {
      "main": [
        [
          {
            "node": "Build Synthesis Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Analysis R1": {
      "main": [
        [
          {
            "node": "Retry Needed? (IF)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Analysis R2": {
      "main": [
        [
          {
            "node": "Still Blocked? (IF)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Firecrawl": {
      "main": [
        [
          {
            "node": "Page Analyzer (Round 2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Filter URLs": {
      "main": [
        [
          {
            "node": "Loop Over URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Still Blocked? (IF)": {
      "main": [
        [
          {
            "node": "Mark as Failed",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge Page Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Firecrawl (Fallback)": {
      "main": [
        [
          {
            "node": "Normalize Firecrawl",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jina Reader (Primary)": {
      "main": [
        [
          {
            "node": "Normalize Jina Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Synthesis Prompt": {
      "main": [
        [
          {
            "node": "Synthesize Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Jina Response": {
      "main": [
        [
          {
            "node": "Page Analyzer (Round 1)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page Analyzer (Round 1)": {
      "main": [
        [
          {
            "node": "Combine Analysis R1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page Analyzer (Round 2)": {
      "main": [
        [
          {
            "node": "Combine Analysis R2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Submit a research topic through a form and receive a professionally styled executive report in your inbox — fully automated, with built-in scraping resilience.

Source: https://n8n.io/workflows/13608/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest — You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract — OCR + parsing pulls out key fie

HTTP Request, Chain Llm, Anthropic Chat +5
AI & RAG

Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 87 nodes.

Form Trigger, Chain Llm, Output Parser Structured +7
AI & RAG

My workflow 53. Uses formTrigger, httpRequest, lmChatOpenAi, form. Event-driven trigger; 74 nodes.

Form Trigger, HTTP Request, OpenAI Chat +15
AI & RAG

Episode 23: UGC with nanobanana. Uses lmChatOpenAi, lmChatOllama, lmChatDeepSeek, lmChatOpenRouter. Event-driven trigger; 74 nodes.

OpenAI Chat, Ollama Chat, Lm Chat Deep Seek +12
AI & RAG

Transform a hand-drawn character sketch into a fully animated, narrated video story — automatically. This 3-part pipeline uses Claude AI, image generation, and video synthesis to go from a simple draw

Form Trigger, Google Gemini, HTTP Request +5