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 →
{
"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, '&').replace(/</g, '<').replace(/>/g, '>');\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
}
]
]
}
}
}
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Relatórios aos Laboratórios. Uses httpRequest, gmail, formTrigger. Scheduled trigger; 54 nodes.
YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.
14310 Send Overdue Invoice Payment Reminders With Ifirma Gmail Postgrid And Slack. Uses httpRequest, stopAndError, slack, gmail. Scheduled trigger; 53 nodes.
Addendo — Blog Automatico Don Jacinto Nahual. Uses httpRequest, redis, github, gmail. Scheduled trigger; 51 nodes.
Addendo — Blog Automatico Don Jacinto Nahual. Uses httpRequest, redis, github, gmail. Scheduled trigger; 51 nodes.