This workflow corresponds to n8n.io template #13552 — 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "5a3638b4-dd45-477c-82c8-24b33e8334c1",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-224,
80
],
"parameters": {
"width": 360,
"height": 952,
"content": "## \ud83e\udd16 Weekly GitHub Digest\n\nSends **one combined weekly email** with 3 sections:\n- \ud83d\ude80 **Releases** \u2014 new releases from your watched repos\n- \ud83d\udc65 **Commits** \u2014 recent activity from tracked users or repos\n- \ud83d\udd25 **Trending** \u2014 GitHub trending repos for the day\n\nOnly sends if at least one section has content.\n\n### How it works\n1. 3 parallel branches run at 9am Monday\n2. **Releases**: checks each watched repo for new releases this week\n3. **Commits**: fetches activity from tracked users OR repos. Accepts both user (e.g.`torvalds`) and repo (e.g`huggingface/transformers`) \n4. **Trending**: scrapes GitHub trending page for today\u2019s top repos\n5. All data merges into one HTML email\n\n### Setup steps\n1. Open **Set Variables** and set:\n - `recipient_email` \u2192 your email\n - `days_back` \u2192 lookback window (default: 7)\n - `release_repos` \u2192 e.g. `[\"n8n-io/n8n\",\"facebook/react\"]`\n - `tracked_entities` \u2192 e.g. `[\"torvalds\",\"huggingface/transformers\"]`\n2. Connect your **Gmail credential** in the Send node\n3. Activate\n\n### Tips\n- Add a GitHub token header for 5000 req/hr limit\n- `tracked_entities` accepts usernames AND repo paths"
},
"typeVersion": 1
},
{
"id": "15d8a903-54f0-4291-966a-0a84692a5197",
"name": "Every Monday at 9am",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
240,
384
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.2
},
{
"id": "7f6e199c-657e-4869-9d37-f8312a2c54dc",
"name": "Releases Section",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
-160
],
"parameters": {
"color": 7,
"width": 664,
"height": 356,
"content": "## Releases\nFetches the latest release per watched repo and collects those published within the lookback window."
},
"typeVersion": 1
},
{
"id": "166f731a-f1b5-47ec-8536-2aaea728ad19",
"name": "Split Watched Repos",
"type": "n8n-nodes-base.splitOut",
"position": [
704,
-48
],
"parameters": {
"options": {},
"fieldToSplitOut": "release_repos"
},
"typeVersion": 1
},
{
"id": "940a1583-8ad8-40b5-8323-92f7b9c900a9",
"name": "Aggregate Releases",
"type": "n8n-nodes-base.code",
"position": [
1136,
-48
],
"parameters": {
"jsCode": "const allItems = $input.all();\nconst config = $('Set Variables').first().json;\nconst cutoff = new Date(Date.now() - config.days_back * 24 * 60 * 60 * 1000);\n\nconst releases = [];\n\nfor (const item of allItems) {\n // /releases?per_page=1 returns an array \u2014 take the first (most recent)\n const releaseList = Array.isArray(item.json) ? item.json : [item.json];\n\n for (const r of releaseList) {\n if (!r.published_at || !r.html_url) continue;\n if (r.draft) continue; // skip drafts only; pre-releases are included (n8n marks stable releases as pre-release on GitHub)\n if (new Date(r.published_at) < cutoff) continue;\n\n const repo = r.html_url.split('/releases/')[0].replace('https://github.com/', '');\n const body = r.body\n ? r.body.replace(/#{1,6}\\s/g, '').replace(/<!--[\\s\\S]*?-->/g, '').trim().substring(0, 280) + '...'\n : 'No release notes.';\n\n releases.push({\n repo,\n tag: r.tag_name,\n name: r.name || r.tag_name,\n url: r.html_url,\n published_at: r.published_at,\n body,\n prerelease: r.prerelease\n });\n break; // only take the most recent per repo\n }\n}\n\nreturn [{ json: { releases, release_count: releases.length } }];"
},
"typeVersion": 2
},
{
"id": "4b4091a3-440c-433f-8905-bae1e382ca32",
"name": "Commits Section",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
400
],
"parameters": {
"color": 7,
"width": 632,
"height": 260,
"content": "## Commits\nSupports both GitHub users (`torvalds`) and repos (`huggingface/transformers`). "
},
"typeVersion": 1
},
{
"id": "1161e57c-c587-462e-b87c-a9a1b893b026",
"name": "Extract Commits",
"type": "n8n-nodes-base.code",
"position": [
1040,
496
],
"parameters": {
"jsCode": "const allItems = $input.all();\nconst config = $('Set Variables').first().json;\nconst cutoff = new Date(Date.now() - config.days_back * 24 * 60 * 60 * 1000);\nconst commits = [];\n\nfor (const item of allItems) {\n const data = item.json;\n if (!data || data.message === 'Not Found') continue;\n\n // User/Org events format\n if (data.type) {\n if (data.type !== 'PushEvent') continue;\n if (new Date(data.created_at) < cutoff) continue;\n const actor = data.actor?.login || 'unknown';\n const repoName = data.repo?.name || 'unknown';\n for (const commit of (data.payload?.commits || []).slice(0,3)) {\n commits.push({ author: actor, author_url: `https://github.com/${actor}`, repo: repoName, repo_url: `https://github.com/${repoName}`, message: (commit.message||'').split('\\n')[0].substring(0,100), sha: (commit.sha||'').substring(0,7), url: `https://github.com/${repoName}/commit/${commit.sha}`, date: data.created_at });\n }\n }\n // Repo commits format\n else if (data.sha && data.commit) {\n const commitDate = data.commit.author?.date || data.commit.committer?.date;\n if (commitDate && new Date(commitDate) < cutoff) continue;\n const repoBase = (data.html_url||'').split('/commit/')[0];\n const repoPath = repoBase.replace('https://github.com/','');\n commits.push({ author: data.author?.login || data.commit.author?.name || 'unknown', author_url: data.author ? `https://github.com/${data.author.login}` : '#', repo: repoPath, repo_url: repoBase, message: (data.commit.message||'').split('\\n')[0].substring(0,100), sha: (data.sha||'').substring(0,7), url: data.html_url||'', date: commitDate||'' });\n }\n}\n\nconst seen = new Set();\nconst unique = commits.filter(c => { if(seen.has(c.sha)) return false; seen.add(c.sha); return true; }).slice(0,40);\nreturn [{ json: { commits: unique, commit_count: unique.length } }];"
},
"typeVersion": 2
},
{
"id": "24e7ab70-e28e-41c9-a14d-669ce9c76746",
"name": "Trending Section",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
832
],
"parameters": {
"color": 7,
"width": 512,
"height": 292,
"content": "## GitHub Trending Today\nScrapes the GitHub trending page for the day the workflow runs."
},
"typeVersion": 1
},
{
"id": "0416371c-2248-442f-8ee9-27a3425f2fc9",
"name": "Fetch GitHub Trending Page",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
736,
928
],
"parameters": {
"url": "https://github.com/trending?since=daily",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
},
{
"name": "Accept",
"value": "text/html,application/xhtml+xml"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "31861753-3d34-41fc-aac1-813efcee001f",
"name": "Parse Trending HTML",
"type": "n8n-nodes-base.code",
"position": [
928,
928
],
"parameters": {
"jsCode": "const rawData = $input.first().json;\nconst html = rawData.data || rawData.body || '';\n\nconst repos = [];\nlet pos = 0;\n\nwhile (repos.length < 10) {\n const start = html.indexOf('class=\"Box-row\"', pos);\n if (start === -1) break;\n const end = html.indexOf('</article>', start);\n if (end === -1) break;\n pos = end + 10;\n const block = html.substring(start, end);\n\n const linkMatch = block.match(/href=\"\\/(([\\w][\\w.-]+)\\/([\\w][\\w.-]+))\"/);\n if (!linkMatch) continue;\n const repoPath = linkMatch[1];\n if (['trending','explore','settings','login','signup'].some(s => repoPath.startsWith(s))) continue;\n\n const descMatch = block.match(/col-9[^\"]*\"[^>]*>\\s*([^<]{3,})/);\n const description = descMatch ? descMatch[1].trim().replace(/\\s+/g,' ').substring(0,120) : '';\n\n const langMatch = block.match(/programmingLanguage\"[^>]*>([^<]+)</);\n const language = langMatch ? langMatch[1].trim() : '';\n\n const starsMatch = block.match(/([\\d,]+)\\s+stars?\\s+today/i);\n const starsToday = starsMatch ? starsMatch[1] : '';\n\n repos.push({ name: repoPath, url: `https://github.com/${repoPath}`, description, language, stars_today: starsToday });\n}\n\nreturn [{ json: { trending_repos: repos, trending_count: repos.length } }];"
},
"typeVersion": 2
},
{
"id": "5132a98d-3f32-496e-9905-8bec7dc28643",
"name": "Merge Releases + Commits",
"type": "n8n-nodes-base.merge",
"position": [
1728,
288
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "753b0c40-c481-4e5c-8bff-9a5ac49b6aa4",
"name": "Merge All Data",
"type": "n8n-nodes-base.merge",
"position": [
1888,
496
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "16f88f96-e5eb-4898-a4b1-ac1501e780d2",
"name": "Has Any Content?",
"type": "n8n-nodes-base.if",
"position": [
2080,
496
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "c1",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.release_count }}",
"rightValue": 0
},
{
"id": "c2",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.commit_count }}",
"rightValue": 0
},
{
"id": "c3",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.trending_count }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "63f1d1c8-1e1b-414f-8a32-9bcfb4523b41",
"name": "Build Combined Email",
"type": "n8n-nodes-base.code",
"position": [
2288,
416
],
"parameters": {
"jsCode": "const items = $input.all();\n\nconst relData = (items.find(i => i.json.releases) || { json: { releases: [], release_count: 0 } }).json;\nconst cmData = (items.find(i => i.json.commits) || { json: { commits: [], commit_count: 0 } }).json;\nconst trData = (items.find(i => i.json.trending_repos) || { json: { trending_repos: [], trending_count: 0 } }).json;\n\nconst releases = relData.releases || [];\nconst commits = cmData.commits || [];\nconst trending = trData.trending_repos || [];\n\nconst today = new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });\nconst subject = `\\uD83E\\uDD16 Weekly GitHub Digest \\u2014 ${today}`;\n\nconst releasesHtml = releases.length === 0\n ? `<p style='color:#999;font-style:italic'>No new releases this week.</p>`\n : releases.map(r => `<div style='margin-bottom:16px;padding:12px;background:#fafafa;border-left:3px solid #ff6d5a;border-radius:4px'><a href='${r.url}' style='font-weight:600;color:#ff6d5a;text-decoration:none'>${r.repo} ${r.tag}</a><span style='color:#999;font-size:12px;margin-left:8px'>${new Date(r.published_at).toLocaleDateString()}</span><p style='margin:6px 0 0;font-size:13px;color:#555'>${r.body}</p></div>`).join('');\n\nconst commitsHtml = commits.length === 0\n ? `<p style='color:#999;font-style:italic'>No recent commits found.</p>`\n : commits.slice(0, 15).map(c => `<div style='margin-bottom:10px;padding:8px 12px;background:#fafafa;border-left:3px solid #6366f1;border-radius:4px'><a href='${c.url}' style='font-size:13px;color:#6366f1;text-decoration:none;font-weight:500'>${c.message}</a><br><span style='font-size:11px;color:#999'>${c.repo} \\u00b7 ${c.sha} \\u00b7 ${c.author} \\u00b7 ${c.date ? new Date(c.date).toLocaleDateString() : ''}</span></div>`).join('');\n\nconst trendingHtml = trending.length === 0\n ? `<p style='color:#999;font-style:italic'>Could not load trending repos today.</p>`\n : trending.map((r, i) => `<div style='margin-bottom:10px;padding:8px 12px;background:#fafafa;border-left:3px solid #10b981;border-radius:4px'><span style='color:#999;font-size:11px;margin-right:8px'>#${i+1}</span><a href='${r.url}' style='font-weight:600;color:#10b981;text-decoration:none'>${r.name}</a>${r.language ? `<span style='font-size:11px;color:#999;margin-left:8px'>${r.language}</span>` : ''}${r.stars_today ? `<span style='font-size:11px;color:#f59e0b;margin-left:8px'>\\u2605 ${r.stars_today} today</span>` : ''}<br><span style='font-size:12px;color:#666;margin-top:2px'>${r.description || ''}</span></div>`).join('');\n\nconst html = `<div style='font-family:sans-serif;max-width:620px;margin:0 auto;padding:24px;color:#333'><h2 style='margin-bottom:4px'>\\uD83E\\uDD16 Weekly GitHub Digest</h2><p style='color:#aaa;font-size:13px;margin-top:0;margin-bottom:28px'>${today}</p><h3 style='color:#ff6d5a;margin-bottom:14px;padding-bottom:6px;border-bottom:2px solid #ff6d5a'>\\uD83D\\uDE80 New Releases (${releases.length})</h3>${releasesHtml}<h3 style='color:#6366f1;margin-bottom:14px;padding-bottom:6px;border-bottom:2px solid #6366f1;margin-top:32px'>\\uD83D\\uDC65 Developer Commits (${commits.length})</h3>${commitsHtml}<h3 style='color:#10b981;margin-bottom:14px;padding-bottom:6px;border-bottom:2px solid #10b981;margin-top:32px'>\\uD83D\\uDD25 Trending Today (${trending.length})</h3>${trendingHtml}<hr style='border:none;border-top:1px solid #eee;margin:32px 0'><p style='font-size:12px;color:#bbb'>Weekly digest created by Dahiana Porto via N8N</p></div>`;\n\nreturn [{ json: { html, subject, release_count: releases.length, commit_count: commits.length, trending_count: trending.length } }];"
},
"typeVersion": 2
},
{
"id": "8c203c76-5a0c-4ec7-a265-8f706d1f4340",
"name": "Send Weekly Digest",
"type": "n8n-nodes-base.gmail",
"position": [
2512,
416
],
"parameters": {
"sendTo": "={{ $('Set Variables').first().json.recipient_email }}",
"message": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "={{ $json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "cbcc5fae-b715-47b6-8bab-3ecaf0c335f6",
"name": "Fetch Latest Release",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
912,
-48
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.release_repos }}/releases?per_page=1",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "0c910eda-7a6f-4641-826c-7e886178116e",
"name": "Fetch Entity Data",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
880,
496
],
"parameters": {
"url": "={{ $json.tracked_entities.includes('/') ? 'https://api.github.com/repos/' + $json.tracked_entities + '/commits?per_page=20&since=' + $now.minus({days: $('Set Variables').first().json.days_back}).toISO() : 'https://api.github.com/users/' + $json.tracked_entities + '/events/public?per_page=30' }}",
"options": {},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "X-GitHub-Api-Version",
"value": "2022-11-28"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "a1472e9e-8c29-4dcd-95d4-6a3787da7288",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
832,
-48
],
"parameters": {
"color": 3,
"width": 272,
"height": 352,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n#### Important\nGitHub's /releases/latest endpoint skips anything flagged as prerelease.\nIf you only want to fetch usual releases, use \n\u00b4https://api.github.com/repos/repo-name/releases/latest\u00b4"
},
"typeVersion": 1
},
{
"id": "1373c533-c8cb-43ea-a2a7-2f492fcab55e",
"name": "Split Tracked Entities",
"type": "n8n-nodes-base.splitOut",
"position": [
720,
496
],
"parameters": {
"options": {},
"fieldToSplitOut": "tracked_entities"
},
"typeVersion": 1
},
{
"id": "9b9f4c5d-daaf-4901-8604-c993b13d7ca0",
"name": "Do Nothing",
"type": "n8n-nodes-base.noOp",
"position": [
2288,
608
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2aa3eb79-1f86-4c68-a48f-159cb59c5f7f",
"name": "Set Variables",
"type": "n8n-nodes-base.set",
"position": [
464,
384
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-1",
"name": "recipient_email",
"type": "string",
"value": "user@example.com"
},
{
"id": "cfg-2",
"name": "days_back",
"type": "number",
"value": 7
},
{
"id": "cfg-3",
"name": "release_repos",
"type": "array",
"value": "={{ ['n8n-io/n8n'] }}"
},
{
"id": "cfg-4",
"name": "tracked_entities",
"type": "array",
"value": "={{ ['anthropics/claude', 'huggingface/transformers'] }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "3083f525-fbff-4ac7-aa0b-6b904a67978e",
"name": "Commits Section1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
160
],
"parameters": {
"color": 7,
"width": 264,
"height": 388,
"content": "## Email Template\n\nAggregates output from all three branches into a single HTML email. \n\nOutput fields: `html`, `subject`, `release_count`,`commit_count`, `trending_count`.\n\nFeel free to modify this template."
},
"typeVersion": 1
}
],
"connections": {
"Set Variables": {
"main": [
[
{
"node": "Split Watched Repos",
"type": "main",
"index": 0
},
{
"node": "Fetch GitHub Trending Page",
"type": "main",
"index": 0
},
{
"node": "Split Tracked Entities",
"type": "main",
"index": 0
}
]
]
},
"Merge All Data": {
"main": [
[
{
"node": "Has Any Content?",
"type": "main",
"index": 0
}
]
]
},
"Extract Commits": {
"main": [
[
{
"node": "Merge Releases + Commits",
"type": "main",
"index": 1
}
]
]
},
"Has Any Content?": {
"main": [
[
{
"node": "Build Combined Email",
"type": "main",
"index": 0
}
],
[
{
"node": "Do Nothing",
"type": "main",
"index": 0
}
]
]
},
"Fetch Entity Data": {
"main": [
[
{
"node": "Extract Commits",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Releases": {
"main": [
[
{
"node": "Merge Releases + Commits",
"type": "main",
"index": 0
}
]
]
},
"Every Monday at 9am": {
"main": [
[
{
"node": "Set Variables",
"type": "main",
"index": 0
}
]
]
},
"Parse Trending HTML": {
"main": [
[
{
"node": "Merge All Data",
"type": "main",
"index": 1
}
]
]
},
"Split Watched Repos": {
"main": [
[
{
"node": "Fetch Latest Release",
"type": "main",
"index": 0
}
]
]
},
"Build Combined Email": {
"main": [
[
{
"node": "Send Weekly Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch Latest Release": {
"main": [
[
{
"node": "Aggregate Releases",
"type": "main",
"index": 0
}
]
]
},
"Split Tracked Entities": {
"main": [
[
{
"node": "Fetch Entity Data",
"type": "main",
"index": 0
}
]
]
},
"Merge Releases + Commits": {
"main": [
[
{
"node": "Merge All Data",
"type": "main",
"index": 0
}
]
]
},
"Fetch GitHub Trending Page": {
"main": [
[
{
"node": "Parse Trending HTML",
"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.
gmailOAuth2
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Stay on top of the GitHub ecosystem with a single automated weekly email covering it all. A schedule trigger fires every Monday at 9am and reads your config variables. Feel free to modify schedule. Three branches run in parallel: Releases: fetches the latest release from each…
Source: https://n8n.io/workflows/13552/ — 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.
YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.
Instead of providing a routine check, it focuses on significant movements by: Sending a Slack alert only if a query crosses a defined movement threshold. Emailing a structured report with the Top 25 i
Looking for a way to track GitHub bounty issues automatically and get notified in real time? This GitHub Bounty Tracker workflow monitors repositories for issues labeled 💎 Bounty, logs them in Google
This workflow automatically sends a beautifully designed HTML newsletter every Sunday at 8 AM, featuring products currently on sale from your Algolia-powered e-commerce store.
This workflow automatically identifies your weekly bestselling product from your Algolia-powered e-commerce store and generates a cinematic product video using Google VEO 3.0 AI, helping marketing tea