This workflow corresponds to n8n.io template #13945 — we link there as the canonical source.
This workflow follows the Telegram → Telegram 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 →
{
"id": "3hWBSzxv0HLuvevU",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "NPM package tracker",
"tags": [],
"nodes": [
{
"id": "d2168682-f0ed-4369-9c40-baee62aebab1",
"name": "Switch Command",
"type": "n8n-nodes-base.switch",
"position": [
-16,
1312
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "downloads",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ceb74833-f6c6-490c-82d2-d3c368258c74",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "downloads"
}
]
},
"renameOutput": true
},
{
"outputKey": "weekly",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3f4103fd-eb69-4b7a-b2c1-144c98b0bfa1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "weekly"
}
]
},
"renameOutput": true
},
{
"outputKey": "status",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1062ff98-6c0f-4edb-92ca-4574cee2462d",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "status"
}
]
},
"renameOutput": true
},
{
"outputKey": "trending",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a366898b-55e2-4e64-bb23-d336bff8de39",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "trending"
}
]
},
"renameOutput": true
},
{
"outputKey": "help",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "149e6ff0-f9cc-4e54-b248-a3efd7678d8e",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "help"
}
]
},
"renameOutput": true
},
{
"outputKey": "unknown",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c345b813-8ce3-421f-a7d4-235049daf6b7",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.command }}",
"rightValue": "unknown"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "d94f22e4-81e3-4f9a-af3d-001ad6c75868",
"name": "1st of Month 6PM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-800,
2800
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 18 1 * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "fd700615-112e-4eb5-b951-6bb947102ca8",
"name": "Fetch Trending",
"type": "n8n-nodes-base.code",
"position": [
512,
1904
],
"parameters": {
"jsCode": "// TRENDING - compares this week vs last week, shows growth rate\nconst NPM_USERNAME = 'monfortbrian';\nconst correctionNote = $input.first().json.wasCorrected ? `_\u270f\ufe0f Did you mean /${$input.first().json.correctedTo}? Showing results:_\\n\\n` : '';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\n// Get date ranges\nconst now = new Date();\nconst oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);\nconst twoWeeksAgo = new Date(now - 14 * 24 * 60 * 60 * 1000);\nconst fmt = d => d.toISOString().split('T')[0];\n\nconst thisWeekStart = fmt(oneWeekAgo);\nconst thisWeekEnd = fmt(now);\nconst lastWeekStart = fmt(twoWeeksAgo);\nconst lastWeekEnd = fmt(oneWeekAgo);\n\nconst results = [];\n\nfor (const pkg of packages) {\n try {\n const [thisWeek, lastWeek] = await Promise.all([\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${thisWeekStart}:${thisWeekEnd}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${lastWeekStart}:${lastWeekEnd}/${pkg}` })\n ]);\n const tw = thisWeek.downloads || 0;\n const lw = lastWeek.downloads || 0;\n const delta = tw - lw;\n const pct = lw > 0 ? Math.round((delta / lw) * 100) : (tw > 0 ? 100 : 0);\n const arrow = delta > 0 ? '\ud83c\udf31 ' : delta < 0 ? '\u26a0\ufe0f' : '\u2796';\n results.push({ pkg, tw, lw, delta, pct, arrow });\n } catch (e) {\n results.push({ pkg, tw: 0, lw: 0, delta: 0, pct: 0, arrow: '\ud83c\udd95' });\n }\n}\n\n// Sort by absolute growth this week\nresults.sort((a, b) => b.tw - a.tw);\n\nconst winner = results[0];\nconst lines = results.map(r => {\n const sign = r.delta >= 0 ? '+' : '';\n return ` ${r.arrow} ${r.pkg}\\n This week: ${r.tw} | Last week: ${r.lw} | ${sign}${r.delta} (${sign}${r.pct}%)`;\n});\n\nconst fmtDisplay = d => d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });\n\nconst message = [\n correctionNote + '\ud83d\udd25 *Trending Packages*',\n `_${fmtDisplay(twoWeeksAgo)} vs ${fmtDisplay(oneWeekAgo)} - ${fmtDisplay(now)}_`,\n '',\n `\ud83c\udfc6 *Top this week: ${winner.pkg}* (${winner.tw} downloads)`,\n '',\n lines.join('\\n'),\n '',\n '',\n '_Sorted by downloads this week._'\n].join('\\n');\n\nreturn [{ json: { message, chatId: $input.first().json.chatId } }];"
},
"typeVersion": 2
},
{
"id": "e42a065d-3481-4142-9ab6-36e886c6f134",
"name": "Fetch Monthly Digest",
"type": "n8n-nodes-base.code",
"position": [
-496,
2800
],
"parameters": {
"jsCode": "// MONTHLY DIGEST - 1st of every month 6PM GMT\nconst NPM_USERNAME = 'monfortbrian';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\n\n// Last month range\nconst lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0);\nconst lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n// Two months ago for comparison\nconst twoMonthsStart = new Date(now.getFullYear(), now.getMonth() - 2, 1);\nconst twoMonthsEnd = new Date(now.getFullYear(), now.getMonth() - 1, 0);\n\nconst fmt = d => d.toISOString().split('T')[0];\nconst monthName = d => d.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' });\n\nconst results = [];\nlet monthTotal = 0;\nlet prevMonthTotal = 0;\nlet grandTotal = 0;\n\nfor (const pkg of packages) {\n try {\n const [thisMonth, prevMonth, allTime] = await Promise.all([\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${fmt(lastMonthStart)}:${fmt(lastMonthEnd)}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${fmt(twoMonthsStart)}:${fmt(twoMonthsEnd)}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${today}/${pkg}` })\n ]);\n const m = thisMonth.downloads || 0;\n const pm = prevMonth.downloads || 0;\n const a = allTime.downloads || 0;\n monthTotal += m;\n prevMonthTotal += pm;\n grandTotal += a;\n results.push({ pkg, m, pm, a });\n } catch(e) {\n results.push({ pkg, m: 0, pm: 0, a: 0 });\n }\n}\n\nresults.sort((a, b) => b.m - a.m);\n\nconst delta = monthTotal - prevMonthTotal;\nconst sign = delta >= 0 ? '+' : '';\nconst arrow = delta > 0 ? '\ud83c\udf31' : delta < 0 ? '\u26a0\ufe0f' : '\u2796';\nconst topPkg = results[0];\n\nconst message = [\n `*Monthly Report - ${monthName(lastMonthStart)}*`,\n '',\n `${arrow} Month-over-month: *${sign}${delta}* downloads vs ${monthName(twoMonthsStart)}`,\n '',\n `\ud83c\udfc6 Top package: *${topPkg.pkg}* (${topPkg.m} downloads)`,\n '',\n '\ud83d\udcca *This Month vs Last Month*',\n results.map(r => {\n const d = r.m - r.pm;\n const s = d >= 0 ? '+' : '';\n return ` \u2022 ${r.pkg}: ${r.m} _(${s}${d})_`;\n }).join('\\n'),\n `*Month Total: ${monthTotal.toLocaleString()}*`,\n '',\n '',\n '\ud83d\udce6 *All-Time Totals*',\n results.map(r => ` \u2022 ${r.pkg}: ${r.a.toLocaleString()}`).join('\\n'),\n `*Grand Total: ${grandTotal.toLocaleString()} downloads*`,\n '',\n '',\n '\ud83d\udd17 [View on npm](https://www.npmjs.com/~monfortbrian)'\n].join('\\n');\n\nreturn [{ json: { message } }];"
},
"typeVersion": 2
},
{
"id": "79a50834-460e-42f7-a61e-d745fa12778f",
"name": "Check Milestones",
"type": "n8n-nodes-base.code",
"position": [
-496,
3040
],
"parameters": {
"jsCode": "// MILESTONE CHECKER - runs daily at 9AM\n// Alerts when a package crosses 100, 500, 1000, 5000, 10000 downloads\nconst NPM_USERNAME = 'monfortbrian';\nconst MILESTONES = [100, 500, 1000, 5000, 10000, 50000, 100000];\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst today = new Date().toISOString().split('T')[0];\nconst yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];\n\nconst alerts = [];\n\nfor (const pkg of packages) {\n try {\n const [todayData, yesterdayData] = await Promise.all([\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${today}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${yesterday}/${pkg}` })\n ]);\n const current = todayData.downloads || 0;\n const previous = yesterdayData.downloads || 0;\n\n for (const milestone of MILESTONES) {\n if (previous < milestone && current >= milestone) {\n alerts.push({ pkg, milestone, current });\n }\n }\n } catch(e) {}\n}\n\nif (alerts.length === 0) {\n return [{ json: { hasAlerts: false, message: null } }];\n}\n\nconst lines = alerts.map(a => `\ud83c\udf89 *${a.pkg}* just crossed *${a.milestone.toLocaleString()} downloads!* (now at ${a.current.toLocaleString()})`);\n\nconst message = [\n '\ud83c\udfc6 *Milestone Alert!*',\n '',\n lines.join('\\n'),\n '',\n '_Keep shipping - the next milestone is closer than you think_'\n].join('\\n');\n\nreturn [{ json: { hasAlerts: true, message } }];"
},
"typeVersion": 2
},
{
"id": "bc57611c-85f0-49b4-ada2-afa92a5e29cd",
"name": "Has Milestones?",
"type": "n8n-nodes-base.if",
"position": [
16,
3040
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.hasAlerts }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "fed0f4e1-3749-42f2-9933-ab4c9e818bef",
"name": "Send Trending",
"type": "n8n-nodes-base.telegram",
"position": [
848,
1904
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "695971b5-34d1-4cd8-99c1-8904be43463e",
"name": "Send Weekly Digest",
"type": "n8n-nodes-base.telegram",
"position": [
864,
2592
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "123456789",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "ed776110-d887-4363-91ed-e990ad3e69b6",
"name": "Send Monthly Digest",
"type": "n8n-nodes-base.telegram",
"position": [
864,
2800
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "123456789",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "38150ac3-1e2f-4e81-9ec3-9cdb6e9e309b",
"name": "Send Milestone Alert",
"type": "n8n-nodes-base.telegram",
"position": [
864,
3024
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "123456789",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "4eb0cc68-c920-45c2-ba62-983aa03d5778",
"name": "Send Downloads",
"type": "n8n-nodes-base.telegram",
"position": [
848,
1296
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "b595746b-b9c0-4a59-93ab-6089efe37820",
"name": "Send Weekly",
"type": "n8n-nodes-base.telegram",
"position": [
848,
1488
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "27752c2f-469e-4a61-a3a4-8a17df0dcd6e",
"name": "Send Status",
"type": "n8n-nodes-base.telegram",
"position": [
848,
1696
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "27e41db2-4eab-4c43-b744-4ceec3bc13ab",
"name": "Send Help",
"type": "n8n-nodes-base.telegram",
"position": [
864,
2112
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "b34b3279-942a-43f5-a407-b48c6abc8069",
"name": "Send Unknown",
"type": "n8n-nodes-base.telegram",
"position": [
864,
2304
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "={{ $json.chatId }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "7c81fc11-aba2-4628-9481-f6d6783305ed",
"name": "Fetch All-Time Downloads",
"type": "n8n-nodes-base.code",
"position": [
512,
1296
],
"parameters": {
"jsCode": "const NPM_USERNAME = 'monfortbrian';\nconst correctionNote = $input.first().json.wasCorrected ? `_\u270f\ufe0f Did you mean /${$input.first().json.correctedTo}? Showing results:_\\n\\n` : '';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst today = new Date().toISOString().split('T')[0];\nconst results = [];\nlet grandTotal = 0;\n\nfor (const pkg of packages) {\n try {\n const res = await this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${today}/${pkg}` });\n const count = res.downloads || 0;\n grandTotal += count;\n results.push({ pkg, count });\n } catch (e) {\n results.push({ pkg, count: 0 });\n }\n}\n\nresults.sort((a, b) => b.count - a.count);\nconst lines = results.map(r => ` \u2022 ${r.pkg}: ${r.count.toLocaleString()}`);\n\nconst message = [\n correctionNote + '\ud83d\udce6 *All-Time Downloads*',\n '',\n lines.join('\\n'),\n '',\n `*Grand Total: ${grandTotal.toLocaleString()} downloads*`\n].join('\\n');\n\nreturn [{ json: { message, chatId: $input.first().json.chatId } }];"
},
"typeVersion": 2
},
{
"id": "a1344ec4-4afb-44ea-9af1-da23f1874486",
"name": "Fetch Weekly Downloads",
"type": "n8n-nodes-base.code",
"position": [
512,
1488
],
"parameters": {
"jsCode": "const NPM_USERNAME = 'monfortbrian';\nconst correctionNote = $input.first().json.wasCorrected ? `_\u270f\ufe0f Did you mean /${$input.first().json.correctedTo}? Showing results:_\\n\\n` : '';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst results = [];\nlet weeklyTotal = 0;\n\nfor (const pkg of packages) {\n try {\n const res = await this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/last-week/${pkg}` });\n const count = res.downloads || 0;\n weeklyTotal += count;\n results.push({ pkg, count });\n } catch (e) {\n results.push({ pkg, count: 0 });\n }\n}\n\nresults.sort((a, b) => b.count - a.count);\nconst lines = results.map(r => ` \u2022 ${r.pkg}: ${r.count.toLocaleString()}`);\n\nconst now = new Date();\nconst weekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);\nconst fmt = d => d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });\n\nconst message = [\n correctionNote + `\ud83d\udcca *Weekly Downloads* (_${fmt(weekAgo)} - ${fmt(now)}_)`,\n '',\n lines.join('\\n'),\n '',\n `*This Week Total: ${weeklyTotal.toLocaleString()} downloads*`\n].join('\\n');\n\nreturn [{ json: { message, chatId: $input.first().json.chatId } }];"
},
"typeVersion": 2
},
{
"id": "3de20ce0-c41c-4c1e-9910-d714fbb04b21",
"name": "Fetch Status",
"type": "n8n-nodes-base.code",
"position": [
512,
1696
],
"parameters": {
"jsCode": "const NPM_USERNAME = 'monfortbrian';\nconst correctionNote = $input.first().json.wasCorrected ? `_\u270f\ufe0f Did you mean /${$input.first().json.correctedTo}? Showing results:_\\n\\n` : '';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst today = new Date().toISOString().split('T')[0];\nconst weeklyResults = [];\nconst allTimeResults = [];\nlet weeklyTotal = 0;\nlet grandTotal = 0;\n\nfor (const pkg of packages) {\n try {\n const [weekly, allTime] = await Promise.all([\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/last-week/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${today}/${pkg}` })\n ]);\n const w = weekly.downloads || 0;\n const a = allTime.downloads || 0;\n weeklyTotal += w;\n grandTotal += a;\n weeklyResults.push({ pkg, count: w });\n allTimeResults.push({ pkg, countW: w, countA: a });\n } catch (e) {\n weeklyResults.push({ pkg, count: 0 });\n allTimeResults.push({ pkg, countW: 0, countA: 0 });\n }\n}\n\nweeklyResults.sort((a, b) => b.count - a.count);\nallTimeResults.sort((a, b) => b.countA - a.countA);\n\nconst message = [\n correctionNote + '*NPM Package Status*',\n '',\n '\ud83d\udcca *This Week*',\n weeklyResults.map(r => ` \u2022 ${r.pkg}: ${r.count.toLocaleString()}`).join('\\n'),\n `*Week Total: ${weeklyTotal.toLocaleString()}*`,\n '',\n '\ud83d\udce6 *All-Time*',\n allTimeResults.map(r => ` \u2022 ${r.pkg}: ${r.countA.toLocaleString()}`).join('\\n'),\n `*Grand Total: ${grandTotal.toLocaleString()}*`\n].join('\\n');\n\nreturn [{ json: { message, chatId: $input.first().json.chatId } }];"
},
"typeVersion": 2
},
{
"id": "48634482-dc40-414a-942a-983c665a4e92",
"name": "Help Message",
"type": "n8n-nodes-base.code",
"position": [
512,
2112
],
"parameters": {
"jsCode": "const chatId = $input.first().json.chatId;\n\nconst message = [\n '*NPM Tracker - Commands*',\n '',\n '/downloads - All-time total per package',\n '/weekly - Downloads this week',\n '/status - Weekly + all-time combined',\n '/trending - This week vs last week with growth %',\n '/help - Show this message',\n '',\n '\u2022 _works with or without \"/\"_',\n '\u2022 _typos auto-corrected_',\n '\u2022 _packages sorted by downloads (highest first)_'\n].join('\\n');\n\nreturn [{ json: { message, chatId } }];"
},
"typeVersion": 2
},
{
"id": "4cf91307-35cc-45c2-9442-0c16737c0a9c",
"name": "Unknown Command",
"type": "n8n-nodes-base.code",
"position": [
512,
2304
],
"parameters": {
"jsCode": "const chatId = $input.first().json.chatId;\nconst original = $input.first().json.original;\n\nconst message = [\n `*\"${original}\"* - command not recognized`,\n '',\n 'Available commands:',\n '',\n '/downloads',\n '/weekly',\n '/status',\n '/trending',\n '/help'\n].join('\\n');\n\nreturn [{ json: { message, chatId } }];"
},
"typeVersion": 2
},
{
"id": "40604584-62db-4d99-bb30-0cee6992bb77",
"name": "Parse Command",
"type": "n8n-nodes-base.code",
"position": [
-512,
1376
],
"parameters": {
"jsCode": "// Fuzzy command matcher - handles typos, slash or no slash, any case\nfunction levenshtein(a, b) {\n const m = a.length, n = b.length;\n const dp = Array.from({length: m+1}, (_, i) => Array(n+1).fill(0).map((_, j) => i === 0 ? j : j === 0 ? i : 0));\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);\n }\n }\n return dp[m][n];\n}\n\nconst raw = $input.first().json?.message?.text || '';\nconst chatId = $input.first().json?.message?.chat?.id;\nconst normalized = raw.trim().toLowerCase().replace(/^\\//, '');\n\nconst commands = ['downloads', 'weekly', 'status', 'trending', 'help'];\n\nlet best = null;\nlet bestScore = Infinity;\nfor (const cmd of commands) {\n const score = levenshtein(normalized, cmd);\n if (score < bestScore) { bestScore = score; best = cmd; }\n}\n\nconst matched = bestScore <= 4 ? best : 'unknown';\nconst wasCorrected = bestScore > 0 && bestScore <= 4 && normalized !== best;\n\nreturn [{ json: { command: matched, original: raw, chatId, wasCorrected, correctedTo: best } }];"
},
"typeVersion": 2
},
{
"id": "4b8fdde6-a8f1-4d17-a9c1-873145f3309d",
"name": "Every Friday 6PM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-800,
2592
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 18 * * 5"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f064321b-de5e-4e95-8429-13a7f74c83b3",
"name": "Milestone (daily check 9AM)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-800,
3040
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "107d892a-8c7e-4f2c-8f16-a7801b1b5e77",
"name": "Fetch Weekly Digest",
"type": "n8n-nodes-base.code",
"position": [
-496,
2592
],
"parameters": {
"jsCode": "// WEEKLY DIGEST - every Friday 6PM GMT\nconst NPM_USERNAME = 'monfortbrian';\n\nlet packages = [];\ntry {\n const profile = await this.helpers.httpRequest({ method: 'GET', url: `https://registry.npmjs.org/-/v1/search?text=maintainer:${NPM_USERNAME}&size=50` });\n packages = profile.objects.map(o => o.package.name);\n} catch(e) {\n packages = ['n8n-nodes-csv-normalizer','n8n-nodes-mtn-momo','comprehensive-design-tokens','n8n-nodes-openmrs','n8n-nodes-dhis2','openmrs-sdk','n8n-nodes-rapidpro'];\n}\n\nconst now = new Date();\nconst oneWeekAgo = new Date(now - 7 * 24 * 60 * 60 * 1000);\nconst twoWeeksAgo = new Date(now - 14 * 24 * 60 * 60 * 1000);\nconst fmt = d => d.toISOString().split('T')[0];\nconst fmtDisplay = d => d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });\nconst today = fmt(now);\n\nconst weeklyResults = [];\nconst allTimeResults = [];\nlet weeklyTotal = 0;\nlet grandTotal = 0;\nlet prevWeekTotal = 0;\n\nfor (const pkg of packages) {\n try {\n const [weekly, prevWeekly, allTime] = await Promise.all([\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${fmt(oneWeekAgo)}:${today}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/${fmt(twoWeeksAgo)}:${fmt(oneWeekAgo)}/${pkg}` }),\n this.helpers.httpRequest({ method: 'GET', url: `https://api.npmjs.org/downloads/point/2020-01-01:${today}/${pkg}` })\n ]);\n const w = weekly.downloads || 0;\n const pw = prevWeekly.downloads || 0;\n const a = allTime.downloads || 0;\n weeklyTotal += w;\n prevWeekTotal += pw;\n grandTotal += a;\n weeklyResults.push({ pkg, w, pw });\n allTimeResults.push({ pkg, a });\n } catch(e) {\n weeklyResults.push({ pkg, w: 0, pw: 0 });\n allTimeResults.push({ pkg, a: 0 });\n }\n}\n\nweeklyResults.sort((a, b) => b.w - a.w);\nallTimeResults.sort((a, b) => b.a - a.a);\n\nconst weekDelta = weeklyTotal - prevWeekTotal;\nconst weekSign = weekDelta >= 0 ? '+' : '';\nconst weekArrow = weekDelta > 0 ? '\ud83c\udf31' : weekDelta < 0 ? '\u26a0\ufe0f' : '\u2796';\n\nconst topPackage = weeklyResults[0];\n\nconst message = [\n `*Your Week on npm* (_${fmtDisplay(oneWeekAgo)} - ${fmtDisplay(now)}_)`,\n '',\n `${weekArrow} Week-over-week: *${weekSign}${weekDelta}* downloads vs last week`,\n '',\n `\ud83c\udfc6 Top package: *${topPackage.pkg}* (${topPackage.w} this week)`,\n '',\n '\ud83d\udcca *This Week*',\n weeklyResults.map(r => {\n const d = r.w - r.pw;\n const s = d >= 0 ? '+' : '';\n return ` \u2022 ${r.pkg}: ${r.w} _(${s}${d} vs last week)_`;\n }).join('\\n'),\n `*Week Total: ${weeklyTotal.toLocaleString()}*`,\n '',\n '',\n '\ud83d\udce6 *All-Time*',\n allTimeResults.map(r => ` \u2022 ${r.pkg}: ${r.a.toLocaleString()}`).join('\\n'),\n `*Grand Total: ${grandTotal.toLocaleString()} downloads*`,\n '',\n '',\n '\ud83d\udd17 [View on npm](https://www.npmjs.com/~monfortbrian)'\n].join('\\n');\n\nreturn [{ json: { message } }];"
},
"typeVersion": 2
},
{
"id": "ef764f5c-f774-4be2-ac28-5b53448dca26",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-816,
1376
],
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "c7e241a0-98ff-472b-8c15-3e463bbd922b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
1104
],
"parameters": {
"width": 480,
"height": 524,
"content": "## Final Output\n"
},
"typeVersion": 1
},
{
"id": "853f6261-6b4f-4dba-98c3-e5f259c56b12",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
1344
],
"parameters": {
"width": 768,
"height": 1904,
"content": "# NPM package tracker\n\nMonitor npm package downloads from Telegram with commands, weekly digests, and milestone alerts\n\n## Description\n\nThis template is designed for developers and teams who publish packages to npm and want simple visibility into package adoption.\n\nIt is particularly useful for:\n\n- Open-source maintainers managing multiple npm packages\n\n- Developer tool creators shipping libraries, plugins, or integrations\n\n- Engineering teams maintaining public SDKs or utilities\n\n- Agencies or startups distributing reusable packages\n\nInstead of manually checking npm statistics, this workflow delivers download insights automatically \nthrough Telegram.\n\n## What it does\n\nThe workflow combines Telegram commands with scheduled reports to provide quick insights into package usage.\n\n**Commands**\n\n- `/downloads` - all-time totals, sorted highest first\n- `/weekly` - last 7 days per package\n- `/status` - weekly and all-time combined\n- `/trending` - this week vs last week with delta and growth percentage\n- `/help` - available commands\n\n\n**Automated reports**\n\n- Weekly digest with package performance\n- Monthly summary comparing usage trends\n- Daily milestone checker that sends alerts when packages cross download thresholds\nThe workflow automatically discovers packages published under your npm username, so new packages appear in reports without any changes.\n\n\n**Smart defaults:**\n\n- Typo-tolerant command matching via Levenshtein distance\n- Slash optional, case insensitive\n- Auto-discovers packages from npm registry - new packages appear without code changes\n- Milestone check is silent on days with no crossings - zero noise\n\n## How it works\n\n1. A Telegram Trigger receives messages from the bot.\n2. A command parser identifies which command was sent.\n3. The workflow queries the npm Downloads API for package statistics.\n4. Results are formatted and sent back to Telegram.\n5. Scheduled triggers generate weekly and monthly reports and check milestone thresholds.\n\n## Set up steps\n\nSetup usually takes about **5\u201310 minutes**.\n\n1. Run n8n (Cloud or self-hosted)\n\n2. Create a Telegram bot via BotFather\n\n3. Import the workflow into n8n\n\n4. Add your Telegram bot credential\n\n5. Set your npm username in the Code nodes\n\n6. Activate the workflow"
},
"typeVersion": 1
},
{
"id": "5ed24a2d-3ba3-4a3a-ae7b-0366f20066f0",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
1104
],
"parameters": {
"color": 7,
"width": 664,
"height": 2140,
"content": "## What to change\n\n1. NPM_USERNAME in the Code nodes - set your npm username\n\n2. Telegram chatId in the digest and milestone nodes"
},
"typeVersion": 1
},
{
"id": "981950e9-cb2c-4f0a-a396-757a07cb6655",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
-896,
2352
],
"parameters": {
"color": 7,
"width": 584,
"height": 892,
"content": "## What to change\n\n1. Cron schedules if you want different reporting times\n\n2. NPM_USERNAME in the Code nodes - set your npm username\n\n3. MILESTONES array to customize alert thresholds\n"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "5caf90e6-2ef6-4e96-87da-ecdd91ffd122",
"connections": {
"Fetch Status": {
"main": [
[
{
"node": "Send Status",
"type": "main",
"index": 0
}
]
]
},
"Help Message": {
"main": [
[
{
"node": "Send Help",
"type": "main",
"index": 0
}
]
]
},
"Parse Command": {
"main": [
[
{
"node": "Switch Command",
"type": "main",
"index": 0
}
]
]
},
"Fetch Trending": {
"main": [
[
{
"node": "Send Trending",
"type": "main",
"index": 0
}
]
]
},
"Switch Command": {
"main": [
[
{
"node": "Fetch All-Time Downloads",
"type": "main",
"index": 0
}
],
[
{
"node": "Fetch Weekly Downloads",
"type": "main",
"index": 0
}
],
[
{
"node": "Fetch Status",
"type": "main",
"index": 0
}
],
[
{
"node": "Fetch Trending",
"type": "main",
"index": 0
}
],
[
{
"node": "Help Message",
"type": "main",
"index": 0
}
],
[
{
"node": "Unknown Command",
"type": "main",
"index": 0
}
]
]
},
"Has Milestones?": {
"main": [
[
{
"node": "Send Milestone Alert",
"type": "main",
"index": 0
}
]
]
},
"Unknown Command": {
"main": [
[
{
"node": "Send Unknown",
"type": "main",
"index": 0
}
]
]
},
"1st of Month 6PM": {
"main": [
[
{
"node": "Fetch Monthly Digest",
"type": "main",
"index": 0
}
]
]
},
"Check Milestones": {
"main": [
[
{
"node": "Has Milestones?",
"type": "main",
"index": 0
}
]
]
},
"Every Friday 6PM": {
"main": [
[
{
"node": "Fetch Weekly Digest",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Parse Command",
"type": "main",
"index": 0
}
]
]
},
"Fetch Weekly Digest": {
"main": [
[
{
"node": "Send Weekly Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch Monthly Digest": {
"main": [
[
{
"node": "Send Monthly Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch Weekly Downloads": {
"main": [
[
{
"node": "Send Weekly",
"type": "main",
"index": 0
}
]
]
},
"Fetch All-Time Downloads": {
"main": [
[
{
"node": "Send Downloads",
"type": "main",
"index": 0
}
]
]
},
"Milestone (daily check 9AM)": {
"main": [
[
{
"node": "Check Milestones",
"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.
telegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Title
Source: https://n8n.io/workflows/13945/ — 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.
Send A Random Recipe Once A Day To Telegram. Uses airtable, telegram, httpRequest, telegramTrigger. Scheduled trigger; 15 nodes.
This telegram bot is designed to send one random recipe a day.
This n8n template demonstrates how to automatically fetch upcoming movie releases from TMDB and let users add selected movies to their Google Calendar directly from Telegram. On a daily schedule, the
TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.
Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 53 nodes.