From 540f321fc9685b9238138c3763c23a828ec6646a Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Fri, 28 Mar 2025 18:07:55 +0800 Subject: [PATCH] Test email plugin (#4387) * add email plugin (#4343) * add email plugin * remove console.log --------- Co-authored-by: zhengshuai.li * perf: smtp email --------- Co-authored-by: lzs2000131 Co-authored-by: zhengshuai.li --- packages/plugins/package.json | 2 + packages/plugins/register.ts | 3 +- packages/plugins/src/smtpEmail/index.ts | 122 ++++ packages/plugins/src/smtpEmail/template.json | 651 ++++++++++++++++++ .../web/components/common/Icon/constants.ts | 1 + .../common/Icon/icons/plugins/email.svg | 12 + pnpm-lock.yaml | 19 + 7 files changed, 809 insertions(+), 1 deletion(-) create mode 100644 packages/plugins/src/smtpEmail/index.ts create mode 100644 packages/plugins/src/smtpEmail/template.json create mode 100644 packages/web/components/common/Icon/icons/plugins/email.svg diff --git a/packages/plugins/package.json b/packages/plugins/package.json index e6d65a3b1..5f0d39e16 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -5,6 +5,7 @@ "dependencies": { "cheerio": "1.0.0-rc.12", "@types/pg": "^8.6.6", + "@types/nodemailer": "^6.4.17", "axios": "^1.8.2", "duck-duck-scrape": "^2.2.5", "echarts": "5.4.1", @@ -13,6 +14,7 @@ "mssql": "^11.0.1", "mysql2": "^3.11.3", "json5": "^2.2.3", + "nodemailer": "^6.10.0", "pg": "^8.10.0", "wikijs": "^6.4.1" }, diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 3e00f18a5..37da626cb 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -29,7 +29,8 @@ const packagePluginList = [ 'databaseConnection', 'Doc2X', 'Doc2X/PDF2text', - 'searchXNG' + 'searchXNG', + 'smtpEmail' ]; export const list = [...staticPluginList, ...packagePluginList]; diff --git a/packages/plugins/src/smtpEmail/index.ts b/packages/plugins/src/smtpEmail/index.ts new file mode 100644 index 000000000..f08afe073 --- /dev/null +++ b/packages/plugins/src/smtpEmail/index.ts @@ -0,0 +1,122 @@ +import { getErrText } from '@fastgpt/global/common/error/utils'; +import nodemailer from 'nodemailer'; + +interface Props { + // SMTP配置 + smtpHost: string; + smtpPort: string; + SSL: boolean; + smtpUser: string; + smtpPass: string; + fromName?: string; + // 邮件参数 + to: string; + subject: string; + content: string; + cc?: string; + bcc?: string; + attachments?: string; +} + +interface Response { + success: boolean; + messageId?: string; + error?: string; +} + +const validateEmail = (email: string) => { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +}; + +const validateEmails = (emails: string) => { + return emails.split(',').every((email) => validateEmail(email.trim())); +}; + +const main = async ({ + smtpHost, + smtpPort, + SSL, + smtpUser, + smtpPass, + fromName, + to, + subject, + content, + cc, + bcc, + attachments +}: Props): Promise => { + try { + // 验证SMTP配置 + if (!smtpHost || !smtpPort || !smtpUser || !smtpPass) { + throw new Error('Incomplete SMTP configuration'); + } + + // 验证必填参数 + if (!to || !subject || !content) { + throw new Error('Recipient, subject, and content are required'); + } + + // 验证邮箱格式 + if (!validateEmails(to)) { + throw new Error('Invalid recipient email format'); + } + if (cc && !validateEmails(cc)) { + throw new Error('Invalid CC email format'); + } + if (bcc && !validateEmails(bcc)) { + throw new Error('Invalid BCC email format'); + } + + // 创建SMTP传输对象 + const transporter = nodemailer.createTransport({ + host: smtpHost, + port: Number(smtpPort), + secure: SSL === true, + auth: { + user: smtpUser, + pass: smtpPass + } + }); + + let attachmentsArray = []; + try { + attachmentsArray = JSON.parse(attachments || '[]'); + } catch (error) { + throw new Error('Attachment format parsing error, please check attachment configuration'); + } + + // 发送邮件 + const info = await transporter.sendMail({ + from: `"${fromName || 'FastGPT'}" <${smtpUser}>`, + to: to + .split(',') + .map((email) => email.trim()) + .join(','), + cc: cc + ?.split(',') + .map((email) => email.trim()) + .join(','), + bcc: bcc + ?.split(',') + .map((email) => email.trim()) + .join(','), + subject, + html: content, + attachments: attachmentsArray || [] + }); + + return { + success: true, + messageId: info.messageId + }; + } catch (error: any) { + return { + success: false, + error: getErrText(error) + }; + } +}; + +export default main; diff --git a/packages/plugins/src/smtpEmail/template.json b/packages/plugins/src/smtpEmail/template.json new file mode 100644 index 000000000..1af9d2d8d --- /dev/null +++ b/packages/plugins/src/smtpEmail/template.json @@ -0,0 +1,651 @@ +{ + "author": "cloudpense", + "version": "1.0.0", + "name": "Email 邮件发送", + "avatar": "plugins/email", + "intro": "通过SMTP协议发送电子邮件(nodemailer)", + "showStatus": true, + "weight": 10, + "isTool": true, + "templateType": "tools", + + "workflow": { + "nodes": [ + { + "nodeId": "pluginInput", + "name": "workflow:template.plugin_start", + "intro": "workflow:intro_plugin_input", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "pluginInput", + "showStatus": false, + "position": { + "x": 595.3456736313964, + "y": -323.02524442647456 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "smtpHost", + "label": "smtpHost", + "description": "", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true, + "customInputConfig": { + "selectValueTypeList": ["string"] + } + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "smtpPort", + "label": "smtpPort", + "description": "SMTP端口", + "defaultValue": "465", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true + }, + { + "renderTypeList": ["select", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "SSL", + "label": "SSL", + "description": "SSL", + "defaultValue": "true", + "list": [ + { + "label": "true", + "value": "true" + }, + { + "label": "false", + "value": "false" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "smtpUser", + "label": "smtpUser", + "description": "SMTP用户名, 邮箱账号", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "smtpPass", + "label": "smtpPass", + "description": "邮箱密码或授权码", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "fromName", + "label": "fromName", + "description": "显示的发件人名称", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "to", + "label": "to", + "description": "请输入收件人邮箱,多个邮箱用逗号分隔", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true, + "toolDescription": "请输入收件人邮箱,多个邮箱用逗号分隔" + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "subject", + "label": "subject", + "description": "请输入邮件主题", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true, + "toolDescription": "请输入邮件主题" + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "content", + "label": "content", + "description": "请输入邮件内容,支持HTML格式", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true, + "toolDescription": "请输入邮件内容,支持HTML格式" + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "cc", + "label": "cc", + "description": "请输入抄送邮箱,多个邮箱用逗号分隔", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": false, + "toolDescription": "请输入抄送邮箱,多个邮箱用逗号分隔" + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "bcc", + "label": "bcc", + "description": "请输入密送邮箱,多个邮箱用逗号分隔", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": false, + "toolDescription": "请输入密送邮箱,多个邮箱用逗号分隔" + }, + { + "renderTypeList": ["JSONEditor", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "attachments", + "label": "attachments", + "description": "必须是json数组格式\n[{\"filename\":\"附件名\",\"path\":\"附件url\"}]", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": false, + "customInputConfig": { + "selectValueTypeList": ["arrayObject"] + }, + "toolDescription": "必须是json数组格式\n[{\"filename\":\"附件名\",\"path\":\"附件url\"}]", + "maxLength": 0 + } + ], + "outputs": [ + { + "id": "smtpHost", + "valueType": "string", + "key": "smtpHost", + "label": "smtpHost", + "type": "hidden" + }, + { + "id": "smtpPort", + "valueType": "string", + "key": "smtpPort", + "label": "smtpPort", + "type": "hidden" + }, + { + "id": "SSL", + "valueType": "string", + "key": "SSL", + "label": "SSL", + "type": "hidden" + }, + { + "id": "smtpUser", + "valueType": "string", + "key": "smtpUser", + "label": "smtpUser", + "type": "hidden" + }, + { + "id": "smtpPass", + "valueType": "string", + "key": "smtpPass", + "label": "smtpPass", + "type": "hidden" + }, + { + "id": "fromName", + "valueType": "string", + "key": "fromName", + "label": "fromName", + "type": "hidden" + }, + { + "id": "to", + "valueType": "string", + "key": "to", + "label": "to", + "type": "hidden" + }, + { + "id": "subject", + "valueType": "string", + "key": "subject", + "label": "subject", + "type": "hidden" + }, + { + "id": "content", + "valueType": "string", + "key": "content", + "label": "content", + "type": "hidden" + }, + { + "id": "cc", + "valueType": "string", + "key": "cc", + "label": "cc", + "type": "hidden" + }, + { + "id": "bcc", + "valueType": "string", + "key": "bcc", + "label": "bcc", + "type": "hidden" + }, + { + "id": "attachments", + "valueType": "string", + "key": "attachments", + "label": "attachments", + "type": "hidden" + } + ] + }, + { + "nodeId": "pluginOutput", + "name": "common:core.module.template.self_output", + "intro": "workflow:intro_custom_plugin_output", + "avatar": "core/workflow/template/pluginOutput", + "flowNodeType": "pluginOutput", + "showStatus": false, + "position": { + "x": 2135.4991928806685, + "y": -98.02524442647456 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "发送结果", + "label": "发送结果", + "isToolOutput": true, + "description": "", + "required": true, + "value": ["uOX6ITvPWm9O", "httpRawResponse"] + } + ], + "outputs": [] + }, + { + "nodeId": "pluginConfig", + "name": "common:core.module.template.system_config", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "pluginConfig", + "position": { + "x": 184.66337662472682, + "y": -216.05298493910115 + }, + "version": "4811", + "inputs": [], + "outputs": [] + }, + { + "nodeId": "uOX6ITvPWm9O", + "name": "HTTP 请求", + "intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1340.0519095857342, + "y": -393.02524442647456 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectDataset", + "selectApp" + ], + "showDescription": false, + "showDefaultValue": true + }, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "POST", + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "smtpEmail", + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "valueDesc": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "{\n\"smtpHost\": \"{{$pluginInput.smtpHost$}}\",\n\"smtpPort\": \"{{$pluginInput.smtpPort$}}\",\n\"SSL\": {{$pluginInput.SSL$}},\n\"smtpUser\": \"{{$pluginInput.smtpUser$}}\",\n\"smtpPass\": \"{{$pluginInput.smtpPass$}}\",\n\"fromName\": \"{{$pluginInput.fromName$}}\",\n\"to\": \"{{$pluginInput.to$}}\",\n\"subject\": \"{{$pluginInput.subject$}}\",\n\"content\": \"{{$pluginInput.content$}}\",\n\"cc\": \"{{$pluginInput.cc$}}\",\n\"bcc\": \"{{$pluginInput.bcc$}}\",\n\"attachments\":'{{$pluginInput.attachments$}}'\n}", + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "valueDesc": "", + "description": "", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP请求错误信息,成功时返回空", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "输出字段提取", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + }, + "description": "可以通过 JSONPath 语法来提取响应值中的指定字段", + "valueDesc": "" + } + ] + } + ], + "edges": [ + { + "source": "uOX6ITvPWm9O", + "target": "pluginOutput", + "sourceHandle": "uOX6ITvPWm9O-source-right", + "targetHandle": "pluginOutput-target-left" + }, + { + "source": "pluginInput", + "target": "uOX6ITvPWm9O", + "sourceHandle": "pluginInput-source-right", + "targetHandle": "uOX6ITvPWm9O-target-left" + } + ], + "chatConfig": { + "welcomeText": "", + "variables": [], + "questionGuide": { + "open": false, + "model": "gpt-4o-mini", + "customPrompt": "" + }, + "ttsConfig": { + "type": "web" + }, + "whisperConfig": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + }, + "chatInputGuide": { + "open": false, + "textList": [], + "customUrl": "" + }, + "instruction": "通过SMTP协议发送电子邮件", + "autoExecute": { + "open": false, + "defaultPrompt": "" + }, + "_id": "67ad649ea4b6b8eefa9d3d0d" + } + } +} diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 713ce7455..65817717f 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -423,6 +423,7 @@ export const iconPaths = { 'plugins/dingding': () => import('./icons/plugins/dingding.svg'), 'plugins/doc2x': () => import('./icons/plugins/doc2x.svg'), 'plugins/qiwei': () => import('./icons/plugins/qiwei.svg'), + 'plugins/email': () => import('./icons/plugins/email.svg'), 'plugins/textEditor': () => import('./icons/plugins/textEditor.svg'), point: () => import('./icons/point.svg'), preview: () => import('./icons/preview.svg'), diff --git a/packages/web/components/common/Icon/icons/plugins/email.svg b/packages/web/components/common/Icon/icons/plugins/email.svg new file mode 100644 index 000000000..32b1454df --- /dev/null +++ b/packages/web/components/common/Icon/icons/plugins/email.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03cf11bbb..9ba6886bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: packages/plugins: dependencies: + '@types/nodemailer': + specifier: ^6.4.17 + version: 6.4.17 '@types/pg': specifier: ^8.6.6 version: 8.11.11 @@ -126,6 +129,9 @@ importers: mysql2: specifier: ^3.11.3 version: 3.13.0 + nodemailer: + specifier: ^6.10.0 + version: 6.10.0 pg: specifier: ^8.10.0 version: 8.14.0 @@ -3301,6 +3307,9 @@ packages: '@types/node@20.17.24': resolution: {integrity: sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==} + '@types/nodemailer@6.4.17': + resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} + '@types/nprogress@0.2.3': resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} @@ -7267,6 +7276,10 @@ packages: engines: {node: '>=10.0.0'} hasBin: true + nodemailer@6.10.0: + resolution: {integrity: sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==} + engines: {node: '>=6.0.0'} + non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} @@ -12513,6 +12526,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/nodemailer@6.4.17': + dependencies: + '@types/node': 20.17.24 + '@types/nprogress@0.2.3': {} '@types/papaparse@5.3.7': @@ -17630,6 +17647,8 @@ snapshots: dependencies: xlsx: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz + nodemailer@6.10.0: {} + non-layered-tidy-tree-layout@2.0.2: {} nopt@5.0.0: