Worker

工作流概述

这是一个包含29个节点的复杂工作流,主要用于自动化处理各种任务。

工作流源代码

下载
{
  "id": 114,
  "name": "Standup Bot - Worker",
  "nodes": [
    {
      "name": "publish report",
      "type": "n8n-nodes-base.mattermost",
      "position": [
        1840,
        1040
      ],
      "parameters": {
        "message": "={{$node[\"Prep Report\"].json[\"post\"]}}",
        "channelId": "={{$node[\"Prep Report\"].json[\"channel\"]}}",
        "attachments": [],
        "otherOptions": {}
      },
      "credentials": {
        "mattermostApi": {
          "id": "2",
          "name": "Mattermost account"
        }
      },
      "typeVersion": 1
    },
    {
      "name": "get user data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1400,
        1040
      ],
      "parameters": {
        "url": "={{$node[\"Read Config 2\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/users/{{$node[\"Action from MM\"].json[\"body\"][\"user_id\"]}}",
        "options": {},
        "jsonParameters": true,
        "headerParametersJson": "={
\"Authorization\": \"Bearer {{$item(0).$node[\"Read Config 2\"].json[\"config\"][\"botUserToken\"]}}\"
}"
      },
      "typeVersion": 1
    },
    {
      "name": "open-standup-dialog?",
      "type": "n8n-nodes-base.if",
      "position": [
        1180,
        1260
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"Action from MM\"].json[\"body\"][\"context\"][\"action\"]}}",
              "value2": "open-standup-dialog"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "name": "Action from MM",
      "type": "n8n-nodes-base.webhook",
      "position": [
        520,
        820
      ],
      "webhookId": "6a28d86b-9f74-4825-9785-57e0d43b198f",
      "parameters": {
        "path": "standup-bot/action/f6f9b174745fa4651f750c36957d674c",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "name": "Slash Cmd from MM",
      "type": "n8n-nodes-base.webhook",
      "position": [
        520,
        600
      ],
      "webhookId": "72732516-1143-430f-8465-d193fe657311",
      "parameters": {
        "path": "standup-bot/slashCmd",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 1
    },
    {
      "name": "config?",
      "type": "n8n-nodes-base.if",
      "position": [
        740,
        600
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$node[\"Slash Cmd from MM\"].json[\"body\"][\"text\"]}}",
              "value2": "config"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "name": "open config dialog",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1360,
        580
      ],
      "parameters": {
        "url": "={{$node[\"Read Config 1\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/actions/dialogs/open",
        "options": {
          "bodyContentType": "json"
        },
        "requestMethod": "POST",
        "jsonParameters": true,
        "bodyParametersJson": "={{$json}}"
      },
      "typeVersion": 1
    },
    {
      "name": "Prep Config Dialog",
      "type": "n8n-nodes-base.function",
      "position": [
        1160,
        580
      ],
      "parameters": {
        "functionCode": "const channelId =
  $item(0).$node['Slash Cmd from MM'].json['body']['channel_id'];

const configuredStandups =
  $item(0).$node['Read Config 1'].json['standups'] ?? [];

let standup = configuredStandups.find(
  (standup) => standup.channelId == channelId
);

// define default values:
if (!standup) {
  standup = {
    title: 'Team Standup',
    time: '09:00',
    days: [1, 2, 3, 4, 5],
    questions: [
      'What have you accomplished since your last report?',
      'What do you want to accomplish until your next report?',
      'Is anything blocking your progress?',
    ],
    users: [],
  };
}

const payload = {
  trigger_id: $item(0).$node['Slash Cmd from MM'].json['body']['trigger_id'],
  url: $item(0).$node['Read Config 1'].json['config']['n8nWebhookUrl'],
  dialog: {
    callback_id: 'standup-config',
    title: 'Standup Configuration',
    submit_label: 'Save',
    notify_on_cancel: false,
    state: JSON.stringify({ standupId: channelId }),
    elements: [
      {
        display_name: 'Standup title',
        name: 'title',
        type: 'text',
        placeholder: 'Team Standup',
        default: standup.title,
        optional: true,
        help_text:
          '💡 The standup can be deleted by setting its title to an empty string!',
      },
      {
        display_name: 'Time',
        name: 'time',
        type: 'select',
        default: standup.time,
        options: [
          {
            text: '06:00',
            value: '06:00',
          },
          {
            text: '07:00',
            value: '07:00',
          },
          {
            text: '08:00',
            value: '08:00',
          },
          {
            text: '09:00',
            value: '09:00',
          },
          {
            text: '10:00',
            value: '10:00',
          },
          {
            text: '11:00',
            value: '11:00',
          },
          {
            text: '12:00',
            value: '12:00',
          },
          {
            text: '13:00',
            value: '13:00',
          },
          {
            text: '14:00',
            value: '14:00',
          },
          {
            text: '15:00',
            value: '15:00',
          },
          {
            text: '16:00',
            value: '16:00',
          },
          {
            text: '17:00',
            value: '17:00',
          },
        ],
      },
      {
        display_name: 'Days',
        name: 'days',
        type: 'text',
        placeholder: '1,2,3,4,5',
        help_text:
          'comma-separated; 0=Sun | 1=Mon | 2=Tue | 3=Wed | 4=Thu | 5=Fri | 6=Sat',
        default: standup.days.join(','),
      },
      {
        display_name: 'Questions',
        name: 'questions',
        type: 'textarea',
        help_text: 'Max 5 questions, one question per line;',
        default: standup.questions.join('\n'),
      },
      {
        display_name: 'Users',
        name: 'users',
        type: 'textarea',
        help_text: 'One user per line',
        default: standup.users.join('\n'),
      },
    ],
  },
};

return [{ json: payload }];

"
      },
      "typeVersion": 1
    },
    {
      "name": "callback ID?",
      "type": "n8n-nodes-base.switch",
      "position": [
        960,
        820
      ],
      "parameters": {
        "rules": {
          "rules": [
            {
              "value2": "standup-config"
            },
            {
              "output": 1,
              "value2": "standup-answers"
            }
          ]
        },
        "value1": "={{$node[\"Action from MM\"].json[\"body\"][\"callback_id\"]}}",
        "dataType": "string",
        "fallbackOutput": 3
      },
      "typeVersion": 1
    },
    {
      "name": "standup-config",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1180,
        820
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "standup-answers",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1180,
        1040
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "Prep Config Override",
      "type": "n8n-nodes-base.function",
      "position": [
        1400,
        820
      ],
      "parameters": {
        "functionCode": "const mattermostInput = $item(0).$node['Action from MM'].json['body'];
const config = $item(0).$node['Read Config 2'].json;

// ensure there is a \"standups\" array:
config['standups'] = config['standups'] ?? [];

// remove the standup from the list:
config['standups'] = config['standups'].filter(
  (standup) => standup.channelId != mattermostInput.channel_id
);

const textToArray = (text, separator) => {
  return text
    .split(separator)
    .map((e) => e.trim())
    .filter((e) => e.length > 0);
};

// a standup can be deleted by updating its title to \"\"
if (mattermostInput.submission.title.length > 0) {
  const newStandup = {
    channelId: mattermostInput.channel_id,
    title: mattermostInput.submission.title,
    time: mattermostInput.submission.time,
    days: textToArray(mattermostInput.submission.days, ',').map((e) =>
      parseInt(e)
    ),
    users: textToArray(mattermostInput.submission.users, '\n'),
    questions: textToArray(mattermostInput.submission.questions, '\n'),
  };

  config['standups'].push(newStandup);
}

return [{ json: config }];

"
      },
      "typeVersion": 1
    },
    {
      "name": "Override Config",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        1620,
        820
      ],
      "parameters": {
        "workflowId": "1005"
      },
      "typeVersion": 1
    },
    {
      "name": "Read Config 1",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        960,
        580
      ],
      "parameters": {
        "workflowId": "1004"
      },
      "typeVersion": 1
    },
    {
      "name": "Read Config 2",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        740,
        820
      ],
      "parameters": {
        "workflowId": "1004"
      },
      "typeVersion": 1
    },
    {
      "name": "confirm success",
      "type": "n8n-nodes-base.mattermost",
      "position": [
        1840,
        820
      ],
      "parameters": {
        "userId": "={{$node[\"Action from MM\"].json[\"body\"][\"user_id\"]}}",
        "message": "new standup config was saved successfully",
        "channelId": "={{$node[\"Action from MM\"].json[\"body\"][\"channel_id\"]}}",
        "operation": "postEphemeral"
      },
      "credentials": {
        "mattermostApi": {
          "id": "2",
          "name": "Mattermost account"
        }
      },
      "typeVersion": 1
    },
    {
      "name": "Read Config 3",
      "type": "n8n-nodes-base.executeWorkflow",
      "position": [
        740,
        380
      ],
      "parameters": {
        "workflowId": "1004"
      },
      "typeVersion": 1
    },
    {
      "name": "Filter Due Standups",
      "type": "n8n-nodes-base.function",
      "position": [
        960,
        380
      ],
      "parameters": {
        "functionCode": "const config = $item(0).$node['Read Config 3'].json;

// ensure there is a \"standups\" array:
config['standups'] = config['standups'] ?? [];

const now = new Date();
const duePattern = `${now.getDay()}_${now
  .getHours()
  .toString()
  .padStart(2, '0')}:00`; // e.g. 1_13:00 => Monday 1 p.m.
  
console.log(duePattern);

// filter standups that are due now:
const dueStandups = config.standups.filter((standup) =>
  //true
  standup.days.map((day) => `${day}_${standup.time}`).includes(duePattern)
);

return dueStandups.map((standup) => ({
  json: standup,
}));

"
      },
      "typeVersion": 1
    },
    {
      "name": "Prep Request Standup",
      "type": "n8n-nodes-base.function",
      "position": [
        1180,
        380
      ],
      "parameters": {
        "functionCode": "const reminders = items.reduce((prev, curr) => {
  return prev.concat(
    curr.json.users.map((user) => ({
      channelId: curr.json.channelId,
      title: curr.json.title,
      user: user,
    }))
  );
}, []);

return reminders.map((reminder) => ({
  json: reminder,
}));
"
      },
      "typeVersion": 1
    },
    {
      "name": "Create Channel",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1620,
        380
      ],
      "parameters": {
        "url": "={{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/channels/direct",
        "options": {},
        "requestMethod": "POST",
        "jsonParameters": true,
        "bodyParametersJson": "=[\"{{$node[\"Get User\"].json[\"id\"]}}\", \"{{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"botUserId\"]}}\"]",
        "headerParametersJson": "={
  \"Authorization\": \"Bearer {{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"botUserToken\"]}}\"
}"
      },
      "typeVersion": 1
    },
    {
      "name": "Remind Users",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2060,
        380
      ],
      "parameters": {
        "url": "={{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/posts",
        "options": {},
        "requestMethod": "POST",
        "jsonParameters": true,
        "bodyParametersJson": "={{$json}}",
        "headerParametersJson": "={
\"Authorization\": \"Bearer {{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"botUserToken\"]}}\"
}"
      },
      "typeVersion": 1
    },
    {
      "name": "Get User",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1400,
        380
      ],
      "parameters": {
        "url": "={{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/users/username/{{$node[\"Prep Request Standup\"].json[\"user\"]}}",
        "options": {},
        "jsonParameters": true,
        "headerParametersJson": "={
  \"Authorization\": \"Bearer {{$item(0).$node[\"Read Config 3\"].json[\"config\"][\"botUserToken\"]}}\"
}"
      },
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "name": "Prep Reminder",
      "type": "n8n-nodes-base.function",
      "position": [
        1840,
        380
      ],
      "parameters": {
        "functionCode": "const webhookUrl =
  $item(0).$node['Read Config 3'].json['config']['n8nWebhookUrl']; // e.g. https://xyz.app.n8n.cloud/webhook-test/standup-bot/action/top-secret-api-key

const botUserToken =
  $item(0).$node['Read Config 3'].json['config']['botUserToken'];

let itemIndex = 0;

for (item of items) {
  const directChannelId = item.json.id;

  const payload = {
    channel_id: directChannelId,
    props: {
      attachments: [
        {
          pretext: \"Hi there! It's time for standup!\",
          text: `Please provide your input for: **${
            $item(itemIndex).$node['Prep Request Standup'].json['title']
          }**`,
          actions: [
            {
              id: webhookUrl.includes('test') ? 'webhook-test' : 'webhook',
              name: 'Provide Update',
              integration: {
                url: webhookUrl,
                context: {
                  action: 'open-standup-dialog',
                  secret: botUserToken, // not ideal but good enough for now...
                  standupId:
                    $item(itemIndex).$node['Prep Request Standup'].json[
                      'channelId'
                    ],
                },
              },
            },
          ],
        },
      ],
    },
  };

  item.json = payload;

  itemIndex++;
}

return items;

"
      },
      "typeVersion": 1
    },
    {
      "name": "Prep Standup Dialog",
      "type": "n8n-nodes-base.function",
      "position": [
        1400,
        1240
      ],
      "parameters": {
        "functionCode": "const standupId =
  $item(0).$node['Action from MM'].json['body']['context']['standupId'];

const postId = $item(0).$node['Action from MM'].json['body']['post_id'];

const configuredStandups =
  $item(0).$node['Read Config 2'].json['standups'] ?? [];

let standup = configuredStandups.find(
  (standup) => (standup.channelId == standupId)
);

const renderQuestions = (questions) => {
  let questionId = 1;

  return questions.map((question) => ({
    display_name: question,
    name: `q${questionId++}`,
    type: 'textarea',
  }));
};

const payload = {
  trigger_id: $item(0).$node['Action from MM'].json['body']['trigger_id'],
  url: $item(0).$node['Read Config 2'].json['config']['n8nWebhookUrl'],
  dialog: {
    callback_id: 'standup-answers',
    title: `Report for: ${standup.title}`,
    submit_label: 'Submit',
    notify_on_cancel: false,
    state: JSON.stringify({ standupId, reminderPostId: postId }),
    elements: renderQuestions(standup.questions),
  },
};

return [{ json: payload }];
"
      },
      "typeVersion": 1
    },
    {
      "name": "open standup dialog",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1600,
        1240
      ],
      "parameters": {
        "url": "={{$node[\"Read Config 2\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/actions/dialogs/open",
        "options": {
          "bodyContentType": "json"
        },
        "requestMethod": "POST",
        "jsonParameters": true,
        "bodyParametersJson": "={{$json}}"
      },
      "typeVersion": 1
    },
    {
      "name": "Prep Report",
      "type": "n8n-nodes-base.function",
      "position": [
        1620,
        1040
      ],
      "parameters": {
        "functionCode": "const { standupId, reminderPostId } = JSON.parse(
  $item(0).$node['Action from MM'].json['body']['state']
);
const submission = $item(0).$node['Action from MM'].json['body']['submission'];

const configuredStandups = $item(0).$node['Read Config 2'].json['standups'];

const standup = configuredStandups.find(
  (standup) => standup.channelId == standupId
);

const emptyAnswers = [
  '-',
  '/',
  ' ',
  'x',
  'n/a',
  'nope',
  'nopes',
  'no',
  'none',
  'no.',
  'nothing',
];

function capitalize(text) {
  return text.charAt(0).toUpperCase() + text.slice(1);
}

const renderPost = (submission, standup) => {
  let postText = `### ${capitalize(
    $item(0).$node['get user data'].json['username']
  )}\n`;

  let questionIndex = 0;

  postText += standup.questions
    .map((question) => {
      questionIndex++;

      if (
        !submission[`q${questionIndex}`] ||
        emptyAnswers.includes(submission[`q${questionIndex}`].toLowerCase())
      ) {
        return '';
      }

      return `#### ${question}\n${submission[`q${questionIndex}`]}`;
    })
    .join('\n');

  return postText;
};

return [
  {
    json: {
      post: renderPost(submission, standup),
      channel: standupId,
      reminderPostId,
      standupTitle: standup.title,
    },
  },
];

"
      },
      "typeVersion": 1
    },
    {
      "name": "Delete ReminderPost",
      "type": "n8n-nodes-base.mattermost",
      "position": [
        2280,
        1040
      ],
      "parameters": {
        "postId": "={{$node[\"Prep Report\"].json[\"reminderPostId\"]}}",
        "operation": "delete"
      },
      "credentials": {
        "mattermostApi": {
          "id": "2",
          "name": "Mattermost account"
        }
      },
      "typeVersion": 1
    },
    {
      "name": "Update Post",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2060,
        1040
      ],
      "parameters": {
        "url": "={{$node[\"Read Config 2\"].json[\"config\"][\"mattermostBaseUrl\"]}}/api/v4/posts/{{$node[\"Prep Report\"].json[\"reminderPostId\"]}}",
        "options": {},
        "requestMethod": "PUT",
        "jsonParameters": true,
        "bodyParametersJson": "={
\"id\":\"{{$node[\"Prep Report\"].json[\"reminderPostId\"]}}\",
\"message\": \"Thank you for providing your report for {{$node[\"Prep Report\"].json[\"standupTitle\"]}}\"
}",
        "headerParametersJson": "={
