使用 Azure OpenAI (GPT-4o-mini) 和 Gmail 生成 Jira 日终摘要与周报
高级
这是一个自动化工作流,包含 26 个节点。主要使用 Code, Jira, Gmail, GoogleSheets, Agent 等节点。 使用 Azure OpenAI 和 Gmail 生成每日 Jira 摘要和每周报告
前置要求
- •Google 账号和 Gmail API 凭证
- •Google Sheets API 凭证
- •OpenAI API Key
使用的节点 (26)
分类
-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "T20oXbS75AjzqlND",
"meta": {
"instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
"templateCredsSetupCompleted": true
},
"name": "使用 Azure OpenAI (GPT-4o-mini) 和 Gmail 生成 Jira 日终摘要与周报",
"tags": [],
"nodes": [
{
"id": "cf39d6c2-8075-48bd-90e2-f5aabe04235e",
"name": "Azure OpenAI 聊天模型",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
672,
640
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"id": "C3WzT18XqF8OdVM6",
"name": "Azure Open AI account"
}
},
"typeVersion": 1
},
{
"id": "bb0d663a-30fd-40dc-affd-b7bedb5d08bf",
"name": "Azure OpenAI 聊天模型1",
"type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
"position": [
608,
1600
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"credentials": {
"azureOpenAiApi": {
"id": "C3WzT18XqF8OdVM6",
"name": "Azure Open AI account"
}
},
"typeVersion": 1
},
{
"id": "654069da-1c35-4475-8a67-1771a9301892",
"name": "结构化输出解析器",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
736,
1600
],
"parameters": {
"jsonSchemaExample": "{\n \"output\": \"1) **Weekly Overview** \\nThis week (Oct 13–17, 2025) saw a **total of 5 items completed**, though a significant **backlog of 32 items remains**, indicating an area for improvement in throughput.\\n\\n2) **Weekly Key Metrics** \\n- **Total items processed (Mon–Fri):** 50 \\n- **Completed (statusCategory: Done or resolution present):** 5 \\n- **In Progress (status.name: In Progress):** 5 \\n- **Backlog/To Do (statusCategory.key: new or status.name: Backlog/To Do):** 32 \\n- **Bugs vs Tasks (issuetype.name counts):** Bugs: 15, Tasks: 15 \\n- **Top assignees (by count, up to 3):** Jyothi Swarup: 50 \\n- **Priority distribution (by priority.name):** Medium: 50 \\n\\n3) **Day-by-Day Highlights** \\n- **Monday:** \\n - Completed 1 task (SCRUM-1: Fix login API timeout) \\n - 5 items in progress (SCRUM-5, SCRUM-2, SCRUM-4, SCRUM-6) \\n- **Tuesday:** \\n - No changes in item status, remained in backlog \\n- **Wednesday:** \\n - No changes, all items unchanged \\n- **Thursday:** \\n - SCRUM-3 and SCRUM-4 remain in the backlog, no new progress \\n- **Friday:** \\n - Status unchanged for remaining items \\n - Continued backlog accumulation \\n\\n4) **Progress & Trends** \\n- Backlog continues to grow with no items moved to completion since Monday. \\n- Completion rate stabilizing at around **1 item/day only.** \\n- Backlog items remain unchanged for most of the week. \\n- All assignee workload appears **highly concentrated**, indicating possible risk. \\n\\n5) **Blockers & Risks (Week)** \\n- **Significant backlog:** Many items (SCRUM-2, SCRUM-3, SCRUM-4, SCRUM-6) remain stagnant. \\n- No visible resolution timelines for in-progress items (particularly SCRUM-5) may lead to increasing risk of missed deadlines. \\n\\n6) **Recommendations (Next Week)** \\n- Prioritize backlog resolution by assigning estimates and deadlines for stalled items. \\n- Focus and urgency required for SCRUM-5 to ensure progress with a clear resolution timeline. \\n- Consider redistributing tasks or adding resources to handle workload effectively. \\n- Schedule daily stand-ups to track progress on backlog items and identify blockers. \\n- Reassess team capacity to meet outstanding sprint deadlines and customer expectations. \\n\\n7) **Completed Work (Week)** \\n- SCRUM-1: Fix login API timeout; resolved on 2025-10-13 \\n\\n8) **Data Snapshot (Appendix)** \\n- { \\\"issuekey\\\": \\\"SCRUM-1\\\", \\\"summary\\\": \\\"Fix login API timeout\\\", \\\"status.name\\\": \\\"Done\\\", \\\"statusCategory.name\\\": \\\"Done\\\", \\\"resolution.name\\\": \\\"Done\\\", \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:19.220+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:36.945+0530\\\" } \\n- { \\\"issuekey\\\": \\\"SCRUM-2\\\", \\\"summary\\\": \\\"Resolve crash on profile photo upload\\\", \\\"status.name\\\": \\\"Backlog\\\", \\\"statusCategory.name\\\": \\\"To Do\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:20.563+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:41.031+0530\\\" } \\n- { \\\"issuekey\\\": \\\"SCRUM-5\\\", \\\"summary\\\": \\\"Address janky scroll in feed list\\\", \\\"status.name\\\": \\\"In Progress\\\", \\\"statusCategory.name\\\": \\\"In Progress\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:29.354+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:49.842+0530\\\" } \"\n }"
},
"typeVersion": 1.3
},
{
"id": "88de2233-3d12-4183-a700-4c40f9c91935",
"name": "结构化输出解析器1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
800,
640
],
"parameters": {
"jsonSchemaExample": "{\n \"output\": \"1) EOD Summary \\n**Today, one item was successfully completed**, reflecting an overall positive outcome and productive effort by the team. Progress was made on several tasks, while a few remained in the backlog.\\n\\n2) Key Metrics (Today) \\n- Total items processed: 10 \\n- Completed (statusCategory: Done or resolution present): 1 \\n- In Progress (status.name: In Progress): 1 \\n- Backlog/To Do (statusCategory: To Do or status.name: Backlog/To Do): 8 \\n- Bugs vs Tasks (issuetype.name counts): Bugs: 5, Tasks: 5 \\n- Top assignees (by count, up to 3): Jyothi Swarup: 10 \\n- Priority distribution (by priority.name): Medium: 10 \\n\\n3) Notable Updates Today \\n- SCRUM-1: Done; summary: “Fix login API timeout”; resolutiondate: 2025-10-13T16:10:59.421+0530 IST \\n- SCRUM-5: In Progress; summary: “Address janky scroll in feed list”; updated: 2025-10-13T16:14:49.842+0530 IST \\n- SCRUM-2: Backlog; summary: “Resolve crash on profile photo upload”; updated: 2025-10-13T16:14:41.031+0530 IST \\n- SCRUM-4: Backlog; summary: “Fix dark mode color contrast in buttons”; updated: 2025-10-13T16:14:45.183+0530 IST \\n- SCRUM-6: Backlog; summary: “Resolve push notification tap not navigating”; updated: 2025-10-13T16:14:51.576+0530 IST \\n\\n4) Blockers & Risks \\n- SCRUM-2, SCRUM-3, SCRUM-4, and SCRUM-6 are all currently blocked as they remain in the Backlog. \\n- **SCRUM-5** is the only task designated as In Progress, though no resolution timeline is indicated. All other items need to be moved forward to avoid further backlog accumulation.\\n\\n5) Actions for Tomorrow \\n- Assign estimates to Scrum tasks currently in the backlog. \\n- Increase focus on SCRUM-5 to progress towards completion. \\n- Identify and address blockers causing delay in Backlog items. \\n- Re-prioritize items based on urgency and team capacity. \\n- Conduct a quick review of upcoming sprint timelines to align tasks accordingly.\\n\\n6) Data Snapshot (Appendix) \\n- { \\\"issuekey\\\": \\\"SCRUM-1\\\", \\\"summary\\\": \\\"Fix login API timeout\\\", \\\"status.name\\\": \\\"Done\\\", \\\"statusCategory.name\\\": \\\"Done\\\", \\\"resolution.name\\\": \\\"Done\\\", \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:19.220+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:36.945+0530\\\" } \\n- { \\\"issuekey\\\": \\\"SCRUM-2\\\", \\\"summary\\\": \\\"Resolve crash on profile photo upload\\\", \\\"status.name\\\": \\\"Backlog\\\", \\\"statusCategory.name\\\": \\\"To Do\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:20.563+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:41.031+0530\\\" } \\n- { \\\"issuekey\\\": \\\"SCRUM-5\\\", \\\"summary\\\": \\\"Address janky scroll in feed list\\\", \\\"status.name\\\": \\\"In Progress\\\", \\\"statusCategory.name\\\": \\\"In Progress\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:29.354+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:49.842+0530\\\" }\"\n }"
},
"typeVersion": 1.3
},
{
"id": "4014c2f1-5ff9-4617-b780-f345669b3a1a",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-336,
496
],
"parameters": {
"content": "## 每日晚间触发器"
},
"typeVersion": 1
},
{
"id": "3d75eeb6-2cac-44a0-8b7a-2462f6fdfe61",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
112,
304
],
"parameters": {
"height": 176,
"content": "## 获取所有问题 (Jira)"
},
"typeVersion": 1
},
{
"id": "2644c493-fb58-474a-bc2e-89fcf93f8299",
"name": "获取所有问题",
"type": "n8n-nodes-base.jira",
"position": [
208,
528
],
"parameters": {
"options": {},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"jiraSoftwareCloudApi": {
"id": "AnaUUmFzt0vUQADt",
"name": "jyothi"
}
},
"typeVersion": 1
},
{
"id": "f4cee73e-d692-4835-b5a7-c0c94808cb13",
"name": "扁平化输入",
"type": "n8n-nodes-base.code",
"position": [
432,
528
],
"parameters": {
"jsCode": "// n8n Code node: merge all incoming items into a single output array in one item\n\nconst inputItems = $input.all();\nconst combined = [];\n\n// Normalize any value to an array of plain objects\nfunction normalizeToArray(val) {\n if (typeof val === 'string') {\n const trimmed = val.trim();\n try {\n const parsed = JSON.parse(trimmed);\n return Array.isArray(parsed) ? parsed : [parsed];\n } catch {\n return [{ raw: trimmed }];\n }\n }\n if (Array.isArray(val)) return val;\n if (val && typeof val === 'object') return [val];\n return [{ value: val }];\n}\n\n// Merge all items' json into one array\nfor (const item of inputItems) {\n const source = item.json !== undefined ? item.json : item;\n const arr = normalizeToArray(source);\n for (const el of arr) {\n const obj = el && typeof el === 'object' ? el : { value: el };\n combined.push(obj);\n }\n}\n\n// Return a single n8n item containing the whole array\nreturn [\n {\n json: {\n combined, // All input collapsed into one array\n count: combined.length\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "7692b76d-68c2-46f0-8d6c-eff609de74b7",
"name": "创建摘要",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
656,
416
],
"parameters": {
"text": "={{ $json.combined }}",
"options": {
"systemMessage": "You are an End‑of‑Day (EOD) Report generator for engineering teams. Your job is to read a pasted text blob representing the day’s work (e.g., combined Jira issue array, logs, API responses) and produce a consistent EOD summary every single time using the exact template and rules below. Do not ask questions. Infer context only from the input.\n\nINPUT FORMAT\n- The input may be a single object containing { combined: [...], count: N } or a raw array of issue/work items.\n- Treat JSON keys/values and log lines as source of truth.\n- Today’s local time context: Asia/Kolkata (IST), unless clearly indicated otherwise.\n\nOUTPUT TEMPLATE\n1) EOD Summary\n- One concise paragraph describing today’s overall outcome and the most important takeaway (e.g., completion, progress, blockers).\n\n2) Key Metrics (Today)\n- Total items processed:\n- Completed (statusCategory: Done or resolution present):\n- In Progress (status.name: In Progress):\n- Backlog/To Do (statusCategory: To Do or status.name: Backlog/To Do):\n- Bugs vs Tasks (issuetype.name counts):\n- Top assignees (by count, up to 3):\n- Priority distribution (by priority.name):\n\n3) Notable Updates Today\n- 3–6 bullets referencing specific keys and summaries (e.g., SCRUM-1: Done; summary: “Fix login API timeout”; resolutiondate/timestamp if present, in IST label).\n\n4) Blockers & Risks\n- Bullets inferred from statuses, null fields (e.g., timeoriginalestimate), overdue sprint windows, inconsistent “active” vs past endDate, or items showing no progress (created vs updated).\n\n5) Actions for Tomorrow\n- 4–7 prioritized, action‑oriented steps tied to blockers and carryover (assign, add estimate, re‑prioritize, resolve conflicts, test/monitor, etc.).\n\n6) Data Snapshot (Appendix)\n- Quote minimal exact fields for 3–5 representative records (issue key, summary, status.name, statusCategory.name, resolution.name, assignee.displayName, priority.name, created, updated). Use original field names exactly.\n\nCLASSIFICATION RULES\n- Completed = statusCategory.key: done OR resolution present.\n- In Progress = status.name: In Progress OR statusCategory.key: indeterminate.\n- Backlog/To Do = status.name: Backlog/To Do OR statusCategory.key: new.\n- Bugs vs Tasks from issuetype.name.\n- Use input’s combined array if present; otherwise use the root array.\n- When timestamps are reported in readable form, label clearly as IST; otherwise preserve originals.\n\nFORMATTING RULES\n- Be concise; no fluff.\n- Headings and section order must never change.\n- Preserve field names exactly (e.g., statuscategorychangedate).\n- Use bold sparingly for 2–4 critical items/terms.\n- If a section has no data, write “None”.\n- Do not add links unless present in the input.\n\nERROR HANDLING\n- If the input appears incomplete or malformed, write “Input appears incomplete or malformed” in EOD Summary, then proceed with whatever can be extracted.\n\nCONSISTENCY CHECK\n- Ensure all six sections are present, labels match exactly, counts are internally consistent, and referenced keys/summaries match the input."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "514fc953-ea89-46e4-b1c0-c05367d978df",
"name": "追加摘要",
"type": "n8n-nodes-base.googleSheets",
"position": [
1008,
528
],
"parameters": {
"columns": {
"value": {
"Date": "={{ $now.format('dd-MMM-yyyy') }}",
"JSON": "={{ $('Flatten Input').item.json.combined }}",
"Summary": "={{ $json.output }}"
},
"schema": [
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "JSON",
"type": "string",
"display": true,
"required": false,
"displayName": "JSON",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Summary",
"type": "string",
"display": true,
"required": false,
"displayName": "Summary",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit?usp=drivesdk",
"cachedResultName": "JIRA Daily Reporting"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "ajCmdXdhjJqZW6RE",
"name": "automations"
}
},
"typeVersion": 4.7
},
{
"id": "30e6b160-c87c-40ca-b2cd-75f5ae93d9ff",
"name": "每日晚间触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-16,
528
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "1e5d7fbe-718a-472f-8d44-efc621042073",
"name": "周五晚间触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-80,
1376
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
5
],
"triggerAtHour": 20
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f4c8a646-d963-4b84-b442-91ecad3b31d8",
"name": "获取所有存储数据",
"type": "n8n-nodes-base.googleSheets",
"position": [
144,
1376
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit?usp=drivesdk",
"cachedResultName": "JIRA Daily Reporting"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "ajCmdXdhjJqZW6RE",
"name": "automations"
}
},
"typeVersion": 4.7
},
{
"id": "d26c622b-92f7-4826-adeb-ddf57df9daf2",
"name": "扁平化输入",
"type": "n8n-nodes-base.code",
"position": [
368,
1376
],
"parameters": {
"jsCode": "// n8n Code node: merge all incoming items into a single output array in one item\n\nconst inputItems = $input.all();\nconst combined = [];\n\n// Normalize any value to an array of plain objects\nfunction normalizeToArray(val) {\n if (typeof val === 'string') {\n const trimmed = val.trim();\n try {\n const parsed = JSON.parse(trimmed);\n return Array.isArray(parsed) ? parsed : [parsed];\n } catch {\n return [{ raw: trimmed }];\n }\n }\n if (Array.isArray(val)) return val;\n if (val && typeof val === 'object') return [val];\n return [{ value: val }];\n}\n\n// Merge all items' json into one array\nfor (const item of inputItems) {\n const source = item.json !== undefined ? item.json : item;\n const arr = normalizeToArray(source);\n for (const el of arr) {\n const obj = el && typeof el === 'object' ? el : { value: el };\n combined.push(obj);\n }\n}\n\n// Return a single n8n item containing the whole array\nreturn [\n {\n json: {\n combined, // All input collapsed into one array\n count: combined.length\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "a626ce54-881d-4dbc-88ae-19adad2f6389",
"name": "创建周度摘要",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
592,
1376
],
"parameters": {
"text": "={{ $json.combined }}",
"options": {
"systemMessage": "You are a Weekly Summary generator for engineering teams. Your job is to read five day-level inputs (Monday through Friday)—each containing data (arrays/objects), JSON details, and an End‑of‑Day (EOD) summary—and produce a consistent Weekly Summary every single time using the exact template and rules below. Do not ask questions. Infer context only from the input.\n\nINPUT FORMAT\n- The input will include five day entries: Monday, Tuesday, Wednesday, Thursday, Friday.\n- Each day may have:\n - Data: arrays/objects of issues/work items.\n - JSON: raw or stringified JSON representing items.\n - Summary: an EOD summary string following a known template.\n- Treat JSON keys/values and EOD text as source of truth.\n- If multiple structures are present (e.g., combined arrays, raw arrays, stringified JSON), parse/normalize and merge.\n\nOUTPUT TEMPLATE\n1) Weekly Overview\n- One concise paragraph stating the week window (Mon–Fri), overall outcome, and the single most important takeaway (e.g., completion count, major blocker cleared, quality trend).\n\n2) Weekly Key Metrics\n- Total items processed (Mon–Fri):\n- Completed (statusCategory: Done or resolution present):\n- In Progress (status.name: In Progress):\n- Backlog/To Do (statusCategory.key: new or status.name: Backlog/To Do):\n- Bugs vs Tasks (issuetype.name counts):\n- Top assignees (by count, up to 3):\n- Priority distribution (by priority.name):\n\n3) Day-by-Day Highlights\n- Monday: 2–4 bullets of notable events (keys, summaries, key timestamps), or “None”.\n- Tuesday: 2–4 bullets, or “None”.\n- Wednesday: 2–4 bullets, or “None”.\n- Thursday: 2–4 bullets, or “None”.\n- Friday: 2–4 bullets, or “None”.\n\n4) Progress & Trends\n- 3–6 bullets comparing days (e.g., rising backlog, stabilization in login, throughput changes, increase/decrease in Done, assignee load shifts).\n\n5) Blockers & Risks (Week)\n- Bullets inferred from the aggregate: persistent backlog items, missing estimates, conflicts (e.g., active sprint with past endDate), stalled items (unchanged status across days), or repeated failures.\n\n6) Recommendations (Next Week)\n- 5–8 prioritized, action‑oriented steps tied to blockers, throughput, and quality trends. Keep each specific and measurable.\n\n7) Completed Work (Week)\n- 3–8 bullets referencing key completions with issue keys, summaries, resolution/resolutiondate/timestamps (label IST if converting).\n\n8) Data Snapshot (Appendix)\n- Quote minimal exact fields for 5–10 representative records across the week:\n - issue key, summary, status.name, statusCategory.name/key, resolution.name, assignee.displayName, priority.name, created, updated.\n - Preserve original field names exactly as found.\n\nCLASSIFICATION & AGGREGATION RULES\n- Completed = statusCategory.key: done OR resolution present.\n- In Progress = status.name: In Progress OR statusCategory.key: indeterminate.\n- Backlog/To Do = status.name: Backlog/To Do OR statusCategory.key: new.\n- Bugs vs Tasks from issuetype.name.\n- Normalize sources in this order per day: combined array → root array/object → stringified JSON → EOD Summary counts (as fallback only).\n- When computing weekly metrics, sum unique items across all days (deduplicate by key if present).\n- Age/staleness: use created vs updated; note items unchanged across multiple days.\n- Timezone: Prefer Asia/Kolkata (IST) when converting; if preserving originals, label as-is.\n\nFORMATTING RULES\n- Be concise; no fluff.\n- Headings and section order must never change.\n- Preserve field names exactly (e.g., statuscategorychangedate).\n- Use bold sparingly for 3–6 critical items/terms.\n- If a section has no data, write “None”.\n- Do not add links unless present in the input.\n\nERROR HANDLING\n- If one or more days are missing or malformed, state “Some daily inputs appear incomplete or malformed” in Weekly Overview, then proceed with whatever can be extracted.\n- If a metric cannot be computed due to missing fields, set it to “Unknown” and briefly explain under Blockers & Risks.\n\nCONSISTENCY CHECK\n- Ensure all eight sections are present, labels match exactly, counts are internally consistent, and referenced keys/summaries match the input."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "82ae1eaa-b298-4a81-a839-248558ce2fd9",
"name": "创建邮件",
"type": "n8n-nodes-base.code",
"position": [
944,
1376
],
"parameters": {
"jsCode": "// filename: n8n-generate-weekly-summary-email.js\n\n/**\n * n8n Code node (JavaScript) – Fixes “Code doesn't return items properly”\n * and returns a valid array of items with the generated HTML email.\n *\n * Input expectation:\n * - Each incoming item has a field `output` (string) containing the weekly summary text\n * with sections 1) .. 8) as provided by your AI Agent node.\n *\n * Output:\n * - Returns an array with ONE item:\n * { json: { html: \"<!-- full HTML email -->\", subject: \"Weekly Summary\", plainTextPreview: \"...\" } }\n *\n * If multiple input items exist, it uses the first one with `output` present.\n *\n * Place this code in your “Code” node and ensure the previous node passes an item\n * with `output` in `item.json.output`.\n */\n\n// --------------- Helper functions ---------------\n\n// Basic HTML escaping\nfunction escapeHtml(str) {\n return String(str)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n// Convert \"1) **Weekly Overview**\" -> \"Weekly Overview\"\nfunction cleanHeading(headingText) {\n let h = headingText.replace(/^\\d\\)\\s*/, '');\n h = h.replace(/\\*\\*/g, '').trim();\n return h;\n}\n\n// Inline formatting: bold **text**, issue keys as pills\nfunction inlineFormat(text) {\n let t = escapeHtml(text);\n t = t.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n t = t.replace(/\\b([A-Z]+-\\d+)\\b/g, '<span class=\"pill\">$1</span>');\n return t;\n}\n\n// Render section content: bullets, paragraphs, snapshot code blocks\nfunction renderSectionContent(content) {\n if (!content) return '<p class=\"muted\">None</p>';\n\n const lines = content.split('\\n');\n const blocks = [];\n let currentList = null;\n\n const flushList = () => {\n if (currentList && currentList.items.length) {\n const li = currentList.items.map(i => `<li>${inlineFormat(i)}</li>`).join('');\n blocks.push(`<ul class=\"metric-list\">${li}</ul>`);\n }\n currentList = null;\n };\n\n for (let rawLine of lines) {\n const line = rawLine.trim();\n\n if (line.length === 0) {\n flushList();\n continue;\n }\n if (/^-\\s+/.test(line)) {\n if (!currentList) currentList = { items: [] };\n currentList.items.push(line.replace(/^-\\s+/, '').trim());\n continue;\n }\n flushList();\n\n // JSON-like snapshot lines -> code block\n if (/^\\{.+\\}$/.test(line) || line.startsWith('{ \"') || line.startsWith('- {')) {\n const cleaned = line.replace(/^-+\\s*/, '');\n blocks.push(`<div class=\"code\">${escapeHtml(cleaned)}</div>`);\n } else {\n blocks.push(`<p>${inlineFormat(line)}</p>`);\n }\n }\n\n flushList();\n return blocks.join('\\n');\n}\n\n// Build email HTML from the `output` string\nfunction buildEmailHtml(outputText) {\n // Find section headings like \"1) **Weekly Overview**\"\n const headingRegex = /\\n?\\s*(\\d\\)\\s\\*\\*?[^\\n]+?\\*\\*?\\s*)/g;\n const parts = [];\n let match;\n\n while ((match = headingRegex.exec(outputText)) !== null) {\n const headingText = match[1];\n const startIndexForContent = headingRegex.lastIndex;\n const cleanedHeading = cleanHeading(headingText);\n\n // Set previous section content boundary\n if (parts.length > 0) {\n parts[parts.length - 1].content = outputText.slice(parts[parts.length - 1].startIndex, match.index).trim();\n }\n\n parts.push({\n heading: cleanedHeading,\n startIndex: startIndexForContent,\n content: '',\n });\n }\n\n if (parts.length > 0) {\n const last = parts[parts.length - 1];\n last.content = outputText.slice(last.startIndex).trim();\n } else {\n // Fallback: whole text as one section\n parts.push({\n heading: 'Weekly Summary',\n content: outputText.trim(),\n });\n }\n\n const sectionHtml = parts.map(({ heading, content }) => {\n return `\n <section class=\"card\">\n <h2 class=\"section-title\">${escapeHtml(heading)}</h2>\n ${renderSectionContent(content)}\n </section>\n `;\n }).join('\\n');\n\n const generatedAtIst = escapeHtml(new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' }));\n\n const html = `\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\" />\n<title>Weekly Summary</title>\n<style>\n :root {\n --bg: #f7f8fa;\n --card-bg: #ffffff;\n --text: #111827;\n --muted: #6b7280;\n --primary: #2563eb;\n --border: #e5e7eb;\n }\n body {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Inter, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Arial, sans-serif;\n line-height: 1.6;\n }\n .container {\n max-width: 760px;\n margin: 0 auto;\n padding: 24px 16px;\n }\n .header {\n background: linear-gradient(135deg, #eef2ff, #f0fdf4);\n border: 1px solid var(--border);\n border-radius: 14px;\n padding: 20px;\n margin-bottom: 16px;\n }\n .title {\n margin: 0;\n font-size: 22px;\n font-weight: 700;\n }\n .subtitle {\n margin: 6px 0 0;\n font-size: 14px;\n color: var(--muted);\n }\n .card {\n background: var(--card-bg);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 18px;\n margin: 12px 0;\n box-shadow: 0 2px 6px rgba(31, 41, 55, 0.06);\n }\n .section-title {\n margin: 0 0 12px;\n font-size: 18px;\n font-weight: 700;\n color: var(--text);\n border-left: 3px solid var(--primary);\n padding-left: 10px;\n }\n p { margin: 10px 0; }\n ul.metric-list { padding-left: 18px; margin: 8px 0; }\n ul.metric-list li { margin: 6px 0; }\n .pill {\n display: inline-block;\n font-size: 12px;\n padding: 4px 8px;\n border-radius: 999px;\n background: #eef2ff;\n color: #1e3a8a;\n border: 1px solid #e5e7eb;\n margin-right: 6px;\n }\n .code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 12px;\n background: #f3f4f6;\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 8px;\n white-space: pre-wrap;\n word-break: break-word;\n margin: 8px 0;\n }\n .footer {\n text-align: center;\n color: var(--muted);\n font-size: 12px;\n margin-top: 18px;\n }\n @media (max-width: 480px) {\n .container { padding: 18px 12px; }\n .title { font-size: 20px; }\n .section-title { font-size: 17px; }\n }\n</style>\n</head>\n<body>\n <div class=\"container\">\n <header class=\"header\">\n <h1 class=\"title\">Weekly Summary</h1>\n <p class=\"subtitle\">Generated automatically from your weekly report data.</p>\n </header>\n\n ${sectionHtml}\n\n <footer class=\"footer\">\n Sent via automated workflow · ${generatedAtIst} IST\n </footer>\n </div>\n</body>\n</html>\n `.trim();\n\n return html;\n}\n\n// Create a short plain text preview (for email providers that show a preview line)\nfunction buildPlainPreview(outputText) {\n // Take the first line after Weekly Overview for a preview\n const firstLine = outputText.split('\\n').find(l => l.trim().length > 0) || 'Weekly Summary';\n return firstLine.replace(/\\*\\*/g, '').replace(/\\s+/g, ' ').trim().slice(0, 160);\n}\n\n// --------------- Main n8n code ---------------\n\nconst items = $input.all();\n\n// Pick the first item that has `output`\nlet outputText = null;\nfor (const it of items) {\n if (it && it.json && typeof it.json.output === 'string' && it.json.output.trim().length > 0) {\n outputText = it.json.output;\n break;\n }\n}\n\n// If not found, try the first item's whole JSON converted to string\nif (!outputText) {\n if (items.length > 0 && items[0].json) {\n outputText = JSON.stringify(items[0].json);\n } else {\n outputText = 'Weekly Summary\\n\\nNo output text found in incoming items.';\n }\n}\n\n// Build HTML and preview\nconst html = buildEmailHtml(outputText);\nconst preview = buildPlainPreview(outputText);\n\n// Return one n8n item with the email payload\nreturn [\n {\n json: {\n subject: 'Weekly Summary',\n html,\n plainTextPreview: preview,\n },\n },\n];\n"
},
"typeVersion": 2
},
{
"id": "9d53e156-2c55-412f-a342-ac7b15be99f9",
"name": "发送邮件给相关方",
"type": "n8n-nodes-base.gmail",
"position": [
1168,
1376
],
"webhookId": "32493ed3-072e-43f9-a3ef-7623fdf6e4cf",
"parameters": {
"sendTo": "jyothi.swarup@techdome.net.in",
"message": "={{ $json.html }}",
"options": {},
"subject": "={{ $json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "70f5n8rPahCANHs7",
"name": "jyothi"
}
},
"typeVersion": 2.1
},
{
"id": "551aec75-3b84-4fe9-a859-c224e194463f",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
352,
688
],
"parameters": {
"height": 176,
"content": "## 扁平化输入"
},
"typeVersion": 1
},
{
"id": "74294bfe-8fcb-4b01-b3de-015ba940405e",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
176
],
"parameters": {
"height": 192,
"content": "## 聊天模型 (日终)"
},
"typeVersion": 1
},
{
"id": "c02f0d08-1761-4f59-bdde-1c430082776f",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
944,
704
],
"parameters": {
"height": 192,
"content": "## 追加摘要 (表格)"
},
"typeVersion": 1
},
{
"id": "8539c40a-70dc-4e66-a2a4-02fe9deca977",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
1360
],
"parameters": {
"height": 176,
"content": "## 周五晚间触发器"
},
"typeVersion": 1
},
{
"id": "affe1837-b2b5-4337-80a0-59adca265027",
"name": "便签6",
"type": "n8n-nodes-base.stickyNote",
"position": [
64,
1168
],
"parameters": {
"height": 176,
"content": "## 获取所有存储数据 (表格)"
},
"typeVersion": 1
},
{
"id": "2e86ba0f-1810-478b-b68b-f5e949b442e8",
"name": "便签7",
"type": "n8n-nodes-base.stickyNote",
"position": [
304,
1536
],
"parameters": {
"height": 176,
"content": "## 扁平化输入 (周度)"
},
"typeVersion": 1
},
{
"id": "5b3bc309-60b3-467c-bc62-3a04b7b52e32",
"name": "便签8",
"type": "n8n-nodes-base.stickyNote",
"position": [
576,
1168
],
"parameters": {
"height": 176,
"content": "## 聊天模型 (周度)"
},
"typeVersion": 1
},
{
"id": "6ab5b8a2-7aa4-4795-aa2b-2514b5045776",
"name": "便签9",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
1536
],
"parameters": {
"height": 176,
"content": "## 创建邮件"
},
"typeVersion": 1
},
{
"id": "91a7270d-1ad6-4d10-9182-29068b710763",
"name": "便签10",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
1168
],
"parameters": {
"height": 176,
"content": "## 发送邮件给相关方"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "8897253f-0b1a-4ea3-bf93-9b00f633c6e3",
"connections": {
"Create Email": {
"main": [
[
{
"node": "Email to Stakeholders",
"type": "main",
"index": 0
}
]
]
},
"Flatten Input": {
"main": [
[
{
"node": "Create Summary",
"type": "main",
"index": 0
}
]
]
},
"Create Summary": {
"main": [
[
{
"node": "Append Summary",
"type": "main",
"index": 0
}
]
]
},
"Get ALL issues": {
"main": [
[
{
"node": "Flatten Input",
"type": "main",
"index": 0
}
]
]
},
"Flatten the input": {
"main": [
[
{
"node": "Create Weekly Summary",
"type": "main",
"index": 0
}
]
]
},
"Get all stored data": {
"main": [
[
{
"node": "Flatten the input",
"type": "main",
"index": 0
}
]
]
},
"Create Weekly Summary": {
"main": [
[
{
"node": "Create Email",
"type": "main",
"index": 0
}
]
]
},
"Daily Evening Trigger": {
"main": [
[
{
"node": "Get ALL issues",
"type": "main",
"index": 0
}
]
]
},
"Friday Evening Trigger": {
"main": [
[
{
"node": "Get all stored data",
"type": "main",
"index": 0
}
]
]
},
"Azure OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Create Summary",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Azure OpenAI Chat Model1": {
"ai_languageModel": [
[
{
"node": "Create Weekly Summary",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Create Weekly Summary",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Structured Output Parser1": {
"ai_outputParser": [
[
{
"node": "Create Summary",
"type": "ai_outputParser",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
从Monday.com和Jira到Outlook的AI驱动反馈分类与报告
使用Azure GPT-4、Jira任务和Outlook报告分析来自Monday.com的客户反馈
Set
Code
Jira
+12
27 节点Rahul Joshi
使用 Slack、Gmail 和 AI 自动化 Jira 待办事项整理和报告
通过 Slack、Gmail 和 GPT-4 实现 Jira 待办事项整理与报告的自动化
If
Set
Jira
+9
31 节点Rahul Joshi
从 Stripe 支付自动交付模板给客户
使用Stripe、GPT-4o和Gmail的自动化模板交付系统
If
Code
Gmail
+12
44 节点Rahul Joshi
客户关系管理
使用 Azure OpenAI 和 Google Workspace 自动化 DEI 资格筛选
使用Azure GPT-4o、Google云端硬盘和表格自动进行DEI资格筛选
If
Code
Gmail
+9
19 节点Rahul Joshi
内容创作
客户入职帮助请求(Typeform 到 Gmail 和 Sheets)
客户入职帮助请求(Typeform 到 Gmail 和 Sheets)
If
Code
Gmail
+10
28 节点Rahul Joshi
内容创作
使用GPT-4自动化从ClickUp到Slack和Gmail的每日晨报生成
使用GPT-4o从ClickUp生成AI驱动的晨报并发送至Slack和Gmail
If
Code
Gmail
+9
27 节点Rahul Joshi
工作流信息
难度等级
高级
节点数量26
分类-
节点类型9
作者
Rahul Joshi
@rahul08Rahul Joshi is a seasoned technology leader specializing in the n8n automation tool and AI-driven workflow automation. With deep expertise in building open-source workflow automation and self-hosted automation platforms, he helps organizations eliminate manual processes through intelligent n8n ai agent automation solutions.
外部链接
在 n8n.io 查看 →
分享此工作流