AutomationFlowsEmail & Gmail › Send Hacker News Keyword Digests with Claude and Gmail

Send Hacker News Keyword Digests with Claude and Gmail

ByNocode Expert @iamrup on n8n.io

This workflow runs every 6 hours to search Hacker News (Algolia API) for your keywords, uses Anthropic Claude to filter and rank the most relevant stories, and emails you a concise digest via Gmail. Runs on a schedule every 6 hours. Builds an Algolia Hacker News search URL for…

Cron / scheduled trigger★★★★☆ complexity13 nodesHTTP RequestGmail
Email & Gmail Trigger: Cron / scheduled Nodes: 13 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

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

Download .json
{
  "id": "15hackernewskeywordm",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Monitor Hacker News for your keywords and get an AI-filtered digest of high-signal posts",
  "tags": [],
  "nodes": [
    {
      "id": "overview",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -768,
        0
      ],
      "parameters": {
        "width": 664,
        "height": 756,
        "content": "## Hacker News keyword monitor \u2192 email\n\nWatch Hacker News for the keywords you care about, let AI keep only what is relevant and worth your time, and get a digest of the highest-signal posts and launches.\n\n### How it works\nSet your keywords and a points floor. A Code node builds a Hacker News search for each keyword (recent stories above your traction threshold), a native HTTP Request node fetches the results from the HN Algolia API, and a Code node parses and tags each story by keyword, de-duplicates, and sorts by points. A single AI call drops off-topic matches, picks what this reader would actually want (a launch, a tool, a hiring signal, a thread to join), and explains why each matters. The picks are emailed with points, comment links and the matching keyword.\n\n### Setup\n- Edit the config node: keywords, tracking context, minimum points, look-back window and send-to address.\n- Add credentials in the canvas: Anthropic on the analysis HTTP node and Gmail on the send node.\n- The HN Algolia API is public and needs no key; the Schedule Trigger runs on an interval.\n\n### Customization\nLower the points floor to catch launches early, add product or competitor names, run it hourly for a real-time radar, or swap the email for Slack.\n\nBuilt by **nocode.expert** \u2014 done-for-you automation and AI workflows. https://nocode.expert"
      },
      "typeVersion": 1
    },
    {
      "id": "section-search-hn",
      "name": "Section: Search HN",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 456,
        "height": 484,
        "content": "## 1. Search & tag\nBuild an HN Algolia search per keyword (recent, above your points floor), fetch with the HTTP node, then parse and tag each story by keyword."
      },
      "typeVersion": 1
    },
    {
      "id": "section-filter-with-ai",
      "name": "Section: Filter with AI",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 908,
        "height": 500,
        "content": "## 2. Filter with AI\nOne AI call drops off-topic matches, picks what you would actually want, and says why each matters."
      },
      "typeVersion": 1
    },
    {
      "id": "section-email",
      "name": "Section: Email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        224
      ],
      "parameters": {
        "color": 7,
        "width": 712,
        "height": 500,
        "content": "## 3. Email digest\nPicks emailed with points, comment links and the matching keyword. Swap for Slack if you prefer."
      },
      "typeVersion": 1
    },
    {
      "id": "on-a-schedule",
      "name": "On a schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -80,
        528
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "set-config-keywords-filters",
      "name": "Set config: keywords & filters",
      "type": "n8n-nodes-base.set",
      "position": [
        208,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "keywords",
              "type": "string",
              "value": "n8n, self-hosted, AI agents, your product name"
            },
            {
              "id": "a2",
              "name": "context",
              "type": "string",
              "value": "I want launches, useful tools, hiring signals, and threads where I could helpfully chime in."
            },
            {
              "id": "a3",
              "name": "minPoints",
              "type": "string",
              "value": "20"
            },
            {
              "id": "a4",
              "name": "hoursBack",
              "type": "string",
              "value": "48"
            },
            {
              "id": "a5",
              "name": "topN",
              "type": "string",
              "value": "15"
            },
            {
              "id": "a6",
              "name": "digestTo",
              "type": "string",
              "value": "you@example.com"
            },
            {
              "id": "a7",
              "name": "model",
              "type": "string",
              "value": "claude-haiku-4-5"
            },
            {
              "id": "a8",
              "name": "anthropicKey",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "expand-keyword-searches",
      "name": "Expand keyword searches",
      "type": "n8n-nodes-base.code",
      "position": [
        400,
        528
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: keywords & filters').first().json;\nconst kws = String(cfg.keywords || '').split(',').map((s) => s.trim()).filter(Boolean);\nconst cutoff = Math.floor((Date.now() - (Number(cfg.hoursBack) || 48) * 3600 * 1000) / 1000);\nconst minPts = Number(cfg.minPoints) || 0;\n// Algolia HN search: recent stories matching the keyword, above a points floor\nreturn kws.map((keyword) => ({ json: {\n  keyword,\n  feedUrl: 'https://hn.algolia.com/api/v1/search_by_date?query=' + encodeURIComponent(keyword) +\n    '&tags=story&hitsPerPage=30&numericFilters=' + encodeURIComponent('created_at_i>' + cutoff + ',points>=' + minPts),\n} }));"
      },
      "typeVersion": 2
    },
    {
      "id": "fetch-hn-per-keyword",
      "name": "Fetch HN per keyword",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        736,
        528
      ],
      "parameters": {
        "url": "={{ $json.feedUrl }}",
        "options": {
          "timeout": 15000
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "n8n-hn-monitor/1.0 (+nocode.expert)"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "parse-tag-hn-stories",
      "name": "Parse & tag HN stories",
      "type": "n8n-nodes-base.code",
      "position": [
        960,
        528
      ],
      "parameters": {
        "jsCode": "const src = $('Expand keyword searches').all();\nconst kwOf = (it, idx) => {\n  let pi = it.pairedItem; let p = (typeof pi === 'number') ? pi : (Array.isArray(pi) ? (pi[0] || {}).item : (pi || {}).item);\n  if (p === undefined || p === null) p = idx;\n  return (src[p] && src[p].json.keyword) || '';\n};\nconst seen = new Set(); const items = [];\nconst responses = $input.all();\nfor (let idx = 0; idx < responses.length; idx++) {\n  let j = responses[idx].json;\n  // HTTP node returns parsed JSON (object) for application/json, or a string we must parse\n  if (typeof j === 'string') { try { j = JSON.parse(j); } catch (e) { j = {}; } }\n  if (j && typeof j.data === 'string') { try { j = JSON.parse(j.data); } catch (e) {} }\n  const hits = (j && j.hits) || [];\n  const keyword = kwOf(responses[idx], idx);\n  for (const h of hits) {\n    if (seen.has(h.objectID)) continue;\n    seen.add(h.objectID);\n    const title = h.title || h.story_title || '';\n    if (!title) continue;\n    items.push({\n      title,\n      url: h.url || ('https://news.ycombinator.com/item?id=' + h.objectID),\n      hn: 'https://news.ycombinator.com/item?id=' + h.objectID,\n      points: h.points || 0, comments: h.num_comments || 0, author: h.author || '',\n      keyword, ts: (h.created_at_i || 0) * 1000,\n    });\n  }\n}\nitems.sort((x, y) => y.points - x.points);\nconst capped = items.slice(0, 40).map((a, i) => ({ i, ...a }));\nreturn [{ json: { items: capped, count: capped.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "build-ai-analysis-request",
      "name": "Build AI analysis request",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        528
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: keywords & filters').first().json;\nconst items = $json.items || [];\nconst schema = { type: 'object', additionalProperties: false, required: ['picks'], properties: {\n  picks: { type: 'array', items: { type: 'object', additionalProperties: false,\n    required: ['i', 'keyword', 'headline', 'why', 'importance'], properties: {\n      i: { type: 'integer' }, keyword: { type: 'string' }, headline: { type: 'string' },\n      why: { type: 'string' }, importance: { type: 'integer' } } } } } };\nconst system = 'You monitor Hacker News for ONE reader. Each story is tagged with the keyword it matched and carries points and comment counts. Drop off-topic matches and low-signal noise, then pick what this reader would actually want. For each pick: keyword = keep the tag, headline = tight rewrite, why = one sentence on why it matters to this reader (a launch, a tool, a hiring signal, a thread to join), importance = 0-100 (weigh relevance and traction). Be terse.';\nconst user = 'READER CONTEXT: ' + cfg.context + '\\n\\nPick the top ' + (cfg.topN || 15) + '.\\n\\nSTORIES (reference each by index i):\\n' +\n  items.map((a) => '[' + a.i + '] {' + a.keyword + '} ' + a.title + ' \u2014 ' + a.points + ' pts, ' + a.comments + ' comments').join('\\n');\nreturn [{ json: { body: {\n  model: cfg.model, max_tokens: 2000, system,\n  messages: [{ role: 'user', content: user }],\n  output_config: { format: { type: 'json_schema', schema } },\n} } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "analyze-hn-with-claude",
      "name": "Analyze HN with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1376,
        528
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $('Set config: keywords & filters').first().json.anthropicKey }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "format-the-hn-digest",
      "name": "Format the HN digest",
      "type": "n8n-nodes-base.code",
      "position": [
        1792,
        528
      ],
      "parameters": {
        "jsCode": "const cfg = $('Set config: keywords & filters').first().json;\nconst items = $('Parse & tag HN stories').first().json.items || [];\nconst top = Number(cfg.topN) || 15;\nconst blocks = ($json.content || []);\nconst text = (blocks.find((b) => b.type === 'text') || {}).text || '{\"picks\":[]}';\nlet picks = []; try { picks = (JSON.parse(text).picks) || []; } catch (e) {}\nconst esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\npicks = picks.map((p) => ({ ...p, ...(items[p.i] || {}), keyword: p.keyword || (items[p.i] || {}).keyword }))\n  .filter((p) => p.url).sort((a, b) => b.importance - a.importance).slice(0, top);\nconst rows = picks.map((p) =>\n  '<li style=\"margin:9px 0\"><a href=\"' + esc(p.url) + '\" style=\"font-weight:700\">' + esc(p.headline) + '</a> ' +\n  '<span style=\"color:#999\">\u00b7 ' + p.points + ' pts \u00b7 <a href=\"' + esc(p.hn) + '\" style=\"color:#999\">' + p.comments + ' comments</a> \u00b7 #' + esc(p.keyword) + '</span><br>' +\n  '<span style=\"color:#333\">' + esc(p.why) + '</span></li>').join('');\nconst html = '<h2 style=\"font-family:system-ui,Arial\">Hacker News monitor</h2>' +\n  '<ul style=\"font-family:system-ui,Arial;padding-left:18px\">' + rows + '</ul>' +\n  '<p style=\"color:#999;font-size:12px\">' + picks.length + ' picks from ' + items.length + ' matched stories \u00b7 ' + esc(cfg.model) + '</p>';\nconst subject = picks.length ? 'HN monitor: ' + picks.length + ' picks \u00b7 top ' + (picks[0] ? picks[0].points + ' pts' : '') : 'HN monitor: nothing matched';\nreturn [{ json: { subject, html, count: picks.length } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "email-the-hn-digest",
      "name": "Email the HN digest",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1984,
        528
      ],
      "parameters": {
        "sendTo": "={{ $('Set config: keywords & filters').first().json.digestTo }}",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "66b83d7c-573c-4cbe-967a-400c4c7c562e",
  "nodeGroups": [],
  "connections": {
    "On a schedule": {
      "main": [
        [
          {
            "node": "Set config: keywords & filters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch HN per keyword": {
      "main": [
        [
          {
            "node": "Parse & tag HN stories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format the HN digest": {
      "main": [
        [
          {
            "node": "Email the HN digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze HN with Claude": {
      "main": [
        [
          {
            "node": "Format the HN digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & tag HN stories": {
      "main": [
        [
          {
            "node": "Build AI analysis request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand keyword searches": {
      "main": [
        [
          {
            "node": "Fetch HN per keyword",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build AI analysis request": {
      "main": [
        [
          {
            "node": "Analyze HN with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set config: keywords & filters": {
      "main": [
        [
          {
            "node": "Expand keyword searches",
            "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

This workflow runs every 6 hours to search Hacker News (Algolia API) for your keywords, uses Anthropic Claude to filter and rank the most relevant stories, and emails you a concise digest via Gmail. Runs on a schedule every 6 hours. Builds an Algolia Hacker News search URL for…

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

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Relatórios aos Laboratórios. Uses httpRequest, gmail, formTrigger. Scheduled trigger; 54 nodes.

HTTP Request, Gmail, Form Trigger
Email & Gmail

YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.

Gmail, Google Drive, Google Sheets +1
Email & Gmail

14310 Send Overdue Invoice Payment Reminders With Ifirma Gmail Postgrid And Slack. Uses httpRequest, stopAndError, slack, gmail. Scheduled trigger; 53 nodes.

HTTP Request, Stop And Error, Slack +1
Email & Gmail

Addendo — Blog Automatico Don Jacinto Nahual. Uses httpRequest, redis, github, gmail. Scheduled trigger; 51 nodes.

HTTP Request, Redis, GitHub +1
Email & Gmail

Addendo — Blog Automatico Don Jacinto Nahual. Uses httpRequest, redis, github, gmail. Scheduled trigger; 51 nodes.

HTTP Request, Redis, GitHub +1