@nocobase/plugin-workflow-mailer 2.1.0-beta.37 → 2.1.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/externalVersion.js
CHANGED
|
@@ -11,9 +11,9 @@ module.exports = {
|
|
|
11
11
|
"@ant-design/icons": "5.6.1",
|
|
12
12
|
"@formily/antd-v5": "1.2.3",
|
|
13
13
|
"react": "18.2.0",
|
|
14
|
-
"@nocobase/client": "2.1.0-beta.
|
|
15
|
-
"@nocobase/plugin-workflow": "2.1.0-beta.
|
|
14
|
+
"@nocobase/client": "2.1.0-beta.40",
|
|
15
|
+
"@nocobase/plugin-workflow": "2.1.0-beta.40",
|
|
16
16
|
"react-i18next": "11.18.6",
|
|
17
17
|
"lodash": "4.18.1",
|
|
18
|
-
"@nocobase/server": "2.1.0-beta.
|
|
18
|
+
"@nocobase/server": "2.1.0-beta.40"
|
|
19
19
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-05-
|
|
1
|
+
{"name":"joi","description":"Object schema validation","version":"17.13.3","repository":"git://github.com/hapijs/joi","main":"lib/index.js","types":"lib/index.d.ts","browser":"dist/joi-browser.min.js","files":["lib/**/*","dist/*"],"keywords":["schema","validation"],"dependencies":{"@hapi/hoek":"^9.3.0","@hapi/topo":"^5.1.0","@sideway/address":"^4.1.5","@sideway/formula":"^3.0.1","@sideway/pinpoint":"^2.0.0"},"devDependencies":{"@hapi/bourne":"2.x.x","@hapi/code":"8.x.x","@hapi/joi-legacy-test":"npm:@hapi/joi@15.x.x","@hapi/lab":"^25.1.3","@types/node":"^14.18.63","typescript":"4.3.x"},"scripts":{"prepublishOnly":"cd browser && npm install && npm run build","test":"lab -t 100 -a @hapi/code -L -Y","test-cov-html":"lab -r html -o coverage.html -a @hapi/code"},"license":"BSD-3-Clause","_lastModified":"2026-05-30T00:02:02.693Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"nodemailer","version":"6.9.13","description":"Easy as cake e-mail sending from your Node.js applications","main":"lib/nodemailer.js","scripts":{"test":"node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","test:coverage":"c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","lint":"eslint .","update":"rm -rf node_modules/ package-lock.json && ncu -u && npm install"},"repository":{"type":"git","url":"https://github.com/nodemailer/nodemailer.git"},"keywords":["Nodemailer"],"author":"Andris Reinman","license":"MIT-0","bugs":{"url":"https://github.com/nodemailer/nodemailer/issues"},"homepage":"https://nodemailer.com/","devDependencies":{"@aws-sdk/client-ses":"3.529.1","bunyan":"1.8.15","c8":"9.1.0","eslint":"8.57.0","eslint-config-nodemailer":"1.2.0","eslint-config-prettier":"9.1.0","libbase64":"1.3.0","libmime":"5.3.4","libqp":"2.1.0","nodemailer-ntlm-auth":"1.0.4","proxy":"1.0.2","proxy-test-server":"1.0.0","smtp-server":"3.13.3"},"engines":{"node":">=6.0.0"},"_lastModified":"2026-05-
|
|
1
|
+
{"name":"nodemailer","version":"6.9.13","description":"Easy as cake e-mail sending from your Node.js applications","main":"lib/nodemailer.js","scripts":{"test":"node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","test:coverage":"c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js","lint":"eslint .","update":"rm -rf node_modules/ package-lock.json && ncu -u && npm install"},"repository":{"type":"git","url":"https://github.com/nodemailer/nodemailer.git"},"keywords":["Nodemailer"],"author":"Andris Reinman","license":"MIT-0","bugs":{"url":"https://github.com/nodemailer/nodemailer/issues"},"homepage":"https://nodemailer.com/","devDependencies":{"@aws-sdk/client-ses":"3.529.1","bunyan":"1.8.15","c8":"9.1.0","eslint":"8.57.0","eslint-config-nodemailer":"1.2.0","eslint-config-prettier":"9.1.0","libbase64":"1.3.0","libmime":"5.3.4","libqp":"2.1.0","nodemailer-ntlm-auth":"1.0.4","proxy":"1.0.2","proxy-test-server":"1.0.0","smtp-server":"3.13.3"},"engines":{"node":">=6.0.0"},"_lastModified":"2026-05-30T00:02:03.425Z"}
|
|
@@ -7,18 +7,11 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import Joi from 'joi';
|
|
10
|
-
import { FlowNodeModel, Instruction, Processor } from '@nocobase/plugin-workflow';
|
|
10
|
+
import { FlowNodeModel, Instruction, InstructionResult, JobModel, Processor } from '@nocobase/plugin-workflow';
|
|
11
11
|
export default class MailerInstruction extends Instruction {
|
|
12
12
|
configSchema: Joi.ObjectSchema<any>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
private createNewTransporter;
|
|
18
|
-
private getTransporter;
|
|
19
|
-
run(node: FlowNodeModel, prevJob: any, processor: Processor): Promise<{
|
|
20
|
-
status: 1 | -1;
|
|
21
|
-
result: any;
|
|
22
|
-
}>;
|
|
23
|
-
resume(node: FlowNodeModel, job: any, processor: Processor): Promise<any>;
|
|
13
|
+
run(node: FlowNodeModel, prevJob: JobModel, processor: Processor, options?: {
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}): Promise<InstructionResult>;
|
|
16
|
+
resume(node: FlowNodeModel, job: JobModel, processor: Processor): Promise<JobModel>;
|
|
24
17
|
}
|
|
@@ -40,10 +40,127 @@ __export(MailerInstruction_exports, {
|
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(MailerInstruction_exports);
|
|
42
42
|
var import_joi = __toESM(require("joi"));
|
|
43
|
-
var import_util = require("util");
|
|
44
43
|
var import_nodemailer = __toESM(require("nodemailer"));
|
|
45
44
|
var import_plugin_workflow = require("@nocobase/plugin-workflow");
|
|
46
45
|
var import_get = __toESM(require("lodash/get"));
|
|
46
|
+
const transporterMap = /* @__PURE__ */ new Map();
|
|
47
|
+
const configMap = /* @__PURE__ */ new Map();
|
|
48
|
+
function getTransporterKey(provider) {
|
|
49
|
+
const { host, port, auth } = provider;
|
|
50
|
+
return `${host}:${port}:${auth == null ? void 0 : auth.user}`;
|
|
51
|
+
}
|
|
52
|
+
function isConfigChanged(oldConfig, newConfig) {
|
|
53
|
+
const fields = ["host", "port", "secure", "auth.user", "auth.pass"];
|
|
54
|
+
return fields.some((key) => (0, import_get.default)(oldConfig, key) !== (0, import_get.default)(newConfig, key));
|
|
55
|
+
}
|
|
56
|
+
function createNewTransporter(key, config) {
|
|
57
|
+
const transporter = import_nodemailer.default.createTransport(config);
|
|
58
|
+
transporterMap.set(key, transporter);
|
|
59
|
+
configMap.set(key, config);
|
|
60
|
+
return transporter;
|
|
61
|
+
}
|
|
62
|
+
function getTransporter(provider) {
|
|
63
|
+
const key = getTransporterKey(provider);
|
|
64
|
+
const newConfig = provider;
|
|
65
|
+
const oldConfig = configMap.get(key);
|
|
66
|
+
if (!oldConfig) {
|
|
67
|
+
return createNewTransporter(key, newConfig);
|
|
68
|
+
}
|
|
69
|
+
if (isConfigChanged(oldConfig, newConfig)) {
|
|
70
|
+
const oldTransporter = transporterMap.get(key);
|
|
71
|
+
if (oldTransporter) {
|
|
72
|
+
oldTransporter.close();
|
|
73
|
+
}
|
|
74
|
+
return createNewTransporter(key, newConfig);
|
|
75
|
+
}
|
|
76
|
+
return transporterMap.get(key);
|
|
77
|
+
}
|
|
78
|
+
function discardTransporter(key, transporter) {
|
|
79
|
+
transporter.close();
|
|
80
|
+
if (transporterMap.get(key) === transporter) {
|
|
81
|
+
transporterMap.delete(key);
|
|
82
|
+
configMap.delete(key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function createAbortSignal(processor, signal, timeout = 0) {
|
|
86
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
87
|
+
const abortController = new AbortController();
|
|
88
|
+
let timeoutTimer = null;
|
|
89
|
+
let abortListener = null;
|
|
90
|
+
const abort = (reason) => {
|
|
91
|
+
if (abortController.signal.aborted) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
abortController.abort(reason instanceof Error ? reason : new import_plugin_workflow.WorkflowTimeoutError());
|
|
95
|
+
};
|
|
96
|
+
if (signal == null ? void 0 : signal.aborted) {
|
|
97
|
+
abort(signal.reason);
|
|
98
|
+
} else {
|
|
99
|
+
abortListener = () => abort(signal == null ? void 0 : signal.reason);
|
|
100
|
+
signal == null ? void 0 : signal.addEventListener("abort", abortListener, { once: true });
|
|
101
|
+
}
|
|
102
|
+
const executionWorkflow = processor.execution.workflow ?? ((_b = (_a = processor.execution).get) == null ? void 0 : _b.call(_a, "workflow"));
|
|
103
|
+
const executionWorkflowOptions = (executionWorkflow == null ? void 0 : executionWorkflow.options) ?? ((_c = executionWorkflow == null ? void 0 : executionWorkflow.get) == null ? void 0 : _c.call(executionWorkflow, "options")) ?? {};
|
|
104
|
+
const workflowTimeout = Number(timeout || executionWorkflowOptions.timeout || 0);
|
|
105
|
+
const expiresAt = processor.execution.expiresAt ?? ((_e = (_d = processor.execution).get) == null ? void 0 : _e.call(_d, "expiresAt"));
|
|
106
|
+
const startedAt = processor.execution.startedAt ?? ((_g = (_f = processor.execution).get) == null ? void 0 : _g.call(_f, "startedAt"));
|
|
107
|
+
const remaining = expiresAt ? new Date(expiresAt).getTime() - Date.now() : workflowTimeout > 0 && startedAt ? new Date(startedAt).getTime() + workflowTimeout - Date.now() : workflowTimeout > 0 ? workflowTimeout : null;
|
|
108
|
+
if (remaining != null) {
|
|
109
|
+
if (remaining <= 0) {
|
|
110
|
+
abort(new import_plugin_workflow.WorkflowTimeoutError());
|
|
111
|
+
} else {
|
|
112
|
+
timeoutTimer = setTimeout(() => abort(new import_plugin_workflow.WorkflowTimeoutError()), remaining);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
signal: abortController.signal,
|
|
117
|
+
cleanup: () => {
|
|
118
|
+
if (timeoutTimer) {
|
|
119
|
+
clearTimeout(timeoutTimer);
|
|
120
|
+
}
|
|
121
|
+
if (signal && abortListener) {
|
|
122
|
+
signal.removeEventListener("abort", abortListener);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function sendMail(transporter, transporterKey, payload, signal) {
|
|
128
|
+
if (signal == null ? void 0 : signal.aborted) {
|
|
129
|
+
discardTransporter(transporterKey, transporter);
|
|
130
|
+
return Promise.reject(signal.reason);
|
|
131
|
+
}
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
let finished = false;
|
|
134
|
+
const cleanup = () => {
|
|
135
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
136
|
+
};
|
|
137
|
+
const done = (error, result) => {
|
|
138
|
+
if (finished) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
finished = true;
|
|
142
|
+
cleanup();
|
|
143
|
+
if (error) {
|
|
144
|
+
reject(error);
|
|
145
|
+
} else {
|
|
146
|
+
resolve(result);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const onAbort = () => {
|
|
150
|
+
if (finished) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
discardTransporter(transporterKey, transporter);
|
|
154
|
+
done((signal == null ? void 0 : signal.reason) instanceof Error ? signal.reason : new import_plugin_workflow.WorkflowTimeoutError());
|
|
155
|
+
};
|
|
156
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort, { once: true });
|
|
157
|
+
try {
|
|
158
|
+
transporter.sendMail(payload, done);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
done(error);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
47
164
|
class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
48
165
|
configSchema = import_joi.default.object({
|
|
49
166
|
provider: import_joi.default.object({
|
|
@@ -65,39 +182,8 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
|
65
182
|
text: import_joi.default.string(),
|
|
66
183
|
ignoreFail: import_joi.default.boolean().default(false)
|
|
67
184
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
getTransporterKey(provider) {
|
|
71
|
-
const { host, port, auth } = provider;
|
|
72
|
-
return `${host}:${port}:${auth == null ? void 0 : auth.user}`;
|
|
73
|
-
}
|
|
74
|
-
isConfigChanged(oldConfig, newConfig) {
|
|
75
|
-
const fields = ["host", "port", "secure", "auth.user", "auth.pass"];
|
|
76
|
-
return fields.some((key) => (0, import_get.default)(oldConfig, key) !== (0, import_get.default)(newConfig, key));
|
|
77
|
-
}
|
|
78
|
-
createNewTransporter(key, config) {
|
|
79
|
-
const transporter = import_nodemailer.default.createTransport(config);
|
|
80
|
-
MailerInstruction.transporterMap.set(key, transporter);
|
|
81
|
-
MailerInstruction.configMap.set(key, config);
|
|
82
|
-
return transporter;
|
|
83
|
-
}
|
|
84
|
-
getTransporter(provider) {
|
|
85
|
-
const key = this.getTransporterKey(provider);
|
|
86
|
-
const newConfig = provider;
|
|
87
|
-
const oldConfig = MailerInstruction.configMap.get(key);
|
|
88
|
-
if (!oldConfig) {
|
|
89
|
-
return this.createNewTransporter(key, newConfig);
|
|
90
|
-
}
|
|
91
|
-
if (this.isConfigChanged(oldConfig, newConfig)) {
|
|
92
|
-
const oldTransporter = MailerInstruction.transporterMap.get(key);
|
|
93
|
-
if (oldTransporter) {
|
|
94
|
-
oldTransporter.close();
|
|
95
|
-
}
|
|
96
|
-
return this.createNewTransporter(key, newConfig);
|
|
97
|
-
}
|
|
98
|
-
return MailerInstruction.transporterMap.get(key);
|
|
99
|
-
}
|
|
100
|
-
async run(node, prevJob, processor) {
|
|
185
|
+
async run(node, prevJob, processor, options) {
|
|
186
|
+
var _a, _b;
|
|
101
187
|
const {
|
|
102
188
|
provider,
|
|
103
189
|
contentType,
|
|
@@ -108,32 +194,41 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
|
108
194
|
html,
|
|
109
195
|
text,
|
|
110
196
|
ignoreFail,
|
|
111
|
-
...
|
|
197
|
+
...others
|
|
112
198
|
} = processor.getParsedValue(node.config, node.id);
|
|
113
199
|
const { workflow } = processor.execution;
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const
|
|
200
|
+
const currentWorkflow = (workflow == null ? void 0 : workflow.options) || ((_a = workflow == null ? void 0 : workflow.get) == null ? void 0 : _a.call(workflow, "options")) ? workflow : await node.getWorkflow();
|
|
201
|
+
const sync = this.workflow.isWorkflowSync(currentWorkflow);
|
|
202
|
+
const workflowOptions = (currentWorkflow == null ? void 0 : currentWorkflow.options) ?? ((_b = currentWorkflow == null ? void 0 : currentWorkflow.get) == null ? void 0 : _b.call(currentWorkflow, "options")) ?? {};
|
|
203
|
+
const workflowTimeout = Number(workflowOptions.timeout ?? 0);
|
|
204
|
+
const transporterKey = getTransporterKey(provider);
|
|
205
|
+
const transporter = getTransporter(provider);
|
|
206
|
+
const mailAbort = createAbortSignal(processor, options == null ? void 0 : options.signal, workflowTimeout);
|
|
117
207
|
const payload = {
|
|
118
|
-
...
|
|
208
|
+
...others,
|
|
119
209
|
...contentType === "html" ? { html } : { text },
|
|
120
210
|
subject: subject == null ? void 0 : subject.trim(),
|
|
121
211
|
to: to ? to.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : void 0,
|
|
122
|
-
cc: cc ? cc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) :
|
|
123
|
-
bcc: bcc ? bcc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) :
|
|
212
|
+
cc: cc ? cc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : [],
|
|
213
|
+
bcc: bcc ? bcc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : []
|
|
124
214
|
};
|
|
125
215
|
if (sync) {
|
|
126
216
|
try {
|
|
127
|
-
const result = await
|
|
217
|
+
const result = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
|
|
128
218
|
return {
|
|
129
219
|
status: import_plugin_workflow.JOB_STATUS.RESOLVED,
|
|
130
220
|
result
|
|
131
221
|
};
|
|
132
222
|
} catch (error) {
|
|
223
|
+
if (mailAbort.signal.aborted) {
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
133
226
|
return {
|
|
134
227
|
status: ignoreFail ? import_plugin_workflow.JOB_STATUS.RESOLVED : import_plugin_workflow.JOB_STATUS.FAILED,
|
|
135
228
|
result: error
|
|
136
229
|
};
|
|
230
|
+
} finally {
|
|
231
|
+
mailAbort.cleanup();
|
|
137
232
|
}
|
|
138
233
|
}
|
|
139
234
|
const { id } = processor.saveJob({
|
|
@@ -145,26 +240,40 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
|
145
240
|
await processor.exit();
|
|
146
241
|
const jobDone = { status: import_plugin_workflow.JOB_STATUS.PENDING };
|
|
147
242
|
try {
|
|
148
|
-
const response = await
|
|
243
|
+
const response = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
|
|
149
244
|
processor.logger.info(`smtp-mailer (#${node.id}) sent successfully.`);
|
|
150
245
|
jobDone.status = import_plugin_workflow.JOB_STATUS.RESOLVED;
|
|
151
246
|
jobDone.result = response;
|
|
152
247
|
} catch (error) {
|
|
153
248
|
processor.logger.warn(`smtp-mailer (#${node.id}) sent failed: ${error.message}`);
|
|
154
|
-
jobDone.status = import_plugin_workflow.JOB_STATUS.FAILED;
|
|
249
|
+
jobDone.status = mailAbort.signal.aborted ? import_plugin_workflow.JOB_STATUS.ABORTED : import_plugin_workflow.JOB_STATUS.FAILED;
|
|
155
250
|
jobDone.result = error;
|
|
156
251
|
} finally {
|
|
252
|
+
mailAbort.cleanup();
|
|
157
253
|
processor.logger.debug(`smtp-mailer (#${node.id}) sending ended, resume workflow...`);
|
|
158
254
|
const job = await this.workflow.app.db.getRepository("jobs").findOne({
|
|
159
255
|
filterByTk: id
|
|
160
256
|
});
|
|
161
|
-
job.
|
|
162
|
-
this.workflow.
|
|
257
|
+
const execution = await job.getExecution();
|
|
258
|
+
const aborted = await this.workflow.abortExecutionIfExpired(execution);
|
|
259
|
+
if (!aborted) {
|
|
260
|
+
await execution.reload();
|
|
261
|
+
await job.reload();
|
|
262
|
+
}
|
|
263
|
+
if (!aborted && execution.status === import_plugin_workflow.EXECUTION_STATUS.STARTED && job.status === import_plugin_workflow.JOB_STATUS.PENDING) {
|
|
264
|
+
job.set(jobDone);
|
|
265
|
+
job.execution = execution;
|
|
266
|
+
this.workflow.resume(job);
|
|
267
|
+
} else {
|
|
268
|
+
processor.logger.warn(
|
|
269
|
+
`smtp-mailer (#${node.id}) result discarded because execution (${execution.id}) is ended`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
163
272
|
}
|
|
164
273
|
}
|
|
165
274
|
async resume(node, job, processor) {
|
|
166
275
|
const { ignoreFail } = node.config;
|
|
167
|
-
if (ignoreFail) {
|
|
276
|
+
if (ignoreFail && job.status !== import_plugin_workflow.JOB_STATUS.ABORTED) {
|
|
168
277
|
job.set("status", import_plugin_workflow.JOB_STATUS.RESOLVED);
|
|
169
278
|
}
|
|
170
279
|
return job;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Send email in workflow.",
|
|
7
7
|
"description.ru-RU": "Отправляет электронное письмо в рамках рабочего процесса.",
|
|
8
8
|
"description.zh-CN": "可用于在工作流中发送电子邮件。",
|
|
9
|
-
"version": "2.1.0-beta.
|
|
9
|
+
"version": "2.1.0-beta.40",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/workflow-smtp-mailer",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@nocobase/server": "2.x",
|
|
26
26
|
"@nocobase/test": "2.x"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "36e906138f6305723abbef676a61006058feb5ea",
|
|
29
29
|
"keywords": [
|
|
30
30
|
"NocoBase",
|
|
31
31
|
"Workflow",
|