@nocobase/plugin-workflow-mailer 2.1.0-beta.8 → 2.1.0
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/client/index.js +1 -1
- package/dist/externalVersion.js +4 -4
- package/dist/node_modules/joi/dist/joi-browser.min.js +1 -0
- package/dist/node_modules/joi/lib/annotate.js +175 -0
- package/dist/node_modules/joi/lib/base.js +1069 -0
- package/dist/node_modules/joi/lib/cache.js +143 -0
- package/dist/node_modules/joi/lib/common.js +216 -0
- package/dist/node_modules/joi/lib/compile.js +283 -0
- package/dist/node_modules/joi/lib/errors.js +271 -0
- package/dist/node_modules/joi/lib/extend.js +312 -0
- package/dist/node_modules/joi/lib/index.d.ts +2365 -0
- package/dist/node_modules/joi/lib/index.js +1 -0
- package/dist/node_modules/joi/lib/manifest.js +476 -0
- package/dist/node_modules/joi/lib/messages.js +178 -0
- package/dist/node_modules/joi/lib/modify.js +267 -0
- package/dist/node_modules/joi/lib/ref.js +414 -0
- package/dist/node_modules/joi/lib/schemas.js +302 -0
- package/dist/node_modules/joi/lib/state.js +166 -0
- package/dist/node_modules/joi/lib/template.js +463 -0
- package/dist/node_modules/joi/lib/trace.js +346 -0
- package/dist/node_modules/joi/lib/types/alternatives.js +364 -0
- package/dist/node_modules/joi/lib/types/any.js +174 -0
- package/dist/node_modules/joi/lib/types/array.js +809 -0
- package/dist/node_modules/joi/lib/types/binary.js +100 -0
- package/dist/node_modules/joi/lib/types/boolean.js +150 -0
- package/dist/node_modules/joi/lib/types/date.js +233 -0
- package/dist/node_modules/joi/lib/types/function.js +93 -0
- package/dist/node_modules/joi/lib/types/keys.js +1067 -0
- package/dist/node_modules/joi/lib/types/link.js +168 -0
- package/dist/node_modules/joi/lib/types/number.js +363 -0
- package/dist/node_modules/joi/lib/types/object.js +22 -0
- package/dist/node_modules/joi/lib/types/string.js +850 -0
- package/dist/node_modules/joi/lib/types/symbol.js +102 -0
- package/dist/node_modules/joi/lib/validator.js +750 -0
- package/dist/node_modules/joi/lib/values.js +263 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.d.ts +60 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/lib/index.js +225 -0
- package/dist/node_modules/joi/node_modules/@hapi/topo/package.json +30 -0
- package/dist/node_modules/joi/package.json +1 -0
- package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
- package/dist/node_modules/nodemailer/package.json +1 -1
- package/dist/server/MailerInstruction.d.ts +7 -12
- package/dist/server/MailerInstruction.js +171 -41
- package/package.json +3 -2
|
@@ -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-
|
|
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-06-10T23:42:54.264Z"}
|
|
@@ -6,17 +6,12 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import
|
|
9
|
+
import Joi from 'joi';
|
|
10
|
+
import { FlowNodeModel, Instruction, InstructionResult, JobModel, Processor } from '@nocobase/plugin-workflow';
|
|
10
11
|
export default class MailerInstruction extends Instruction {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
private getTransporter;
|
|
17
|
-
run(node: FlowNodeModel, prevJob: any, processor: Processor): Promise<{
|
|
18
|
-
status: 1 | -1;
|
|
19
|
-
result: any;
|
|
20
|
-
}>;
|
|
21
|
-
resume(node: FlowNodeModel, job: any, processor: Processor): Promise<any>;
|
|
12
|
+
configSchema: Joi.ObjectSchema<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>;
|
|
22
17
|
}
|
|
@@ -39,44 +39,151 @@ __export(MailerInstruction_exports, {
|
|
|
39
39
|
default: () => MailerInstruction
|
|
40
40
|
});
|
|
41
41
|
module.exports = __toCommonJS(MailerInstruction_exports);
|
|
42
|
-
var
|
|
42
|
+
var import_joi = __toESM(require("joi"));
|
|
43
43
|
var import_nodemailer = __toESM(require("nodemailer"));
|
|
44
44
|
var import_plugin_workflow = require("@nocobase/plugin-workflow");
|
|
45
45
|
var import_get = __toESM(require("lodash/get"));
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
|
52
68
|
}
|
|
53
|
-
isConfigChanged(oldConfig, newConfig) {
|
|
54
|
-
const
|
|
55
|
-
|
|
69
|
+
if (isConfigChanged(oldConfig, newConfig)) {
|
|
70
|
+
const oldTransporter = transporterMap.get(key);
|
|
71
|
+
if (oldTransporter) {
|
|
72
|
+
oldTransporter.close();
|
|
73
|
+
}
|
|
74
|
+
return createNewTransporter(key, newConfig);
|
|
56
75
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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);
|
|
62
83
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
69
93
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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);
|
|
74
123
|
}
|
|
75
|
-
return this.createNewTransporter(key, newConfig);
|
|
76
124
|
}
|
|
77
|
-
|
|
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);
|
|
78
131
|
}
|
|
79
|
-
|
|
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
|
+
}
|
|
164
|
+
class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
165
|
+
configSchema = import_joi.default.object({
|
|
166
|
+
provider: import_joi.default.object({
|
|
167
|
+
host: import_joi.default.string(),
|
|
168
|
+
port: import_joi.default.alternatives().try(import_joi.default.number().port(), import_joi.default.string()).default(465),
|
|
169
|
+
secure: import_joi.default.alternatives().try(import_joi.default.boolean(), import_joi.default.string()).default(true),
|
|
170
|
+
auth: import_joi.default.object({
|
|
171
|
+
user: import_joi.default.string(),
|
|
172
|
+
pass: import_joi.default.string()
|
|
173
|
+
})
|
|
174
|
+
}),
|
|
175
|
+
from: import_joi.default.string(),
|
|
176
|
+
to: import_joi.default.array().items(import_joi.default.string()),
|
|
177
|
+
cc: import_joi.default.array().items(import_joi.default.string()),
|
|
178
|
+
bcc: import_joi.default.array().items(import_joi.default.string()),
|
|
179
|
+
subject: import_joi.default.string(),
|
|
180
|
+
contentType: import_joi.default.string().valid("html", "text").default("html"),
|
|
181
|
+
html: import_joi.default.string(),
|
|
182
|
+
text: import_joi.default.string(),
|
|
183
|
+
ignoreFail: import_joi.default.boolean().default(false)
|
|
184
|
+
});
|
|
185
|
+
async run(node, prevJob, processor, options) {
|
|
186
|
+
var _a, _b;
|
|
80
187
|
const {
|
|
81
188
|
provider,
|
|
82
189
|
contentType,
|
|
@@ -87,32 +194,41 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
|
87
194
|
html,
|
|
88
195
|
text,
|
|
89
196
|
ignoreFail,
|
|
90
|
-
...
|
|
197
|
+
...others
|
|
91
198
|
} = processor.getParsedValue(node.config, node.id);
|
|
92
199
|
const { workflow } = processor.execution;
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
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);
|
|
96
207
|
const payload = {
|
|
97
|
-
...
|
|
208
|
+
...others,
|
|
98
209
|
...contentType === "html" ? { html } : { text },
|
|
99
210
|
subject: subject == null ? void 0 : subject.trim(),
|
|
100
211
|
to: to ? to.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) : void 0,
|
|
101
|
-
cc: cc ? cc.flat().map((item) => item == null ? void 0 : item.trim()).filter(Boolean) :
|
|
102
|
-
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) : []
|
|
103
214
|
};
|
|
104
215
|
if (sync) {
|
|
105
216
|
try {
|
|
106
|
-
const result = await
|
|
217
|
+
const result = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
|
|
107
218
|
return {
|
|
108
219
|
status: import_plugin_workflow.JOB_STATUS.RESOLVED,
|
|
109
220
|
result
|
|
110
221
|
};
|
|
111
222
|
} catch (error) {
|
|
223
|
+
if (mailAbort.signal.aborted) {
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
112
226
|
return {
|
|
113
227
|
status: ignoreFail ? import_plugin_workflow.JOB_STATUS.RESOLVED : import_plugin_workflow.JOB_STATUS.FAILED,
|
|
114
228
|
result: error
|
|
115
229
|
};
|
|
230
|
+
} finally {
|
|
231
|
+
mailAbort.cleanup();
|
|
116
232
|
}
|
|
117
233
|
}
|
|
118
234
|
const { id } = processor.saveJob({
|
|
@@ -124,26 +240,40 @@ class MailerInstruction extends import_plugin_workflow.Instruction {
|
|
|
124
240
|
await processor.exit();
|
|
125
241
|
const jobDone = { status: import_plugin_workflow.JOB_STATUS.PENDING };
|
|
126
242
|
try {
|
|
127
|
-
const response = await
|
|
243
|
+
const response = await sendMail(transporter, transporterKey, payload, mailAbort.signal);
|
|
128
244
|
processor.logger.info(`smtp-mailer (#${node.id}) sent successfully.`);
|
|
129
245
|
jobDone.status = import_plugin_workflow.JOB_STATUS.RESOLVED;
|
|
130
246
|
jobDone.result = response;
|
|
131
247
|
} catch (error) {
|
|
132
248
|
processor.logger.warn(`smtp-mailer (#${node.id}) sent failed: ${error.message}`);
|
|
133
|
-
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;
|
|
134
250
|
jobDone.result = error;
|
|
135
251
|
} finally {
|
|
252
|
+
mailAbort.cleanup();
|
|
136
253
|
processor.logger.debug(`smtp-mailer (#${node.id}) sending ended, resume workflow...`);
|
|
137
254
|
const job = await this.workflow.app.db.getRepository("jobs").findOne({
|
|
138
255
|
filterByTk: id
|
|
139
256
|
});
|
|
140
|
-
job.
|
|
141
|
-
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
|
+
}
|
|
142
272
|
}
|
|
143
273
|
}
|
|
144
274
|
async resume(node, job, processor) {
|
|
145
275
|
const { ignoreFail } = node.config;
|
|
146
|
-
if (ignoreFail) {
|
|
276
|
+
if (ignoreFail && job.status !== import_plugin_workflow.JOB_STATUS.ABORTED) {
|
|
147
277
|
job.set("status", import_plugin_workflow.JOB_STATUS.RESOLVED);
|
|
148
278
|
}
|
|
149
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
|
|
9
|
+
"version": "2.1.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"main": "./dist/server/index.js",
|
|
12
12
|
"homepage": "https://docs.nocobase.com/handbook/workflow-smtp-mailer",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"homepage.ru-RU": "https://docs-ru.nocobase.com/handbook/workflow-smtp-mailer",
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"antd": "5.x",
|
|
17
|
+
"joi": "^17.13.3",
|
|
17
18
|
"nodemailer": "6.9.13",
|
|
18
19
|
"react": "18.x"
|
|
19
20
|
},
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"@nocobase/server": "2.x",
|
|
25
26
|
"@nocobase/test": "2.x"
|
|
26
27
|
},
|
|
27
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "9373212dd0f22cd985be1e23674d6b454944b9ee",
|
|
28
29
|
"keywords": [
|
|
29
30
|
"NocoBase",
|
|
30
31
|
"Workflow",
|