This workflow corresponds to n8n.io template #8039 — we link there as the canonical source.
This workflow follows the Gmail → Google Docs 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": "AI Summarize Weekly Google Docs Updates \u2192 Send Email",
"nodes": [
{
"id": "setup-instructions",
"name": "Setup Instructions",
"type": "n8n-nodes-base.stickyNote",
"position": [
200,
80
],
"parameters": {
"width": 280,
"height": 240,
"content": "\ud83d\udcc4 **SETUP REQUIRED:**\n\n1. **Google Docs Setup:**\n - Add document IDs in 'Get Docs' node\n - Enable Google Drive API access\n - Connect Google OAuth credentials\n\n2. **OpenAI API Key:**\n - Get from platform.openai.com\n - Uses GPT-4 for better summaries\n\n3. **Email Recipients:**\n - Update recipients in Gmail node\n - Customize email template\n\n4. **Schedule:**\n - Runs every Monday at 9 AM\n - Adjust cron expression if needed\n\n\ud83c\udfaf Perfect for team weekly updates!"
},
"typeVersion": 1
},
{
"id": "weekly-trigger",
"name": "Weekly Monday 9AM Trigger",
"type": "n8n-nodes-base.cron",
"position": [
200,
340
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 9 * * 1"
}
]
}
},
"typeVersion": 1
},
{
"id": "prepare-docs-list",
"name": "Prepare Docs List",
"type": "n8n-nodes-base.code",
"position": [
400,
340
],
"parameters": {
"jsCode": "// Define Google Docs to monitor\nconst docsToMonitor = [\n {\n id: 'DOC_ID_1_HERE',\n name: 'Project Status Doc',\n category: 'Projects'\n },\n {\n id: 'DOC_ID_2_HERE', \n name: 'Meeting Notes',\n category: 'Meetings'\n },\n {\n id: 'DOC_ID_3_HERE',\n name: 'Team Updates',\n category: 'Updates'\n }\n // Add more docs as needed\n];\n\n// Calculate date range for last week\nconst now = new Date();\nconst lastWeek = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));\n\nconsole.log(`Checking docs for updates between ${lastWeek.toISOString()} and ${now.toISOString()}`);\n\nconst docsList = {\n docs: docsToMonitor,\n check_date_start: lastWeek.toISOString(),\n check_date_end: now.toISOString(),\n total_docs: docsToMonitor.length\n};\n\nreturn docsToMonitor.map(doc => ({ json: doc }));"
},
"typeVersion": 2
},
{
"id": "get-doc-content",
"name": "Get Doc Content",
"type": "n8n-nodes-base.googleDocs",
"position": [
600,
340
],
"parameters": {
"fileId": "={{ $json.id }}",
"operation": "get",
"authentication": "oAuth2"
},
"typeVersion": 2
},
{
"id": "get-doc-metadata",
"name": "Get Doc Metadata",
"type": "n8n-nodes-base.googleDrive",
"position": [
600,
480
],
"parameters": {
"fileId": "={{ $json.id }}",
"options": {
"fields": "modifiedTime,lastModifyingUser,version"
},
"operation": "get",
"authentication": "oAuth2"
},
"typeVersion": 3
},
{
"id": "process-doc-data",
"name": "Process Doc Data",
"type": "n8n-nodes-base.code",
"position": [
800,
340
],
"parameters": {
"jsCode": "// Combine doc content with metadata and check if updated\nconst docContent = $('Get Doc Content').item.json;\nconst docMetadata = $('Get Doc Metadata').item.json;\nconst docInfo = $input.first().json;\n\n// Check if doc was modified in the last week\nconst lastModified = new Date(docMetadata.modifiedTime);\nconst oneWeekAgo = new Date(Date.now() - (7 * 24 * 60 * 60 * 1000));\nconst wasUpdated = lastModified > oneWeekAgo;\n\nif (!wasUpdated) {\n console.log(`Document ${docInfo.name} not updated in last week, skipping`);\n return null;\n}\n\n// Extract text content from Google Docs structure\nlet textContent = '';\nif (docContent.body && docContent.body.content) {\n docContent.body.content.forEach(element => {\n if (element.paragraph && element.paragraph.elements) {\n element.paragraph.elements.forEach(textElement => {\n if (textElement.textRun && textElement.textRun.content) {\n textContent += textElement.textRun.content;\n }\n });\n }\n });\n}\n\n// Clean up text content\ntextContent = textContent.replace(/\\s+/g, ' ').trim();\n\nconst processedDoc = {\n id: docInfo.id,\n name: docInfo.name,\n category: docInfo.category,\n content: textContent,\n last_modified: docMetadata.modifiedTime,\n modified_by: docMetadata.lastModifyingUser?.displayName || 'Unknown',\n version: docMetadata.version,\n word_count: textContent.split(' ').length,\n was_updated: wasUpdated,\n content_preview: textContent.substring(0, 200) + '...'\n};\n\nconsole.log(`Processed doc: ${processedDoc.name} (${processedDoc.word_count} words, updated: ${processedDoc.was_updated})`);\n\nreturn {\n json: processedDoc\n};"
},
"typeVersion": 2
},
{
"id": "aggregate-docs",
"name": "Aggregate Updated Docs",
"type": "n8n-nodes-base.code",
"position": [
1000,
340
],
"parameters": {
"jsCode": "// Aggregate all processed docs for AI summarization\nconst allDocs = $input.all();\nconst updatedDocs = allDocs.filter(item => item.json.was_updated);\n\nif (updatedDocs.length === 0) {\n console.log('No documents were updated this week');\n return {\n json: {\n has_updates: false,\n message: 'No documents were updated this week.',\n summary: 'No weekly summary needed - all documents remain unchanged.'\n }\n };\n}\n\n// Prepare content for AI summarization\nlet combinedContent = 'WEEKLY DOCUMENT UPDATES SUMMARY REQUEST:\\n\\n';\n\nupdatedDocs.forEach((doc, index) => {\n combinedContent += `DOCUMENT ${index + 1}: ${doc.json.name} (${doc.json.category})\\n`;\n combinedContent += `Last Modified: ${new Date(doc.json.last_modified).toLocaleDateString()} by ${doc.json.modified_by}\\n`;\n combinedContent += `Word Count: ${doc.json.word_count}\\n`;\n combinedContent += `Content: ${doc.json.content}\\n\\n---\\n\\n`;\n});\n\nconst aggregatedData = {\n has_updates: true,\n total_updated_docs: updatedDocs.length,\n docs_summary: updatedDocs.map(doc => ({\n name: doc.json.name,\n category: doc.json.category,\n modified_by: doc.json.modified_by,\n last_modified: doc.json.last_modified\n })),\n combined_content: combinedContent,\n week_range: `${new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toLocaleDateString()} - ${new Date().toLocaleDateString()}`\n};\n\nconsole.log(`Aggregated ${aggregatedData.total_updated_docs} updated documents for AI summary`);\n\nreturn {\n json: aggregatedData\n};"
},
"typeVersion": 2
},
{
"id": "generate-ai-summary",
"name": "Generate AI Summary",
"type": "n8n-nodes-base.openAi",
"position": [
1200,
340
],
"parameters": {
"resource": "chat",
"operation": "create",
"requestBody": {
"model": "gpt-4",
"messages": [
{
"role": "system",
"content": "You are an executive assistant creating weekly document update summaries. Create a professional, concise summary highlighting key changes, updates, and action items from the provided documents. Format as a business email body with clear sections and bullet points."
},
{
"role": "user",
"content": "{{ $json.combined_content }}\n\nPlease create a professional weekly summary email covering:\n1. Executive Summary\n2. Key Updates by Document\n3. Important Changes\n4. Action Items (if any)\n5. Next Week's Focus\n\nKeep it concise but informative for team leadership."
}
],
"max_tokens": 1500,
"temperature": 0.3
}
},
"typeVersion": 1
},
{
"id": "prepare-email-content",
"name": "Prepare Email Content",
"type": "n8n-nodes-base.code",
"position": [
1400,
340
],
"parameters": {
"jsCode": "// Prepare final email content\nconst summaryData = $('Aggregate Updated Docs').item.json;\nconst aiResponse = $input.first().json;\n\nif (!summaryData.has_updates) {\n return {\n json: {\n send_email: false,\n subject: 'Weekly Document Updates - No Changes',\n body: summaryData.message\n }\n };\n}\n\nconst aiSummary = aiResponse.choices[0].message.content;\n\n// Create professional email\nconst emailContent = {\n send_email: true,\n subject: `\ud83d\udcc4 Weekly Document Updates Summary - ${summaryData.week_range}`,\n body: `Dear Team,\\n\\nHere's your weekly document updates summary for ${summaryData.week_range}:\\n\\n${aiSummary}\\n\\n\ud83d\udcca UPDATED DOCUMENTS (${summaryData.total_updated_docs}):\\n${summaryData.docs_summary.map(doc => `\u2022 ${doc.name} (${doc.category}) - Updated by ${doc.modified_by} on ${new Date(doc.last_modified).toLocaleDateString()}`).join('\\n')}\\n\\n---\\nThis summary was automatically generated by your n8n workflow.\\nTo modify monitored documents, update the workflow configuration.\\n\\nBest regards,\\nYour Automation Assistant`,\n html_body: `\n <div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;\">\n <h2>\ud83d\udcc4 Weekly Document Updates Summary</h2>\n <p><strong>Period:</strong> ${summaryData.week_range}</p>\n \n <div style=\"background: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0;\">\n ${aiSummary.replace(/\\n/g, '<br>')}\n </div>\n \n <h3>\ud83d\udcca Updated Documents (${summaryData.total_updated_docs})</h3>\n <ul>\n ${summaryData.docs_summary.map(doc => `<li><strong>${doc.name}</strong> (${doc.category})<br><small>Updated by ${doc.modified_by} on ${new Date(doc.last_modified).toLocaleDateString()}</small></li>`).join('')}\n </ul>\n \n <hr>\n <p style=\"color: #666; font-size: 12px;\">This summary was automatically generated by your n8n workflow.</p>\n </div>\n `\n};\n\nconsole.log('Prepared email summary:', {\n subject: emailContent.subject,\n docs_count: summaryData.total_updated_docs\n});\n\nreturn {\n json: emailContent\n};"
},
"typeVersion": 2
},
{
"id": "send-summary-email",
"name": "Send Summary Email",
"type": "n8n-nodes-base.gmail",
"position": [
1600,
340
],
"parameters": {
"email": "user@example.com,user@example.com",
"message": "={{ $json.body }}",
"options": {
"htmlMessage": "={{ $json.html_body }}"
},
"subject": "={{ $json.subject }}",
"operation": "send"
},
"typeVersion": 2.1
}
],
"active": true,
"settings": {
"timezone": "UTC"
},
"versionId": "1",
"connections": {
"Get Doc Content": {
"main": [
[
{
"node": "Process Doc Data",
"type": "main",
"index": 0
}
]
]
},
"Get Doc Metadata": {
"main": [
[
{
"node": "Process Doc Data",
"type": "main",
"index": 0
}
]
]
},
"Process Doc Data": {
"main": [
[
{
"node": "Aggregate Updated Docs",
"type": "main",
"index": 0
}
]
]
},
"Prepare Docs List": {
"main": [
[
{
"node": "Get Doc Content",
"type": "main",
"index": 0
},
{
"node": "Get Doc Metadata",
"type": "main",
"index": 0
}
]
]
},
"Generate AI Summary": {
"main": [
[
{
"node": "Prepare Email Content",
"type": "main",
"index": 0
}
]
]
},
"Prepare Email Content": {
"main": [
[
{
"node": "Send Summary Email",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Updated Docs": {
"main": [
[
{
"node": "Generate AI Summary",
"type": "main",
"index": 0
}
]
]
},
"Weekly Monday 9AM Trigger": {
"main": [
[
{
"node": "Prepare Docs List",
"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 automatically reviews selected Google Docs every week, checks for updates, and generates a professional weekly summary email using AI. It’s perfect for keeping your team or leadership informed without manually digging through multiple documents. Weekly Monday 9AM…
Source: https://n8n.io/workflows/8039/ — 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.
Marketing agencies, digital agencies, and freelancers who need to streamline their client onboarding process and create consistent, professional documentation for new clients. Perfect for teams handli
WooriFisa. Uses agent, httpRequest, documentDefaultDataLoader, vectorStorePinecone. Scheduled trigger; 86 nodes.
WooriFisa 최종. Uses memoryMongoDbChat, agent, httpRequest, documentDefaultDataLoader. Scheduled trigger; 68 nodes.
Content Teams, Researchers, and Administrators who need to automatically process voice memos, meeting recordings, or interview audio into structured, searchable documents.
This n8n template shows you how to turn outbound sales into a fully automated machine: scrape verified leads, research them with AI, and fire off personalized cold emails while you sleep.