8
n8n 中文网amn8n.com

使用n8n API和邮件发送生成周度工作流分析报告

高级

这是一个DevOps领域的自动化工作流,包含 19 个节点。主要使用 N8n, Set, Code, Gmail, Merge 等节点。 使用n8n API和邮件发送生成周度工作流分析报告

前置要求
  • Google 账号和 Gmail API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "89249a8a187ba6e01e16112a0d334a3aa01d510ad8f88d223e12cc0a2a8beb6b"
  },
  "nodes": [
    {
      "id": "9c2f922e-2f23-417f-9e26-f2f91d719728",
      "name": "计划触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        912,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "daysInterval": 7
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ea56ce13-5b66-439c-8241-3c8eecb06eb8",
      "name": "获取所有先前执行记录",
      "type": "n8n-nodes-base.n8n",
      "position": [
        1824,
        112
      ],
      "parameters": {
        "filters": {},
        "options": {},
        "resource": "execution",
        "returnAll": true,
        "requestOptions": {}
      },
      "notesInFlow": false,
      "typeVersion": 1
    },
    {
      "id": "22cefb90-bce7-402d-a996-6489bda7c136",
      "name": "获取所有工作流",
      "type": "n8n-nodes-base.n8n",
      "position": [
        1424,
        -160
      ],
      "parameters": {
        "filters": {},
        "requestOptions": {}
      },
      "typeVersion": 1
    },
    {
      "id": "b6395b45-7c15-4ab4-9dc4-905ddf04d54b",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        2496,
        0
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "clashHandling": {
            "values": {
              "resolveClash": "preferLast"
            }
          }
        },
        "advanced": true,
        "joinMode": "enrichInput2",
        "mergeByFields": {
          "values": [
            {
              "field1": "workflowId",
              "field2": "workflowId"
            }
          ]
        }
      },
      "typeVersion": 3.2
    },
    {
      "id": "8e131881-f7ab-46cf-9ebc-f006a819d66d",
      "name": "编辑字段 ID 为 workflowId",
      "type": "n8n-nodes-base.set",
      "position": [
        2160,
        -160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "474e54af-e79e-4d58-8c11-dbd920f0511c",
              "name": "workflowId",
              "type": "string",
              "value": "={{ $json.id }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "12a17b24-b3c7-4d40-8a03-3b84c932521b",
      "name": "筛选上周的执行记录",
      "type": "n8n-nodes-base.filter",
      "position": [
        2896,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "31745f1d-793a-4674-80ab-77afede449d6",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              },
              "leftValue": "={{ $json.startedAt }}",
              "rightValue": "={{ DateTime.now().minus({ days: 7 }) }}"
            }
          ]
        }
      },
      "typeVersion": 2,
      "alwaysOutputData": false
    },
    {
      "id": "2e18c677-62ae-4228-9aeb-ba665f8d05c8",
      "name": "准备 HTML 报告",
      "type": "n8n-nodes-base.code",
      "position": [
        3376,
        0
      ],
      "parameters": {
        "jsCode": "// Calculate the date range (7 days ago to now)\nconst now = new Date();\nconst sevenDaysAgo = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));\n\n// Group executions by workflow\nconst workflowStats = {};\n\nfor (const item of $input.all()) {\n  const workflowId = item.json.workflowId;\n  const workflowName = item.json.name;\n  const status = item.json.status;\n  const startedAt = new Date(item.json.startedAt);\n  const stoppedAt = item.json.stoppedAt ? new Date(item.json.stoppedAt) : null;\n  \n  // Calculate runtime in seconds\n  let runtime = 0;\n  if (stoppedAt) {\n    runtime = (stoppedAt - startedAt) / 1000; // Convert to seconds\n  }\n  \n  // Initialize workflow stats if not exists\n  if (!workflowStats[workflowId]) {\n    workflowStats[workflowId] = {\n      name: workflowName,\n      id: workflowId,\n      error: { count: 0, totalRuntime: 0 },\n      success: { count: 0, totalRuntime: 0 },\n      waiting: { count: 0, totalRuntime: 0 }\n    };\n  }\n  \n  // Update counts and runtime based on status\n  if (status === 'error') {\n    workflowStats[workflowId].error.count++;\n    workflowStats[workflowId].error.totalRuntime += runtime;\n  } else if (status === 'success') {\n    workflowStats[workflowId].success.count++;\n    workflowStats[workflowId].success.totalRuntime += runtime;\n  } else if (status === 'waiting') {\n    workflowStats[workflowId].waiting.count++;\n    workflowStats[workflowId].waiting.totalRuntime += runtime;\n  }\n}\n\n// Helper function to format date (without time)\nfunction formatDate(date) {\n  const options = { \n    year: 'numeric', \n    month: 'long', \n    day: 'numeric'\n  };\n  return date.toLocaleDateString('en-US', options);\n}\n\n// Helper function to format runtime\nfunction formatRuntime(seconds) {\n  if (seconds < 60) {\n    return `${seconds.toFixed(2)}s`;\n  } else if (seconds < 3600) {\n    const minutes = Math.floor(seconds / 60);\n    const remainingSeconds = (seconds % 60).toFixed(0);\n    return `${minutes}m ${remainingSeconds}s`;\n  } else {\n    const hours = Math.floor(seconds / 3600);\n    const minutes = Math.floor((seconds % 3600) / 60);\n    return `${hours}h ${minutes}m`;\n  }\n}\n\n// Format date range for header and subject (7 days ago to now)\nconst fromDate = formatDate(sevenDaysAgo);\nconst toDate = formatDate(now);\nconst dateRangeText = `${fromDate} - ${toDate}`;\nconst subject = `n8n Execution Report ${fromDate} - ${toDate}`;\n\n// Build HTML report\nlet html = `\n<!DOCTYPE html>\n<html>\n<head>\n  <style>\n    body {\n      font-family: Arial, sans-serif;\n      margin: 20px;\n      background-color: #f5f5f5;\n    }\n    .header {\n      background-color: white;\n      padding: 20px;\n      margin-bottom: 20px;\n      border-radius: 8px;\n      box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n    }\n    h1 {\n      color: #333;\n      margin: 0 0 10px 0;\n    }\n    .date-range {\n      color: #666;\n      font-size: 1.1em;\n      margin: 10px 0;\n    }\n    .summary {\n      display: flex;\n      gap: 20px;\n      margin-top: 15px;\n    }\n    .summary-item {\n      padding: 10px 15px;\n      border-radius: 4px;\n      font-weight: bold;\n    }\n    .summary-error {\n      background-color: #ffebee;\n      color: #c62828;\n    }\n    .summary-success {\n      background-color: #e8f5e9;\n      color: #2e7d32;\n    }\n    .summary-waiting {\n      background-color: #fff3e0;\n      color: #ef6c00;\n    }\n    table {\n      width: 100%;\n      border-collapse: collapse;\n      background-color: white;\n      box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n      border-radius: 8px;\n      overflow: hidden;\n    }\n    th {\n      background-color: #4CAF50;\n      color: white;\n      padding: 12px;\n      text-align: left;\n      font-weight: bold;\n    }\n    td {\n      padding: 10px 12px;\n      border-bottom: 1px solid #ddd;\n    }\n    tr:hover {\n      background-color: #f5f5f5;\n    }\n    .workflow-name {\n      font-weight: bold;\n      color: #333;\n    }\n    .workflow-id {\n      color: #666;\n      font-size: 0.9em;\n      display: block;\n      margin-top: 4px;\n    }\n    .status-cell {\n      text-align: center;\n    }\n    .status-badge {\n      display: inline-block;\n      padding: 4px 8px;\n      border-radius: 4px;\n      font-size: 0.85em;\n      font-weight: bold;\n      margin-bottom: 4px;\n    }\n    .error { background-color: #ffebee; color: #c62828; }\n    .success { background-color: #e8f5e9; color: #2e7d32; }\n    .waiting { background-color: #fff3e0; color: #ef6c00; }\n    .runtime {\n      font-size: 0.8em;\n      color: #666;\n      display: block;\n      margin-top: 2px;\n    }\n    .no-data {\n      color: #999;\n      font-style: italic;\n    }\n  </style>\n</head>\n<body>\n  <div class=\"header\">\n    <h1>n8n Execution Report</h1>\n    <div class=\"date-range\">\n      <strong>Period:</strong> ${dateRangeText}\n    </div>\n`;\n\n// Calculate totals for summary\nlet totalError = 0;\nlet totalSuccess = 0;\nlet totalWaiting = 0;\n\nfor (const workflowId in workflowStats) {\n  const stats = workflowStats[workflowId];\n  totalError += stats.error.count;\n  totalSuccess += stats.success.count;\n  totalWaiting += stats.waiting.count;\n}\n\nhtml += `\n    <div class=\"summary\">\n      <div class=\"summary-item summary-error\">\n        ✗ ${totalError} Errors\n      </div>\n      <div class=\"summary-item summary-success\">\n        ✓ ${totalSuccess} Success\n      </div>\n      <div class=\"summary-item summary-waiting\">\n        ⏳ ${totalWaiting} Waiting\n      </div>\n    </div>\n  </div>\n  \n  <table>\n    <thead>\n      <tr>\n        <th>Workflow</th>\n        <th class=\"status-cell\">Error</th>\n        <th class=\"status-cell\">Success</th>\n        <th class=\"status-cell\">Waiting</th>\n      </tr>\n    </thead>\n    <tbody>\n`;\n\n// Add rows for each workflow\nfor (const workflowId in workflowStats) {\n  const stats = workflowStats[workflowId];\n  \n  html += `\n      <tr>\n        <td>\n          <span class=\"workflow-name\">${stats.name}</span>\n          <span class=\"workflow-id\">${stats.id}</span>\n        </td>\n  `;\n  \n  // Error column\n  html += '<td class=\"status-cell\">';\n  if (stats.error.count > 0) {\n    const avgRuntime = stats.error.totalRuntime / stats.error.count;\n    html += `\n      <span class=\"status-badge error\">✗ ${stats.error.count}</span>\n      <span class=\"runtime\">Avg: ${formatRuntime(avgRuntime)}</span>\n      <span class=\"runtime\">Total: ${formatRuntime(stats.error.totalRuntime)}</span>\n    `;\n  } else {\n    html += '<span class=\"no-data\">-</span>';\n  }\n  html += '</td>';\n  \n  // Success column\n  html += '<td class=\"status-cell\">';\n  if (stats.success.count > 0) {\n    const avgRuntime = stats.success.totalRuntime / stats.success.count;\n    html += `\n      <span class=\"status-badge success\">✓ ${stats.success.count}</span>\n      <span class=\"runtime\">Avg: ${formatRuntime(avgRuntime)}</span>\n      <span class=\"runtime\">Total: ${formatRuntime(stats.success.totalRuntime)}</span>\n    `;\n  } else {\n    html += '<span class=\"no-data\">-</span>';\n  }\n  html += '</td>';\n  \n  // Waiting column\n  html += '<td class=\"status-cell\">';\n  if (stats.waiting.count > 0) {\n    const avgRuntime = stats.waiting.totalRuntime / stats.waiting.count;\n    html += `\n      <span class=\"status-badge waiting\">⏳ ${stats.waiting.count}</span>\n      <span class=\"runtime\">Avg: ${formatRuntime(avgRuntime)}</span>\n      <span class=\"runtime\">Total: ${formatRuntime(stats.waiting.totalRuntime)}</span>\n    `;\n  } else {\n    html += '<span class=\"no-data\">-</span>';\n  }\n  html += '</td>';\n  \n  html += '</tr>';\n}\n\nhtml += `\n    </tbody>\n  </table>\n</body>\n</html>\n`;\n\nreturn [{ json: { html: html, subject: subject } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4b1154e2-76bc-4b8c-a2b6-f4338a3c3f10",
      "name": "通过 Gmail 发送消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3792,
        -176
      ],
      "webhookId": "c0581cd3-0b50-4ed9-844d-2e4c7af1b30a",
      "parameters": {
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2946c6b2-f93f-4e31-bca1-14995de72e7d",
      "name": "通过 Outlook 发送消息",
      "type": "n8n-nodes-base.microsoftOutlook",
      "position": [
        3792,
        96
      ],
      "webhookId": "87fa1111-29bb-471d-b68f-ae35f785dc6f",
      "parameters": {
        "subject": "={{ $json.subject }}",
        "bodyContent": "={{ $json.html }}",
        "additionalFields": {
          "bodyContentType": "html"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0faa8265-c32e-479f-873d-c603722afe12",
      "name": "便签 - 概述",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -160
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 1020,
        "content": "## 记录 n8n 执行情况的工作流概览"
      },
      "typeVersion": 1
    },
    {
      "id": "595171e1-c6b0-48b8-bf86-86ffb155e118",
      "name": "便签 - 发送 Gmail 消息",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3664,
        -1056
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 860,
        "content": "## 📧 发送 Gmail 消息"
      },
      "typeVersion": 1
    },
    {
      "id": "7a12b760-b67c-47ff-995c-6ac53194a036",
      "name": "便签 - 发送 Outlook 消息",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3680,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 📩 发送 Outlook 消息"
      },
      "typeVersion": 1
    },
    {
      "id": "a85a215d-c1ef-4695-b965-9d3a56cfd9b1",
      "name": "便签 - 准备 HTML 报告",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3248,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 📁 准备 HTML 报告"
      },
      "typeVersion": 1
    },
    {
      "id": "d134b4f0-1fbc-404c-9edc-9f2884824226",
      "name": "便签 - 过滤上周执行",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2848,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 过滤上周执行"
      },
      "typeVersion": 1
    },
    {
      "id": "d6d8301c-b2f7-4e8b-b3ed-f32edd84c92b",
      "name": "便签 - 合并",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 合并"
      },
      "typeVersion": 1
    },
    {
      "id": "95f22f9d-86fa-4cc3-aeba-38d90320ca29",
      "name": "便签 - 编辑字段 ID 为 workflowId",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 编辑字段 ID 为 workflowId"
      },
      "typeVersion": 1
    },
    {
      "id": "9be4bac7-d68a-47d8-87c5-99df34830f13",
      "name": "便签 - 获取所有先前执行",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1664,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 获取所有先前执行"
      },
      "typeVersion": 1
    },
    {
      "id": "9946f889-e6c4-46e0-bd6e-b5fe41a6e991",
      "name": "便签 - 获取所有工作流",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 获取所有工作流"
      },
      "typeVersion": 1
    },
    {
      "id": "27a53d26-b49c-41f9-9cde-49c81e3a5454",
      "name": "便签 - 计划触发器",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        864,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 1020,
        "content": "## 🗓️ 计划触发器"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Filter executions last week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get all previous executions",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get all Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all Workflows": {
      "main": [
        [
          {
            "node": "Edit Field ID to workflowId",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare html report": {
      "main": [
        [
          {
            "node": "Send a message outlook",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send a message gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Field ID to workflowId": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter executions last week": {
      "main": [
        [
          {
            "node": "Prepare html report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all previous executions": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 开发运维

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量19
分类1
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Wessel Bulte

Wessel Bulte

@uuessel

Cybersecurity and automation consultant specializing in n8n workflows for GDPR compliance, process optimization, and business integration. Helping teams streamline operations with secure, scalable automation solutions.

外部链接
在 n8n.io 查看

分享此工作流