This workflow follows the Gmail → Google Sheets 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 →
{
"name": "LAB3",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-736,
-384
],
"id": "c992bd28-a996-466a-b0f7-0b468ebe359c",
"name": "When clicking \u2018Execute workflow\u2019"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1FcJV3CPgLRKstIbLQeIV1t0cVOmrkXL3Pugr2SHTOWM",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Feuille 1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FcJV3CPgLRKstIbLQeIV1t0cVOmrkXL3Pugr2SHTOWM/edit#gid=0"
},
"options": {
"dataLocationOnSheet": {
"values": {
"rangeDefinition": "specifyRangeA1",
"range": "A1:A"
}
},
"outputFormatting": {
"values": {
"general": "UNFORMATTED_VALUE",
"date": "FORMATTED_STRING"
}
}
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-272,
-208
],
"id": "d38dc34d-3b87-4d10-b7b2-4b32d35321e8",
"name": "Get row(s) in sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sendTo": "={{$json[\"email\"]}}",
"subject": "=Weekly Newsletter \u2013 {{ $now.startOf('week').toISODate() }}",
"message": "={{ $json.html }}",
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
544,
-288
],
"id": "f742712c-012c-4a30-9585-9b0180b681a9",
"name": "Send a message",
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list",
"cachedResultName": "GPT-4O-MINI"
},
"responses": {
"values": [
{
"content": "=You are a professional newsletter writer. Here are 15 news items:\n\n{{ $json.formatted_items }}\n\nCreate a professional daily newsletter that:\n1. Provides a brief executive summary (2-3 sentences)\n2. Groups related stories together\n3. Highlights the most important items\n4. Includes a brief insight on each story (1-2 sentences)\n5. Formats with clear headers and bullet points\n\nKeep the total length under 500 words."
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2,
"position": [
-336,
-512
],
"id": "8034213e-ee47-4af6-aed6-058f69fca28e",
"name": "Message a model",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineAll",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
320,
-624
],
"id": "3f76a93b-76f1-4ad9-b86c-c628a347c05a",
"name": "Merge"
},
{
"parameters": {
"url": "https://www.404media.co/rss",
"options": {}
},
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.2,
"position": [
-576,
-736
],
"id": "053728c1-34d2-43b3-8622-013138e8a8ab",
"name": "RSS Read",
"alwaysOutputData": false,
"executeOnce": false
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst allItems = items.map(item => {\n const data = item.json;\n return `\n Title: ${data.title || 'N/A'}\n Description: ${data.description || 'N/A'}\n Link: ${data.link || 'N/A'}\n ---`;\n }).join('\\n');\n\nreturn {\n json: {\n formatted_items: allItems,\n item_count: items.length\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-336,
-832
],
"id": "54e5b63b-ac8e-4543-9e18-c375d1b01eda",
"name": "Code in JavaScript"
},
{
"parameters": {
"jsCode": "// n8n Function Node - Generate Professional HTML Newsletter with ChatGPT Summary\n\n// Get the ChatGPT response from previous node\n// Replace 'ChatGPT' with your actual ChatGPT node name\nlet chatGptResponse = '';\nlet newsItems = [];\n\ntry {\n const gptData = $input.first().json.output[0].content\n if (gptData) {\n // Try different possible response fields\n chatGptResponse = $input.first().json.output[0].content[0].text\n }\n} catch (e) {\n chatGptResponse = '';\n}\n\n// Extract RSS items - handle both single item and array inputs\ntry {\n const items = $input.first().json.formatted_items\n if (Array.isArray(items)) {\n newsItems = items.map(item => item.json || item).filter(item => item);\n } else {\n newsItems = [items.json || items];\n }\n} catch (e) {\n newsItems = [];\n}\n\n// Create clean HTML newsletter template with ChatGPT injection\nconst htmlNewsletter = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Daily News Letter</title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n \n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n background-color: #f5f5f5;\n color: #2c3e50;\n line-height: 1.6;\n }\n \n .container {\n max-width: 600px;\n margin: 0 auto;\n background-color: #ffffff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n }\n \n .header {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n padding: 40px 30px;\n text-align: center;\n color: white;\n }\n \n .header h1 {\n font-size: 28px;\n font-weight: 700;\n letter-spacing: -0.5px;\n margin-bottom: 8px;\n }\n \n .header p {\n font-size: 14px;\n opacity: 0.9;\n font-weight: 300;\n }\n \n .content {\n padding: 40px 30px;\n }\n \n /* Executive Summary Section */\n .executive-summary {\n background: linear-gradient(135deg, rgba(102, 126, 234, 0.08) 0%, rgba(118, 75, 162, 0.08) 100%);\n padding: 24px;\n border-radius: 12px;\n margin-bottom: 32px;\n border-left: 4px solid #667eea;\n }\n \n .executive-summary h2 {\n font-size: 16px;\n font-weight: 700;\n margin-bottom: 14px;\n color: #2c3e50;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .summary-icon {\n font-size: 18px;\n }\n \n .executive-summary-content {\n font-size: 14px;\n color: #444;\n line-height: 1.8;\n font-weight: 500;\n }\n \n .executive-summary-content p {\n margin-bottom: 12px;\n }\n \n .executive-summary-content p:last-child {\n margin-bottom: 0;\n }\n \n .executive-summary-content ul,\n .executive-summary-content ol {\n margin-left: 20px;\n margin-top: 12px;\n margin-bottom: 12px;\n }\n \n .executive-summary-content li {\n margin-bottom: 8px;\n color: #555;\n }\n \n .divider {\n height: 2px;\n background: linear-gradient(90deg, transparent, #667eea, transparent);\n margin: 32px 0;\n }\n \n .stories-section h2 {\n font-size: 18px;\n font-weight: 700;\n margin-bottom: 24px;\n color: #2c3e50;\n }\n \n .news-item {\n padding: 24px 0;\n border-bottom: 1px solid #e8e8e8;\n transition: all 0.3s ease;\n }\n \n .news-item:last-child {\n border-bottom: none;\n }\n \n .news-item-header {\n display: flex;\n align-items: flex-start;\n margin-bottom: 12px;\n }\n \n .news-number {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border-radius: 50%;\n font-weight: 600;\n font-size: 13px;\n flex-shrink: 0;\n margin-right: 12px;\n }\n \n .news-item h3 {\n font-size: 16px;\n font-weight: 600;\n color: #2c3e50;\n margin-bottom: 8px;\n line-height: 1.4;\n }\n \n .news-item-meta {\n font-size: 12px;\n color: #999;\n margin-bottom: 10px;\n }\n \n .news-item p {\n font-size: 14px;\n color: #555;\n line-height: 1.7;\n margin-bottom: 12px;\n }\n \n .read-more {\n display: inline-block;\n color: #667eea;\n text-decoration: none;\n font-size: 13px;\n font-weight: 600;\n transition: color 0.3s ease;\n }\n \n .read-more:hover {\n color: #764ba2;\n text-decoration: underline;\n }\n \n .footer {\n background-color: #f9f9f9;\n padding: 30px;\n text-align: center;\n border-top: 1px solid #e8e8e8;\n }\n \n .footer p {\n font-size: 12px;\n color: #999;\n margin-bottom: 8px;\n }\n \n .footer-divider {\n height: 1px;\n background-color: #e8e8e8;\n margin: 16px 0;\n }\n \n .social-links {\n margin-top: 16px;\n }\n \n .social-links a {\n display: inline-block;\n margin: 0 8px;\n color: #667eea;\n text-decoration: none;\n font-size: 12px;\n font-weight: 500;\n }\n \n @media (max-width: 600px) {\n .container {\n max-width: 100%;\n border-radius: 0;\n }\n \n .header {\n padding: 30px 20px;\n }\n \n .header h1 {\n font-size: 24px;\n }\n \n .content {\n padding: 24px 20px;\n }\n \n .news-item {\n padding: 16px 0;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <!-- Header -->\n <div class=\"header\">\n <h1>\ud83d\udcf0 Daily News Letter</h1>\n <p>Your curated news digest \u2022 ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</p>\n </div>\n \n <!-- Content -->\n <div class=\"content\">\n <!-- Executive Summary from ChatGPT -->\n <div class=\"executive-summary\">\n <h2>\n <span class=\"summary-icon\">\u2728</span>\n Executive Summary\n </h2>\n <div class=\"executive-summary-content\">\n ${formatChatGptOutput(chatGptResponse)}\n </div>\n </div>\n \n <div class=\"divider\"></div>\n \n <!-- Full Stories -->\n <div class=\"stories-section\">\n <h2>\ud83d\udcd1 Today's Stories</h2>\n \n <div>\n ${newsItems.map((item, index) => `\n <div class=\"news-item\">\n <div class=\"news-item-header\">\n <div class=\"news-number\">${index + 1}</div>\n <div>\n <h3>${sanitizeHtml(item.title || 'Untitled')}</h3>\n <div class=\"news-item-meta\">\n ${item.creator ? `By ${sanitizeHtml(item.creator)} \u2022 ` : ''}${formatDate(item.pubDate)}\n </div>\n </div>\n </div>\n <p>${sanitizeHtml(item.description || item.content || 'No description available').substring(0, 220)}...</p>\n <a href=\"${item.link}\" class=\"read-more\">Read full story \u2192</a>\n </div>\n `).join('')}\n </div>\n </div>\n </div>\n \n <!-- Footer -->\n <div class=\"footer\">\n <p><strong>Daily News Letter</strong></p>\n <p>Delivered to your inbox every morning</p>\n <div class=\"footer-divider\"></div>\n <div class=\"social-links\">\n <a href=\"#unsubscribe\">Unsubscribe</a>\n <a href=\"#preferences\">Preferences</a>\n <a href=\"#archive\">Archive</a>\n </div>\n <p style=\"margin-top: 16px; font-size: 11px;\">\u00a9 ${new Date().getFullYear()} News Letter. All rights reserved.</p>\n </div>\n </div>\n</body>\n</html>\n`;\n\n// Helper functions\nfunction sanitizeHtml(text) {\n if (!text) return '';\n // Convert to string if it's not already\n text = String(text);\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/<[^>]*>/g, '');\n}\n\nfunction formatDate(dateString) {\n if (!dateString) return 'Recently';\n const date = new Date(dateString);\n const today = new Date();\n const yesterday = new Date(today);\n yesterday.setDate(yesterday.getDate() - 1);\n \n if (date.toDateString() === today.toDateString()) {\n return 'Today';\n } else if (date.toDateString() === yesterday.toDateString()) {\n return 'Yesterday';\n } else {\n return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n }\n}\n\nfunction formatChatGptOutput(text) {\n if (!text) return '<p>Summary not available</p>';\n \n // Convert to string if it's not already\n text = String(text);\n \n // Sanitize the text\n let formatted = sanitizeHtml(text);\n \n // Convert markdown-style formatting to HTML\n // Convert **bold** to <strong>\n formatted = formatted.replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>');\n \n // Convert *italic* to <em>\n formatted = formatted.replace(/\\*(.*?)\\*/g, '<em>$1</em>');\n \n // Convert numbered lists\n formatted = formatted.replace(/^\\d+\\.\\s+/gm, '');\n \n // Split by double line breaks for paragraphs\n const paragraphs = formatted.split(/\\n\\n+/);\n \n return paragraphs\n .filter(p => p.trim())\n .map(p => {\n // Check if it's a list item\n if (p.trim().match(/^[-\u2022]/m)) {\n const items = p.split(/\\n/).filter(line => line.trim());\n return '<ul>' + items.map(item => `<li>${item.replace(/^[-\u2022]\\s+/, '')}</li>`).join('') + '</ul>';\n }\n return `<p>${p.trim()}</p>`;\n })\n .join('');\n}\n\nreturn {\n json: {\n html: htmlNewsletter,\n itemCount: newsItems.length,\n summaryIncluded: !!chatGptResponse\n }\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
160,
-752
],
"id": "00e649d6-0bc7-435e-8ca3-30521ecafd13",
"name": "Code in JavaScript1"
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineAll",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-80,
-816
],
"id": "94a56944-0dfe-49be-a3cf-d8afeae0b781",
"name": "Merge1"
}
],
"connections": {
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Get row(s) in sheet",
"type": "main",
"index": 0
},
{
"node": "RSS Read",
"type": "main",
"index": 0
}
]
]
},
"Get row(s) in sheet": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Message a model": {
"main": [
[
{
"node": "Merge1",
"type": "main",
"index": 1
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"RSS Read": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
},
{
"node": "Merge1",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Merge1": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "b9d9fa27-b0c1-4b38-afa6-af117901f4ed",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "KyDvt1Puta6LQr5l",
"tags": [
{
"name": "v3.0",
"id": "piwGWxfeN7bLb5m8",
"updatedAt": "2025-12-02T22:02:24.422Z",
"createdAt": "2025-12-02T22:02:24.422Z"
}
]
}
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.
gmailOAuth2googleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
LAB3. Uses googleSheets, gmail, openAi, rssFeedRead. Event-driven trigger; 9 nodes.
Source: https://github.com/FRWD789/n8nNewsLettre/blob/037622d9671e103d83d1f537c6296cf87099d1a2/workflows/LAB3.json — 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.
Transform YouTube Videos to Social Media Content with Vizard AI and GPT‑4.1
This intelligent email automation workflow helps you maximize engagement through domain-based outreach. It utilizes AI-powered personalization and strategic follow-ups to increase response rates. The
Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:
Send a target niche and location via Telegram message Workflow discovers businesses via Google Maps API AI enriches contacts with email and LinkedIn data via Serper GPT-4o scores and qualifies each le
This workflow is designed for SEO professionals, digital agencies, content creators, and WordPress site owners who want to improve their search engine rankings automatically. It’s also perfect for cur