This workflow follows the Emailsend → 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 →
{
"name": "Flow 9 - Personal Weekly Review",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
0
],
"triggerAtHour": 19
}
]
}
},
"name": "Cron Trigger (Sunday 7PM)",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
240,
300
]
},
{
"parameters": {
"url": "={{$env.BACKEND_API_URL}}/api/webhooks/n8n/tasks",
"authentication": "none",
"options": {}
},
"name": "HTTP: Get All Tasks",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
460,
300
]
},
{
"parameters": {
"url": "={{$env.BACKEND_API_URL}}/api/webhooks/n8n/forecasts/latest",
"authentication": "none",
"options": {}
},
"name": "HTTP: Get Forecasts",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
680,
300
]
},
{
"parameters": {
"functionCode": "// Calculate weekly statistics per owner\nconst tasksResponse = $node['HTTP: Get All Tasks'].json;\nconst forecastsResponse = $input.first().json;\n\nconst tasks = tasksResponse.tasks || [];\nconst forecasts = forecastsResponse.forecasts || [];\n\nconst now = new Date();\nconst oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);\nconst nextWeek = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);\n\n// Group tasks by owner\nconst ownerMap = new Map();\n\nfor (const task of tasks) {\n const ownerId = task.owner_id;\n if (!ownerId) continue;\n \n if (!ownerMap.has(ownerId)) {\n ownerMap.set(ownerId, {\n owner_id: ownerId,\n owner_name: task.owner_name,\n // This week stats\n completed_this_week: [],\n created_this_week: [],\n // Current status\n total_tasks: 0,\n done_tasks: 0,\n in_progress_tasks: 0,\n todo_tasks: 0,\n overdue_tasks: [],\n // Next week\n upcoming_deadlines: [],\n // Risk assessment\n high_risk_tasks: []\n });\n }\n \n const ownerData = ownerMap.get(ownerId);\n ownerData.total_tasks++;\n \n // Count by status\n if (task.status === 'done') {\n ownerData.done_tasks++;\n // Check if completed this week\n const updatedAt = task.updated_at ? new Date(task.updated_at) : null;\n if (updatedAt && updatedAt >= oneWeekAgo) {\n ownerData.completed_this_week.push(task);\n }\n } else if (task.status === 'in_progress') {\n ownerData.in_progress_tasks++;\n } else {\n ownerData.todo_tasks++;\n }\n \n // Created this week\n const createdAt = task.created_at ? new Date(task.created_at) : null;\n if (createdAt && createdAt >= oneWeekAgo) {\n ownerData.created_this_week.push(task);\n }\n \n // Check overdue\n if (task.deadline && task.status !== 'done') {\n const deadline = new Date(task.deadline);\n if (deadline < now) {\n ownerData.overdue_tasks.push(task);\n } else if (deadline <= nextWeek) {\n ownerData.upcoming_deadlines.push(task);\n }\n }\n \n // Check high risk from forecasts\n const taskForecast = forecasts.find(f => f.task_id === task.id);\n if (taskForecast && (taskForecast.risk_level === 'high' || taskForecast.risk_level === 'critical')) {\n ownerData.high_risk_tasks.push({\n ...task,\n risk_level: taskForecast.risk_level,\n risk_percentage: taskForecast.risk_percentage\n });\n }\n}\n\n// Filter owners with activity\nconst activeOwners = Array.from(ownerMap.values()).filter(owner => \n owner.total_tasks > 0\n);\n\nif (activeOwners.length === 0) {\n return [];\n}\n\nreturn activeOwners.map(owner => ({ json: owner }));"
},
"name": "Function: Calculate Weekly Stats",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
900,
300
]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"name": "Split Into Items",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"position": [
1120,
300
]
},
{
"parameters": {
"url": "={{$env.BACKEND_API_URL}}/api/webhooks/n8n/user-email/{{$json.owner_id}}",
"authentication": "none",
"options": {}
},
"name": "HTTP: Get Owner Email",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1340,
300
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json.owner_email}}",
"operation": "isNotEmpty"
}
]
}
},
"name": "IF: Has Email",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
1560,
300
]
},
{
"parameters": {
"functionCode": "// Build AI prompt for personalized weekly advice\nconst ownerInfo = $input.first().json;\nconst stats = $node['Split Into Items'].json;\n\nconst completionRate = stats.total_tasks > 0 \n ? Math.round((stats.done_tasks / stats.total_tasks) * 100) \n : 0;\n\nconst prompt = `B\u1ea1n l\u00e0 tr\u1ee3 l\u00fd AI gi\u00fap ng\u01b0\u1eddi d\u00f9ng qu\u1ea3n l\u00fd deadline C\u00c1 NH\u00c2N.\n\nTh\u1ed1ng k\u00ea tu\u1ea7n c\u1ee7a \"${stats.owner_name}\":\n- T\u1ed5ng tasks: ${stats.total_tasks}\n- Ho\u00e0n th\u00e0nh: ${stats.done_tasks} (${completionRate}%)\n- \u0110ang l\u00e0m: ${stats.in_progress_tasks}\n- Ch\u01b0a b\u1eaft \u0111\u1ea7u: ${stats.todo_tasks}\n- Qu\u00e1 h\u1ea1n: ${stats.overdue_tasks.length}\n- Tu\u1ea7n n\u00e0y ho\u00e0n th\u00e0nh: ${stats.completed_this_week.length} tasks\n- Tu\u1ea7n n\u00e0y t\u1ea1o m\u1edbi: ${stats.created_this_week.length} tasks\n- Deadline tu\u1ea7n t\u1edbi: ${stats.upcoming_deadlines.length} tasks\n- Tasks r\u1ee7i ro cao: ${stats.high_risk_tasks.length}\n\nVi\u1ebft b\u00e1o c\u00e1o c\u00e1 nh\u00e2n h\u00f3a (150-200 t\u1eeb, ti\u1ebfng Vi\u1ec7t, tone th\u00e2n thi\u1ec7n \u0111\u1ed9ng vi\u00ean):\n1. \u0110\u00e1nh gi\u00e1 hi\u1ec7u su\u1ea5t tu\u1ea7n qua (khen ng\u1ee3i n\u1ebfu t\u1ed1t, \u0111\u1ed9ng vi\u00ean n\u1ebfu ch\u01b0a t\u1ed1t)\n2. \u0110i\u1ec3m c\u1ea7n c\u1ea3i thi\u1ec7n (n\u1ebfu c\u00f3 overdue ho\u1eb7c high risk)\n3. G\u1ee3i \u00fd c\u1ee5 th\u1ec3 cho tu\u1ea7n t\u1edbi (3-4 \u0111i\u1ec3m ng\u1eafn g\u1ecdn)\n\nKh\u00f4ng s\u1eed d\u1ee5ng tone nghi\u00eam kh\u1eafc. \u0110\u00e2y l\u00e0 c\u00f4ng c\u1ee5 C\u00c1 NH\u00c2N, h\u00e3y vi\u1ebft nh\u01b0 m\u1ed9t ng\u01b0\u1eddi b\u1ea1n \u0111ang h\u1ed7 tr\u1ee3.`;\n\nreturn {\n json: {\n prompt: prompt,\n owner_email: ownerInfo.owner_email,\n owner_name: stats.owner_name,\n stats: stats,\n completion_rate: completionRate\n }\n};"
},
"name": "Function: Build AI Prompt",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1780,
300
]
},
{
"parameters": {
"url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent?key={{$env.GEMINI_API_KEY}}",
"authentication": "none",
"method": "POST",
"sendBody": true,
"contentType": "json",
"specifyBody": "json",
"jsonBody": "={\n \"contents\": [\n {\n \"parts\": [\n {\n \"text\": {{JSON.stringify($json.prompt)}}\n }\n ]\n }\n ]\n}",
"options": {
"retry": {
"values": {
"maxTries": 3,
"retryOnStatusCodes": {
"values": [
{
"statusCode": "429,500,502,503,504"
}
]
},
"waitBetweenTries": 2000
}
},
"timeout": 60000
}
},
"name": "HTTP: Gemini API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2000,
300
]
},
{
"parameters": {
"functionCode": "// Parse AI response and prepare email\nconst response = $input.first().json;\nconst previousData = $node['Function: Build AI Prompt'].json;\n\nlet aiAdvice = 'Ch\u00fac b\u1ea1n m\u1ed9t tu\u1ea7n m\u1edbi hi\u1ec7u qu\u1ea3!';\n\ntry {\n const text = response.candidates[0].content.parts[0].text;\n aiAdvice = text.trim();\n} catch (error) {\n console.log('Error parsing AI response:', error);\n}\n\nconst stats = previousData.stats;\n\n// Build task lists as HTML\nconst formatTaskList = (tasks, max = 5) => {\n if (!tasks || tasks.length === 0) return '<li style=\"color: #888;\">Kh\u00f4ng c\u00f3</li>';\n return tasks.slice(0, max).map(t => \n `<li><strong>${t.name}</strong> - ${t.status === 'done' ? '\u2705' : t.progress + '%'}</li>`\n ).join('') + (tasks.length > max ? `<li style=\"color: #888;\">...v\u00e0 ${tasks.length - max} tasks kh\u00e1c</li>` : '');\n};\n\nreturn {\n json: {\n owner_email: previousData.owner_email,\n owner_name: previousData.owner_name,\n completion_rate: previousData.completion_rate,\n ai_advice: aiAdvice,\n total_tasks: stats.total_tasks,\n done_tasks: stats.done_tasks,\n in_progress_tasks: stats.in_progress_tasks,\n todo_tasks: stats.todo_tasks,\n overdue_count: stats.overdue_tasks.length,\n upcoming_count: stats.upcoming_deadlines.length,\n completed_this_week: stats.completed_this_week.length,\n created_this_week: stats.created_this_week.length,\n high_risk_count: stats.high_risk_tasks.length,\n completed_html: formatTaskList(stats.completed_this_week),\n upcoming_html: formatTaskList(stats.upcoming_deadlines),\n overdue_html: formatTaskList(stats.overdue_tasks)\n }\n};"
},
"name": "Function: Parse AI Response",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
2220,
300
]
},
{
"parameters": {
"fromEmail": "={{$env.EMAIL_FROM}}",
"toEmail": "={{$json.owner_email}}",
"subject": "=\ud83d\udcca B\u00e1o c\u00e1o tu\u1ea7n c\u1ee7a b\u1ea1n - Ho\u00e0n th\u00e0nh {{ $json.completion_rate }}%",
"emailType": "html",
"text": "=Xin ch\u00e0o {{$json.owner_name}}!\n\n\u0110\u00e2y l\u00e0 b\u00e1o c\u00e1o tu\u1ea7n C\u00c1 NH\u00c2N c\u1ee7a b\u1ea1n:\n\n\ud83d\udcc8 TH\u1ed0NG K\u00ca T\u1ed4NG QUAN:\n- T\u1ef7 l\u1ec7 ho\u00e0n th\u00e0nh: {{$json.completion_rate}}%\n- T\u1ed5ng tasks: {{$json.total_tasks}}\n- Ho\u00e0n th\u00e0nh: {{$json.done_tasks}}\n- \u0110ang l\u00e0m: {{$json.in_progress_tasks}}\n- Ch\u01b0a b\u1eaft \u0111\u1ea7u: {{$json.todo_tasks}}\n\n\ud83d\udcc5 TU\u1ea6N N\u00c0Y:\n- Ho\u00e0n th\u00e0nh: {{$json.completed_this_week}} tasks\n- T\u1ea1o m\u1edbi: {{$json.created_this_week}} tasks\n\n\u26a0\ufe0f C\u1ea6N CH\u00da \u00dd:\n- Qu\u00e1 h\u1ea1n: {{$json.overdue_count}} tasks\n- Deadline tu\u1ea7n t\u1edbi: {{$json.upcoming_count}} tasks\n- R\u1ee7i ro cao: {{$json.high_risk_count}} tasks\n\n\ud83e\udd16 G\u1ee2I \u00dd T\u1eea AI:\n{{$json.ai_advice}}\n\nCh\u00fac b\u1ea1n m\u1ed9t tu\u1ea7n m\u1edbi hi\u1ec7u qu\u1ea3! \ud83d\udcaa\n\n---\nEmail t\u1ef1 \u0111\u1ed9ng t\u1eeb AI Deadline - C\u00f4ng c\u1ee5 qu\u1ea3n l\u00fd deadline C\u00c1 NH\u00c2N c\u1ee7a b\u1ea1n",
"html": "=<html>\n<head>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }\n .container { max-width: 650px; margin: 0 auto; padding: 20px; }\n .header { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }\n .content { background: #f9f9f9; padding: 25px; }\n .stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin: 20px 0; }\n .stat-card { background: white; padding: 20px; border-radius: 10px; text-align: center; }\n .stat-value { font-size: 32px; font-weight: bold; }\n .stat-label { font-size: 12px; color: #666; margin-top: 5px; }\n .progress-bar { background: #e5e7eb; border-radius: 10px; height: 20px; overflow: hidden; margin: 10px 0; }\n .progress-fill { background: linear-gradient(90deg, #10b981 0%, #059669 100%); height: 100%; transition: width 0.3s; }\n .section { background: white; border-radius: 10px; padding: 20px; margin: 15px 0; }\n .section-title { font-weight: bold; margin-bottom: 15px; display: flex; align-items: center; gap: 8px; }\n .ai-box { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; padding: 20px; margin: 20px 0; }\n .footer { text-align: center; padding: 20px; color: #666; font-size: 12px; background: #f9f9f9; border-radius: 0 0 10px 10px; }\n ul { margin: 0; padding-left: 20px; }\n li { margin: 5px 0; }\n </style>\n</head>\n<body>\n <div class='container'>\n <div class='header'>\n <h1>\ud83d\udcca B\u00e1o c\u00e1o Tu\u1ea7n c\u1ee7a B\u1ea1n</h1>\n <p style='margin: 0; opacity: 0.9;'>Xin ch\u00e0o {{$json.owner_name}}! \u0110\u00e2y l\u00e0 t\u1ed5ng k\u1ebft tu\u1ea7n c\u1ee7a b\u1ea1n</p>\n </div>\n <div class='content'>\n \n <!-- Completion Rate -->\n <div class='section'>\n <div class='section-title'>\ud83c\udfaf T\u1ef7 l\u1ec7 ho\u00e0n th\u00e0nh</div>\n <div class='progress-bar'>\n <div class='progress-fill' style='width: {{$json.completion_rate}}%;'></div>\n </div>\n <div style='text-align: center; font-size: 24px; font-weight: bold; color: #10b981;'>\n {{$json.completion_rate}}%\n </div>\n </div>\n \n <!-- Stats Grid -->\n <div class='stats-grid'>\n <div class='stat-card'>\n <div class='stat-value' style='color: #10b981;'>{{$json.done_tasks}}</div>\n <div class='stat-label'>\u2705 Ho\u00e0n th\u00e0nh</div>\n </div>\n <div class='stat-card'>\n <div class='stat-value' style='color: #3b82f6;'>{{$json.in_progress_tasks}}</div>\n <div class='stat-label'>\ud83d\udd35 \u0110ang l\u00e0m</div>\n </div>\n <div class='stat-card'>\n <div class='stat-value' style='color: #9ca3af;'>{{$json.todo_tasks}}</div>\n <div class='stat-label'>\u26aa Ch\u01b0a b\u1eaft \u0111\u1ea7u</div>\n </div>\n <div class='stat-card'>\n <div class='stat-value' style='color: #ef4444;'>{{$json.overdue_count}}</div>\n <div class='stat-label'>\ud83d\udd34 Qu\u00e1 h\u1ea1n</div>\n </div>\n </div>\n \n <!-- This Week Summary -->\n <div class='section'>\n <div class='section-title'>\ud83d\udcc5 Tu\u1ea7n n\u00e0y</div>\n <p>\u2705 Ho\u00e0n th\u00e0nh <strong>{{$json.completed_this_week}}</strong> tasks</p>\n <p>\u2795 T\u1ea1o m\u1edbi <strong>{{$json.created_this_week}}</strong> tasks</p>\n </div>\n \n <!-- Upcoming Deadlines -->\n <div class='section'>\n <div class='section-title'>\ud83d\uddd3\ufe0f Deadline tu\u1ea7n t\u1edbi ({{$json.upcoming_count}})</div>\n <ul>{{$json.upcoming_html}}</ul>\n </div>\n \n {{#if $json.overdue_count}}\n <div class='section' style='border-left: 4px solid #ef4444;'>\n <div class='section-title'>\u26a0\ufe0f Tasks qu\u00e1 h\u1ea1n ({{$json.overdue_count}})</div>\n <ul>{{$json.overdue_html}}</ul>\n </div>\n {{/if}}\n \n <!-- AI Advice -->\n <div class='ai-box'>\n <div style='font-weight: bold; margin-bottom: 10px;'>\ud83e\udd16 G\u1ee3i \u00fd t\u1eeb AI</div>\n <p style='white-space: pre-wrap; margin: 0;'>{{$json.ai_advice}}</p>\n </div>\n \n <div style='text-align: center; margin-top: 25px;'>\n <a href='http://localhost:5173/dashboard' style='display: inline-block; background: #10b981; color: white; padding: 14px 35px; text-decoration: none; border-radius: 25px; font-weight: bold;'>\n Xem Dashboard c\u1ee7a b\u1ea1n \u2192\n </a>\n </div>\n \n <p style='text-align: center; margin-top: 20px; color: #666;'>\n \ud83d\udcaa Ch\u00fac b\u1ea1n m\u1ed9t tu\u1ea7n m\u1edbi hi\u1ec7u qu\u1ea3!\n </p>\n </div>\n <div class='footer'>\n <p>Email t\u1ef1 \u0111\u1ed9ng t\u1eeb <strong>AI Deadline Forecasting Agent</strong></p>\n <p><em>C\u00f4ng c\u1ee5 qu\u1ea3n l\u00fd deadline C\u00c1 NH\u00c2N c\u1ee7a b\u1ea1n</em></p>\n </div>\n </div>\n</body>\n</html>",
"options": {}
},
"name": "Send Email: Weekly Review",
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2,
"position": [
2440,
300
],
"credentials": {
"smtp": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "={{$env.BACKEND_API_URL}}/api/webhooks/n8n/automation-log",
"authentication": "none",
"method": "POST",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "workflow_name",
"value": "Personal Weekly Review"
},
{
"name": "status",
"value": "success"
},
{
"name": "input_data",
"value": "={{JSON.stringify({owner_email: $json.owner_email})}}"
},
{
"name": "output_data",
"value": "={{JSON.stringify({completion_rate: $json.completion_rate, done: $json.done_tasks, total: $json.total_tasks})}}"
}
]
},
"options": {}
},
"name": "HTTP: Log to Backend",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
2660,
300
]
}
],
"connections": {
"Cron Trigger (Sunday 7PM)": {
"main": [
[
{
"node": "HTTP: Get All Tasks",
"type": "main",
"index": 0
}
]
]
},
"HTTP: Get All Tasks": {
"main": [
[
{
"node": "HTTP: Get Forecasts",
"type": "main",
"index": 0
}
]
]
},
"HTTP: Get Forecasts": {
"main": [
[
{
"node": "Function: Calculate Weekly Stats",
"type": "main",
"index": 0
}
]
]
},
"Function: Calculate Weekly Stats": {
"main": [
[
{
"node": "Split Into Items",
"type": "main",
"index": 0
}
]
]
},
"Split Into Items": {
"main": [
[
{
"node": "HTTP: Get Owner Email",
"type": "main",
"index": 0
}
]
]
},
"HTTP: Get Owner Email": {
"main": [
[
{
"node": "IF: Has Email",
"type": "main",
"index": 0
}
]
]
},
"IF: Has Email": {
"main": [
[
{
"node": "Function: Build AI Prompt",
"type": "main",
"index": 0
}
]
]
},
"Function: Build AI Prompt": {
"main": [
[
{
"node": "HTTP: Gemini API",
"type": "main",
"index": 0
}
]
]
},
"HTTP: Gemini API": {
"main": [
[
{
"node": "Function: Parse AI Response",
"type": "main",
"index": 0
}
]
]
},
"Function: Parse AI Response": {
"main": [
[
{
"node": "Send Email: Weekly Review",
"type": "main",
"index": 0
}
]
]
},
"Send Email: Weekly Review": {
"main": [
[
{
"node": "HTTP: Log to Backend",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"versionId": "9",
"id": "9",
"tags": []
}
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.
smtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Flow 9 - Personal Weekly Review. Uses httpRequest, emailSend. Scheduled trigger; 12 nodes.
Source: https://github.com/kdktj/ai_deadline_ck/blob/cfb3541a9507dfb54fa0320122eeefade5543a76/n8n-workflows/flow7-personal-weekly-review.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.
This workflow is an improvement of this workflow by Greg Brzezinka.
N8N-Self-Updater. Uses ssh, emailSend, httpRequest. Scheduled trigger; 27 nodes.
> An automated n8n workflow originally built for DigitalOcean-based n8n deployments, but fully compatible with any VPS or cloud hosting (e.g., AWS, Google Cloud, Hetzner, Linode, etc.) where n8n ru
What if you could spot a major sales problem—or a winning campaign—the very next morning, instead of weeks later? Imagine receiving a beautiful, data-rich alert directly in your inbox the moment your
Track Changes Of Product Prices. Uses htmlExtract, functionItem, httpRequest, writeBinaryFile. Scheduled trigger; 25 nodes.