\"Content-Type\":\"application/json\",
\"Authorization\": \"Bearer {{$item(0).$node[\"Read Config 2\"].json[\"config\"][\"botUserToken\"]}}\"
}"
      },
      "typeVersion": 1
    },
    {
      "name": "Every hour",
      "type": "n8n-nodes-base.cron",
      "position": [
        520,
        380
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "custom",
              "cronExpression": "0 0 6-12 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {},
  "connections": {
    "config?": {
      "main": [
        [
          {
            "node": "Read Config 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get User": {
      "main": [
        [
          {
            "node": "Create Channel",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every hour": {
      "main": [
        [
          {
            "node": "Read Config 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Report": {
      "main": [
        [
          {
            "node": "publish report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "callback ID?": {
      "main": [
        [
          {
            "node": "standup-config",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "standup-answers",
            "type": "main",
            "index": 0
          }
        ],
        [],
        [
          {
            "node": "open-standup-dialog?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Reminder": {
      "main": [
        [
          {
            "node": "Remind Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config 1": {
      "main": [
        [
          {
            "node": "Prep Config Dialog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config 2": {
      "main": [
        [
          {
            "node": "callback ID?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config 3": {
      "main": [
        [
          {
            "node": "Filter Due Standups",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get user data": {
      "main": [
        [
          {
            "node": "Prep Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Action from MM": {
      "main": [
        [
          {
            "node": "Read Config 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Channel": {
      "main": [
        [
          {
            "node": "Prep Reminder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "publish report": {
      "main": [
        [
          {
            "node": "Update Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "standup-config": {
      "main": [
        [
          {
            "node": "Prep Config Override",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Override Config": {
      "main": [
        [
          {
            "node": "confirm success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "standup-answers": {
      "main": [
        [
          {
            "node": "get user data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slash Cmd from MM": {
      "main": [
        [
          {
            "node": "config?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Config Dialog": {
      "main": [
        [
          {
            "node": "open config dialog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Due Standups": {
      "main": [
        [
          {
            "node": "Prep Request Standup",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Standup Dialog": {
      "main": [
        [
          {
            "node": "open standup dialog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Config Override": {
      "main": [
        [
          {
            "node": "Override Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prep Request Standup": {
      "main": [
        [
          {
            "node": "Get User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "open-standup-dialog?": {
      "main": [
        [
          {
            "node": "Prep Standup Dialog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

功能特点

  • 自动检测新邮件
  • AI智能内容分析
  • 自定义分类规则
  • 批量处理能力
  • 详细的处理日志

技术分析

节点类型及作用

  • Mattermost
  • Httprequest
  • If
  • Webhook
  • Function

复杂度评估

配置难度:
★★★★☆
维护难度:
★★☆☆☆
扩展性:
★★★★☆

实施指南

前置条件

  • 有效的Gmail账户
  • n8n平台访问权限
  • Google API凭证
  • AI分类服务订阅

配置步骤

  1. 在n8n中导入工作流JSON文件
  2. 配置Gmail节点的认证信息
  3. 设置AI分类器的API密钥
  4. 自定义分类规则和标签映射
  5. 测试工作流执行
  6. 配置定时触发器(可选)

关键参数

参数名称 默认值 说明
maxEmails 50 单次处理的最大邮件数量
confidenceThreshold 0.8 分类置信度阈值
autoLabel true 是否自动添加标签

最佳实践

优化建议

  • 定期更新AI分类模型以提高准确性
  • 根据邮件量调整处理批次大小
  • 设置合理的分类置信度阈值
  • 定期清理过期的分类规则

安全注意事项

  • 妥善保管API密钥和认证信息
  • 限制工作流的访问权限
  • 定期审查处理日志
  • 启用双因素认证保护Gmail账户

性能优化

  • 使用增量处理减少重复工作
  • 缓存频繁访问的数据
  • 并行处理多个邮件分类任务
  • 监控系统资源使用情况

故障排除

常见问题

邮件未被正确分类

检查AI分类器的置信度阈值设置,适当降低阈值或更新训练数据。

Gmail认证失败

确认Google API凭证有效且具有正确的权限范围,重新进行OAuth授权。

调试技巧

  • 启用详细日志记录查看每个步骤的执行情况
  • 使用测试邮件验证分类逻辑
  • 检查网络连接和API服务状态
  • 逐步执行工作流定位问题节点

错误处理

工作流包含以下错误处理机制:

  • 网络超时自动重试(最多3次)
  • API错误记录和告警
  • 处理失败邮件的隔离机制
  • 异常情况下的回滚操作