8
n8n 中文网amn8n.com

使用GPT-4自动化从ClickUp到Slack和Gmail的每日晨报生成

高级

这是一个自动化工作流,包含 27 个节点。主要使用 If, Code, Gmail, Slack, ClickUp 等节点。 使用GPT-4o从ClickUp生成AI驱动的晨报并发送至Slack和Gmail

前置要求
  • Google 账号和 Gmail API 凭证
  • Slack Bot Token 或 Webhook URL
  • OpenAI API Key

分类

-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "GUsJXoE0d02fP7ii",
  "meta": {
    "instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
    "templateCredsSetupCompleted": true
  },
  "name": "使用 GPT-4 自动化从 ClickUp 到 Slack 和 Gmail 的每日晨报生成",
  "tags": [],
  "nodes": [
    {
      "id": "2bf648b3-58dc-4003-b682-9d35749f3c9f",
      "name": "注意:工作流概览",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4032,
        -384
      ],
      "parameters": {
        "color": 4,
        "width": 454,
        "height": 794,
        "content": "## 🌅 使用 GPT-4 自动化从 ClickUp 到 Slack 和 Gmail 的每日晨报生成"
      },
      "typeVersion": 1
    },
    {
      "id": "92a7dff2-bade-4ad8-884c-c6d6d78276c8",
      "name": "注意:计划触发器",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3488,
        -512
      ],
      "parameters": {
        "width": 276,
        "height": 362,
        "content": "## ⏰ 计划触发器"
      },
      "typeVersion": 1
    },
    {
      "id": "351c23ff-37f5-4305-888b-9d41cd6af400",
      "name": "触发器:早晨计划",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -3344,
        -80
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "15 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "788b9c85-c74a-4533-8552-7f1b6523516b",
      "name": "注意:获取冲刺",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3168,
        -576
      ],
      "parameters": {
        "width": 285,
        "height": 451,
        "content": "## 📋 获取冲刺列表"
      },
      "typeVersion": 1
    },
    {
      "id": "22bc18f9-01ab-4674-ba53-c72f7c71685c",
      "name": "获取所有列表(冲刺)",
      "type": "n8n-nodes-base.clickUp",
      "position": [
        -3120,
        -80
      ],
      "parameters": {
        "team": "YOUR_TEAM_ID",
        "limit": 100,
        "space": "YOUR_SPACE_ID",
        "folder": "YOUR_FOLDER_ID",
        "filters": {},
        "resource": "list",
        "operation": "getAll",
        "authentication": "oAuth2"
      },
      "credentials": {
        "clickUpOAuth2Api": {
          "id": "6UikDwF6BMx3ln9O",
          "name": "ClickUp account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cfc16d55-b75a-4801-8620-cc5aab3acf46",
      "name": "注意:验证数据",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2816,
        -512
      ],
      "parameters": {
        "width": 249,
        "height": 367,
        "content": "## ✅ 验证检查"
      },
      "typeVersion": 1
    },
    {
      "id": "ff0eb47a-246d-4aed-8760-a09020487bbb",
      "name": "检查列表是否存在",
      "type": "n8n-nodes-base.if",
      "position": [
        -2896,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition1",
              "operator": {
                "type": "boolean",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.isNotEmpty() }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4c9545e5-eda5-444e-bceb-6d61d7f06648",
      "name": "注意:冲刺逻辑",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2736,
        96
      ],
      "parameters": {
        "width": 263,
        "height": 388,
        "content": "## 🔍 查找活跃冲刺"
      },
      "typeVersion": 1
    },
    {
      "id": "997dcb26-240f-4ed0-86bb-9bcb302aaa47",
      "name": "查找最新冲刺",
      "type": "n8n-nodes-base.code",
      "position": [
        -2672,
        -80
      ],
      "parameters": {
        "jsCode": "// Get currently active sprint or the latest one\nconst lists = $input.all();\nconst now = Date.now();\n\n// First, try to find active sprints (current time between start and due date)\nconst activeSprints = lists.filter(list => {\n  const startDate = parseInt(list.json.start_date);\n  const dueDate = parseInt(list.json.due_date);\n  return startDate <= now && now <= dueDate;\n});\n\nlet latestSprint;\n\nif (activeSprints.length > 0) {\n  // If we have active sprints, get the most recent one by start date\n  latestSprint = activeSprints.sort((a, b) => \n    parseInt(b.json.start_date) - parseInt(a.json.start_date)\n  )[0];\n} else {\n  // No active sprints, get the most recent by start date (could be future sprint)\n  latestSprint = lists.sort((a, b) => \n    parseInt(b.json.start_date) - parseInt(a.json.start_date)\n  )[0];\n}\n\nreturn {\n  json: {\n    latest_sprint_id: latestSprint.json.id,\n    latest_sprint_name: latestSprint.json.name,\n    latest_sprint_start_date: latestSprint.json.start_date,\n    latest_sprint_due_date: latestSprint.json.due_date,\n    is_currently_active: activeSprints.length > 0,\n    total_sprints_found: lists.length\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "46cc6963-ea8a-4bb5-ac09-de10816754e9",
      "name": "注意:任务检索",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        -560
      ],
      "parameters": {
        "width": 259,
        "height": 390,
        "content": "## 📥 获取冲刺任务"
      },
      "typeVersion": 1
    },
    {
      "id": "18dd7a8f-caad-4a73-b334-6d6140638834",
      "name": "从最新冲刺获取任务",
      "type": "n8n-nodes-base.clickUp",
      "position": [
        -2448,
        -80
      ],
      "parameters": {
        "list": "={{ $json.latest_sprint_id }}",
        "team": "YOUR_TEAM_ID",
        "space": "YOUR_SPACE_ID",
        "folder": "={{ $json.latest_sprint_name }}",
        "filters": {
          "subtasks": true,
          "dueDateGt": "={{ new Date().setHours(0, 0, 0, 0) }}",
          "dueDateLt": "={{ new Date().setHours(23, 59, 59, 999) }}"
        },
        "operation": "getAll",
        "authentication": "oAuth2"
      },
      "credentials": {
        "clickUpOAuth2Api": {
          "id": "6UikDwF6BMx3ln9O",
          "name": "ClickUp account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2d279459-7101-408e-8a79-10030d627705",
      "name": "注意:数据处理",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2304,
        96
      ],
      "parameters": {
        "width": 264,
        "height": 313,
        "content": "## 📊 格式化任务数据"
      },
      "typeVersion": 1
    },
    {
      "id": "d76873cc-ba60-4cbe-9163-05e7b90baf26",
      "name": "格式化:撰写简报数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -2224,
        -80
      ],
      "parameters": {
        "jsCode": "// Get all inputs dynamically\nconst allInputs = $input.all();\n\n// Extract ClickUp data from all items\nconst clickupData = allInputs.map(item => item.json).filter(item => item && item.id);\n\nconst highPriority = [];\nconst normalPriority = [];\nconst blockers = [];\nconst urgentTasks = [];\nconst inProgress = [];\n\nclickupData.forEach(task => {\n  // Extract checklist items as subtasks\n  const subtasks = [];\n  if (task.checklists && task.checklists.length > 0) {\n    task.checklists.forEach(checklist => {\n      if (checklist.items && checklist.items.length > 0) {\n        checklist.items.forEach(item => {\n          subtasks.push({\n            name: item.name,\n            resolved: item.resolved,\n            assignee: item.assignee ? 'Assigned' : 'Unassigned'\n          });\n        });\n      }\n    });\n  }\n\n  const taskInfo = {\n    name: task.name || 'Untitled',\n    status: task.status?.status || 'Unknown',\n    statusType: task.status?.type || 'unknown',\n    priority: task.priority?.priority || 'normal',\n    url: task.url || '',\n    assignees: task.assignees?.map(a => a.username).join(', ') || 'Unassigned',\n    list: task.list?.name || '',\n    folder: task.folder?.name || task.project?.name || '',\n    dueDate: task.due_date ? new Date(parseInt(task.due_date)).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : 'No due date',\n    timeSpent: task.time_spent ? `${Math.floor(task.time_spent / 60000)} min` : null,\n    description: task.description ? (task.description.length > 100 ? task.description.substring(0, 100) + '...' : task.description) : null,\n    subtasks: subtasks,\n    subtasksSummary: subtasks.length > 0 ? `${subtasks.filter(s => s.resolved).length}/${subtasks.length} completed` : null\n  };\n  \n  // Categorize by status first (in progress)\n  if (task.status?.status?.toLowerCase().includes('progress')) {\n    inProgress.push(taskInfo);\n  }\n  // Check for blockers\n  else if (task.status?.status?.toLowerCase().includes('block')) {\n    blockers.push(taskInfo);\n  } \n  // Then by priority\n  else if (task.priority?.priority === 'urgent') {\n    urgentTasks.push(taskInfo);\n  } \n  else if (task.priority?.priority === 'high') {\n    highPriority.push(taskInfo);\n  } \n  else {\n    normalPriority.push(taskInfo);\n  }\n});\n\n// Dynamically get board URL from first task\nconst firstTask = clickupData[0];\nconst boardUrl = firstTask?.list?.id \n  ? `https://app.clickup.com/${firstTask.team_id}/v/li/${firstTask.list.id}`\n  : firstTask?.space?.id \n  ? `https://app.clickup.com/${firstTask.team_id}/v/s/${firstTask.space.id}`\n  : 'https://app.clickup.com';\n\n// Count status types\nconst statusSummary = {\n  backlogs: clickupData.filter(t => t.status?.status === 'backlogs').length,\n  inProgress: clickupData.filter(t => t.status?.status?.toLowerCase().includes('progress')).length,\n  completed: clickupData.filter(t => t.status?.type === 'closed').length\n};\n\nconst summary = {\n  date: new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }),\n  inProgressTasks: inProgress,\n  urgentTasks: urgentTasks,\n  highPriorityTasks: highPriority,\n  normalPriorityTasks: normalPriority,\n  blockers: blockers,\n  clickupBoardUrl: boardUrl,\n  totalTasks: clickupData.length,\n  listName: firstTask?.list?.name || 'Tasks',\n  folderName: firstTask?.folder?.name || firstTask?.project?.name || 'Board',\n  statusSummary: statusSummary,\n  teamId: firstTask?.team_id || null\n};\n\nreturn { json: summary };"
      },
      "typeVersion": 2
    },
    {
      "id": "d1a1920b-f54d-4172-9b8c-b17054b3a39d",
      "name": "注意:AI 处理",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2080,
        -480
      ],
      "parameters": {
        "width": 385,
        "height": 396,
        "content": "## 🤖 AI 简报生成"
      },
      "typeVersion": 1
    },
    {
      "id": "e5fae6c6-9533-46b3-be91-0441dc52ad08",
      "name": "OpenAI:生成简报",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1936,
        -80
      ],
      "parameters": {
        "text": "=Create a comprehensive Morning Brief for {{ $json.date }}.\n\n## CURRENT DATE\n{{ $json.date }}\n\n## SPRINT OVERVIEW\n- **List:** {{ $json.listName }}\n- **Total Tasks:** {{ $json.totalTasks }}\n- **Status Breakdown:** {{ $json.statusSummary.backlogs }} Backlogs | {{ $json.statusSummary.inProgress }} In Progress | {{ $json.statusSummary.completed }} Completed\n\n## IN PROGRESS TASKS ({{ $json.inProgressTasks.length }})\n{{ $json.inProgressTasks.map(t => `- **${t.name}** (${t.assignees}) - Due: ${t.dueDate}${t.timeSpent ? ' | Time Spent: ' + t.timeSpent : ''}${t.subtasksSummary ? ' | Subtasks: ' + t.subtasksSummary : ''}`).join('\\n') || 'None' }}\n\n## HIGH PRIORITY TASKS\n{{ $json.highPriorityTasks.map(t => `- **${t.name}** (${t.assignees}) - Due: ${t.dueDate} - ${t.priority.toUpperCase()}`).join('\\n') || 'None' }}\n\n## NORMAL PRIORITY TASKS\n{{ $json.normalPriorityTasks.map(t => `- **${t.name}** (${t.assignees}) - Due: ${t.dueDate}${t.subtasksSummary ? ' | ' + t.subtasksSummary : ''}`).join('\\n') || 'None' }}\n\n## BLOCKERS ({{ $json.blockers.length }})\n{{ $json.blockers.map(t => `- **${t.name}** (${t.assignees}) - Status: ${t.status}`).join('\\n') || 'None - All Clear!' }}\n\n## ALL TEAM MEMBERS\n{{ [...new Set([...$json.inProgressTasks, ...$json.highPriorityTasks, ...$json.normalPriorityTasks].map(t => t.assignees).join(', ').split(', '))].join(', ') }}\n\n---\n\n**Instructions:**\nToday's date is: {{ $json.date }}\n\nAnalyze the tasks above and identify which ones are due TODAY based on the dueDate field.\n\nReturn ONLY valid JSON with this exact structure:\n{\n  \"title\": \"Morning Brief - {{ $json.date }}\",\n  \"executiveSummary\": \"2-3 sentence overview highlighting key priorities and team focus for TODAY\",\n  \"inProgressHighlights\": \"Brief summary of active in-progress tasks and their status\",\n  \"todaysPriorities\": \"List ALL tasks due TODAY. Format as bullet points with task name and assignee. If no tasks due today, state 'No tasks due today - focus on in-progress items'\",\n  \"teamFocus\": \"For each team member, list what they are working on TODAY. Focus on tasks due today or in-progress tasks. Group by person name.\",\n  \"blockers\": \"{{ $json.blockers.length > 0 ? 'List blockers' : 'All Clear' }}\",\n  \"clickupBoardUrl\": \"{{ $json.clickupBoardUrl }}\",\n  \"metrics\": {\n    \"totalTasks\": {{ $json.totalTasks }},\n    \"dueToday\": \"COUNT how many tasks are due today\",\n    \"inProgress\": {{ $json.statusSummary.inProgress }},\n    \"backlogs\": {{ $json.statusSummary.backlogs }}\n  }\n}\n\nCRITICAL: \n- Look at the dueDate field for each task\n- In \"todaysPriorities\", list ONLY tasks due today\n- In \"teamFocus\", mention what each person is working on\n- Be specific and actionable",
        "options": {
          "systemMessage": "=You are a professional executive assistant creating detailed morning briefs for a team.\nYour response must:\n- Be structured, actionable, and team-focused\n- Highlight in-progress tasks and blockers prominently\n- Group tasks by assignee/team member\n- Show clear priorities with due dates\n- Include subtask progress where available\n- Be concise but comprehensive\n- Focus on TODAY's tasks and priorities\n- Use the exact date provided in the input data\n\nAlways output valid JSON matching the exact schema provided.\nNever add markdown fences, code blocks, or explanatory text."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "357b93df-c039-4fde-90f3-6fec91193d32",
      "name": "结构化输出解析器",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -1744,
        160
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"title\": \"\",\n  \"executiveSummary\": \"\",\n  \"inProgressHighlights\": \"\",\n  \"todaysPriorities\": \"\",\n  \"teamFocus\": \"\",\n  \"blockers\": \"\",\n  \"clickupBoardUrl\": \"\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "d1af24e4-dd4f-4e6b-bca7-340f1280624c",
      "name": "Azure OpenAI聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
      "position": [
        -2000,
        160
      ],
      "parameters": {
        "model": "gpt-4o",
        "options": {}
      },
      "credentials": {
        "azureOpenAiApi": {
          "id": "C3WzT18XqF8OdVM6",
          "name": "Azure Open AI account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f336c158-207b-4ec0-8bf9-917a1ad7aabc",
      "name": "简单记忆",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        -1872,
        160
      ],
      "parameters": {
        "sessionKey": "\"lead_intent_classifier\"",
        "sessionIdType": "customKey",
        "contextWindowLength": 7
      },
      "typeVersion": 1.3
    },
    {
      "id": "1d5d6d60-31ef-4cb8-bee0-c781bf54a0ca",
      "name": "注意:电子邮件构建器",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1584,
        192
      ],
      "parameters": {
        "width": 256,
        "height": 349,
        "content": "## 📧 电子邮件格式化"
      },
      "typeVersion": 1
    },
    {
      "id": "0a16b85f-c088-48f4-a4a5-4f236a0a875b",
      "name": "注意:Slack 集成",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1488,
        -480
      ],
      "parameters": {
        "width": 246,
        "height": 299,
        "content": "## 💬 Slack 通知"
      },
      "typeVersion": 1
    },
    {
      "id": "e62daaeb-4688-42d1-92d1-663418816d80",
      "name": "Slack:发布简报",
      "type": "n8n-nodes-base.slack",
      "position": [
        -1536,
        -176
      ],
      "webhookId": "f9ae2b0d-06b5-46e2-9e4f-6133473fc6e4",
      "parameters": {
        "text": "=**Morning Brief** - {{ $('Format: Compose Brief Data').item.json.date }}**\n\n**Summary** - {{ $json.output.executiveSummary }}\n\n**Today's Priority** - {{ $json.output.todaysPriorities }} \n\n**Team's Focus** - {{ $json.output.teamFocus }}\n\nlink: <{{ $('Format: Compose Brief Data').item.json.clickupBoardUrl }}|View ClickUp Today Board>",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "credentials": {
        "slackApi": {
          "id": "rNqvWj9TfChPVRYY",
          "name": "Slack account vivek"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "03f81896-e5e6-42a5-9b1f-cd7102b0091c",
      "name": "注意:邮件发送",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1168,
        48
      ],
      "parameters": {
        "width": 256,
        "height": 421,
        "content": "## 📨 Gmail 投递"
      },
      "typeVersion": 1
    },
    {
      "id": "518dbe59-a863-444c-b34f-fa15529ade86",
      "name": "注意:错误管理",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3472,
        416
      ],
      "parameters": {
        "width": 379,
        "height": 329,
        "content": "## 🚨 错误处理系统"
      },
      "typeVersion": 1
    },
    {
      "id": "6213ae5c-d856-4db2-9dd2-fece957df490",
      "name": "错误触发器:捕获失败",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -3344,
        256
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "865225c0-7954-4202-b893-f798b465d889",
      "name": "Slack:错误警报",
      "type": "n8n-nodes-base.slack",
      "position": [
        -3120,
        256
      ],
      "webhookId": "9625246c-14d5-41a7-a11f-3162e1ad650b",
      "parameters": {
        "text": "=🚨 **Morning Brief Workflow Failed**\n\n*Error:* {{ $json.error.message }}\n*Node:* {{ $json.error.node.name }}\n*Time:* {{ $json.error.timestamp }}\n\nPlease check the workflow and retry manually.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_ERROR_CHANNEL_ID",
          "cachedResultName": "errors"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "credentials": {
        "slackApi": {
          "id": "rNqvWj9TfChPVRYY",
          "name": "Slack account vivek"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "952212b7-d531-4d9e-ba24-49101e309aaa",
      "name": "为电子邮件格式化数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -1536,
        32
      ],
      "parameters": {
        "jsCode": "// N8N Code Node - Enhanced Morning Brief HTML Email Formatter\nconst inputData = $input.all()[0].json.output;\n\n// Helper function to parse priorities into structured data\nfunction parsePriorities(text) {\n    const lines = text.split('\\n').filter(line => line.trim().startsWith('-'));\n    return lines.map(line => {\n        const cleanLine = line.replace(/^-\\s*/, '').trim();\n        const match = cleanLine.match(/^(.+?)\\s*\\(([^)]+)\\)$/);\n        if (match) {\n            return { task: match[1].trim(), assignee: match[2].trim() };\n        }\n        return { task: cleanLine, assignee: '' };\n    });\n}\n\n// Helper function to parse team focus by person\nfunction parseTeamFocus(text) {\n    const sections = text.split(/(?=[A-Z][a-z]+\\s+[A-Z][a-z]+:)/);\n    return sections.filter(s => s.trim()).map(section => {\n        const [name, ...tasks] = section.split(':');\n        return {\n            name: name.trim(),\n            tasks: tasks.join(':').trim()\n        };\n    });\n}\n\nconst priorities = parsePriorities(inputData.todaysPriorities);\nconst teamMembers = parseTeamFocus(inputData.teamFocus);\n\nconst 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.0\">\n    <title>${inputData.title}</title>\n    <!--[if mso]>\n    <style type=\"text/css\">\n        body, table, td {font-family: Arial, sans-serif !important;}\n    </style>\n    <![endif]-->\n</head>\n<body style=\"margin: 0; padding: 0; background-color: #f4f4f4;\">\n    <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"background-color: #f4f4f4;\">\n        <tr>\n            <td align=\"center\" style=\"padding: 20px 0;\">\n                \n                <!-- Main Container -->\n                <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"700\" style=\"background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">\n                    \n                    <!-- Header Section -->\n                    <tr>\n                        <td style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 8px 8px 0 0;\">\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\">\n                                <tr>\n                                    <td>\n                                        <h1 style=\"margin: 0; color: #ffffff; font-size: 28px; font-weight: bold; font-family: Arial, sans-serif; text-shadow: 0 2px 4px rgba(0,0,0,0.2);\">\n                                            📋 ${inputData.title}\n                                        </h1>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Executive Summary Section -->\n                    <tr>\n                        <td style=\"padding: 30px 30px 20px 30px;\">\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border-left: 4px solid #667eea; background-color: #f8f9ff; border-radius: 4px;\">\n                                <tr>\n                                    <td style=\"padding: 20px;\">\n                                        <h2 style=\"margin: 0 0 12px 0; color: #667eea; font-size: 16px; font-weight: bold; font-family: Arial, sans-serif; text-transform: uppercase; letter-spacing: 0.5px;\">\n                                            📊 EXECUTIVE SUMMARY\n                                        </h2>\n                                        <p style=\"margin: 0; color: #2d3748; font-size: 14px; line-height: 1.7; font-family: Arial, sans-serif;\">\n                                            ${inputData.executiveSummary}\n                                        </p>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Today's Priorities Section -->\n                    <tr>\n                        <td style=\"padding: 10px 30px 20px 30px;\">\n                            <h2 style=\"margin: 0 0 15px 0; color: #2d3748; font-size: 18px; font-weight: bold; font-family: Arial, sans-serif; border-bottom: 3px solid #f56565; padding-bottom: 10px;\">\n                                🎯 TODAY'S PRIORITIES (${priorities.length} Tasks)\n                            </h2>\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border-collapse: collapse; border: 1px solid #e2e8f0; border-radius: 4px; overflow: hidden;\">\n                                <tr>\n                                    <td width=\"60\" align=\"center\" style=\"background-color: #f56565; color: #ffffff; font-weight: bold; padding: 12px 8px; font-family: Arial, sans-serif; font-size: 13px;\">\n                                        #\n                                    </td>\n                                    <td style=\"background-color: #f56565; color: #ffffff; font-weight: bold; padding: 12px 15px; font-family: Arial, sans-serif; font-size: 13px;\">\n                                        Task Description\n                                    </td>\n                                    <td width=\"150\" style=\"background-color: #f56565; color: #ffffff; font-weight: bold; padding: 12px 15px; font-family: Arial, sans-serif; font-size: 13px;\">\n                                        Assigned To\n                                    </td>\n                                </tr>\n                                ${priorities.map((priority, index) => `\n                                <tr>\n                                    <td align=\"center\" style=\"background-color: ${index % 2 === 0 ? '#ffffff' : '#f7fafc'}; padding: 14px 8px; font-family: Arial, sans-serif; font-size: 14px; border-top: 1px solid #e2e8f0; font-weight: bold; color: #f56565;\">\n                                        ${index + 1}\n                                    </td>\n                                    <td style=\"background-color: ${index % 2 === 0 ? '#ffffff' : '#f7fafc'}; padding: 14px 15px; font-family: Arial, sans-serif; font-size: 13px; line-height: 1.5; border-top: 1px solid #e2e8f0; color: #2d3748;\">\n                                        ${priority.task}\n                                    </td>\n                                    <td style=\"background-color: ${index % 2 === 0 ? '#ffffff' : '#f7fafc'}; padding: 14px 15px; font-family: Arial, sans-serif; font-size: 13px; border-top: 1px solid #e2e8f0; color: #4a5568; font-weight: 600;\">\n                                        ${priority.assignee}\n                                    </td>\n                                </tr>\n                                `).join('')}\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- In Progress Highlights Section -->\n                    <tr>\n                        <td style=\"padding: 10px 30px 20px 30px;\">\n                            <h2 style=\"margin: 0 0 15px 0; color: #2d3748; font-size: 18px; font-weight: bold; font-family: Arial, sans-serif; border-bottom: 3px solid #ed8936; padding-bottom: 10px;\">\n                                ⚡ IN PROGRESS HIGHLIGHTS\n                            </h2>\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border-left: 4px solid #ed8936; background-color: #fffaf0; border-radius: 4px;\">\n                                <tr>\n                                    <td style=\"padding: 18px;\">\n                                        <p style=\"margin: 0; color: #2d3748; font-size: 14px; line-height: 1.7; font-family: Arial, sans-serif;\">\n                                            ${inputData.inProgressHighlights}\n                                        </p>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Team Focus Section -->\n                    <tr>\n                        <td style=\"padding: 10px 30px 20px 30px;\">\n                            <h2 style=\"margin: 0 0 15px 0; color: #2d3748; font-size: 18px; font-weight: bold; font-family: Arial, sans-serif; border-bottom: 3px solid #48bb78; padding-bottom: 10px;\">\n                                👥 TEAM FOCUS & ASSIGNMENTS\n                            </h2>\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border-collapse: collapse;\">\n                                ${teamMembers.map((member, index) => `\n                                <tr>\n                                    <td style=\"padding: ${index > 0 ? '15' : '0'}px 0 0 0;\">\n                                        <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border: 2px solid #48bb78; border-radius: 6px; overflow: hidden; background-color: #f0fff4;\">\n                                            <tr>\n                                                <td style=\"background-color: #48bb78; padding: 12px 18px;\">\n                                                    <p style=\"margin: 0; color: #ffffff; font-size: 14px; font-weight: bold; font-family: Arial, sans-serif;\">\n                                                        👤 ${member.name}\n                                                    </p>\n                                                </td>\n                                            </tr>\n                                            <tr>\n                                                <td style=\"padding: 15px 18px;\">\n                                                    <p style=\"margin: 0; color: #2d3748; font-size: 13px; line-height: 1.7; font-family: Arial, sans-serif;\">\n                                                        ${member.tasks}\n                                                    </p>\n                                                </td>\n                                            </tr>\n                                        </table>\n                                    </td>\n                                </tr>\n                                `).join('')}\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Blockers Section -->\n                    <tr>\n                        <td style=\"padding: 10px 30px 20px 30px;\">\n                            <h2 style=\"margin: 0 0 15px 0; color: #2d3748; font-size: 18px; font-weight: bold; font-family: Arial, sans-serif; border-bottom: 3px solid ${inputData.blockers.toLowerCase().includes('all clear') ? '#48bb78' : '#f56565'}; padding-bottom: 10px;\">\n                                ${inputData.blockers.toLowerCase().includes('all clear') ? '✅' : '⚠️'} BLOCKERS & ISSUES\n                            </h2>\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\" style=\"border-left: 4px solid ${inputData.blockers.toLowerCase().includes('all clear') ? '#48bb78' : '#f56565'}; background-color: ${inputData.blockers.toLowerCase().includes('all clear') ? '#f0fff4' : '#fff5f5'}; border-radius: 4px;\">\n                                <tr>\n                                    <td style=\"padding: 18px;\">\n                                        <p style=\"margin: 0; color: #2d3748; font-size: 15px; font-weight: ${inputData.blockers.toLowerCase().includes('all clear') ? 'bold' : 'normal'}; font-family: Arial, sans-serif; line-height: 1.6;\">\n                                            ${inputData.blockers}\n                                        </p>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Action Button -->\n                    <tr>\n                        <td align=\"center\" style=\"padding: 25px 30px 35px 30px;\">\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">\n                                <tr>\n                                    <td align=\"center\" style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 16px 45px; border-radius: 6px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);\">\n                                        <a href=\"${inputData.clickupBoardUrl}\" style=\"color: #ffffff; text-decoration: none; font-weight: bold; font-size: 14px; font-family: Arial, sans-serif; display: block; letter-spacing: 0.5px;\">\n                                            🔗 VIEW FULL BOARD ON CLICKUP\n                                        </a>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                    <!-- Footer -->\n                    <tr>\n                        <td style=\"background-color: #f7fafc; padding: 25px 30px; border-top: 1px solid #e2e8f0; border-radius: 0 0 8px 8px;\">\n                            <table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" width=\"100%\">\n                                <tr>\n                                    <td align=\"center\">\n                                        <p style=\"margin: 0; color: #718096; font-size: 12px; line-height: 1.6; font-family: Arial, sans-serif;\">\n                                            🤖 This brief was automatically generated by N8N Automation<br>\n                                            <strong style=\"color: #4a5568;\">Have a productive day! 🚀</strong>\n                                        </p>\n                                    </td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                    \n                </table>\n                \n            </td>\n        </tr>\n    </table>\n</body>\n</html>\n`;\n\n// Enhanced plain text version\nconst plainText = `${inputData.title}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n📊 EXECUTIVE SUMMARY\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${inputData.executiveSummary}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n🎯 TODAY'S PRIORITIES (${priorities.length} Tasks)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${priorities.map((p, i) => `${i + 1}. ${p.task} → ${p.assignee}`).join('\\n')}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n⚡ IN PROGRESS HIGHLIGHTS\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${inputData.inProgressHighlights}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n👥 TEAM FOCUS & ASSIGNMENTS\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${teamMembers.map(m => `👤 ${m.name}\\n   ${m.tasks}`).join('\\n\\n')}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${inputData.blockers.toLowerCase().includes('all clear') ? '✅' : '⚠️'} BLOCKERS & ISSUES\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n${inputData.blockers}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nView Full Board: ${inputData.clickupBoardUrl}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;\n\nreturn {\n  json: {\n    html: html,\n    subject: `📋 ${inputData.title}`,\n    plainText: plainText\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "78494f7e-40da-4c02-aa89-62dc1a3783e4",
      "name": "发送晨报电子邮件",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -1312,
        32
      ],
      "webhookId": "9e36fe20-bb0c-4c0a-8421-2f57096e79d7",
      "parameters": {
        "sendTo": "YOUR_EMAIL@example.com",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "gEIaWCTvGfYjMSb3",
          "name": "Gmail credentials"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "36a5ec42-fd1c-4215-9a66-eb98c6bc60c3",
  "connections": {
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "OpenAI: Generate Brief",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Check Lists Exist": {
      "main": [
        [
          {
            "node": "Find Latest Sprint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Latest Sprint": {
      "main": [
        [
          {
            "node": "Get Task From Latest Sprint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data For Email": {
      "main": [
        [
          {
            "node": "Send Morning Brief Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI: Generate Brief": {
      "main": [
        [
          {
            "node": "Slack: Post Brief",
            "type": "main",
            "index": 0
          },
          {
            "node": "Format Data For Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Azure OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "OpenAI: Generate Brief",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get All Lists (Sprints)": {
      "main": [
        [
          {
            "node": "Check Lists Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "OpenAI: Generate Brief",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Trigger: Morning Schedule": {
      "main": [
        [
          {
            "node": "Get All Lists (Sprints)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format: Compose Brief Data": {
      "main": [
        [
          {
            "node": "OpenAI: Generate Brief",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Task From Latest Sprint": {
      "main": [
        [
          {
            "node": "Format: Compose Brief Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger: Catch Failures": {
      "main": [
        [
          {
            "node": "Slack: Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级

需要付费吗?

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

工作流信息
难度等级
高级
节点数量27
分类-
节点类型12
难度说明

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

作者
Rahul Joshi

Rahul Joshi

@rahul08

Rahul 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 查看

分享此工作流