@dbx-tools/email 0.9.5 → 0.9.7
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/README.md +17 -17
- package/exports.ts +2 -0
- package/index.ts +2 -1
- package/lib/exports.d.ts +2 -0
- package/lib/exports.js +3 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -2
- package/lib/src/config.js +15 -14
- package/lib/src/email-html.d.ts +2 -2
- package/lib/src/email-html.js +3 -3
- package/lib/src/outbox.js +3 -3
- package/lib/src/plugin.d.ts +10 -17
- package/lib/src/plugin.js +14 -26
- package/lib/src/sender.js +6 -6
- package/lib/src/tool.js +3 -3
- package/lib/src/transport.js +6 -14
- package/package.json +7 -6
- package/src/config.ts +19 -13
- package/src/email-html.ts +2 -2
- package/src/outbox.ts +2 -2
- package/src/plugin.ts +12 -34
- package/src/sender.ts +5 -5
- package/src/tool.ts +2 -2
- package/src/transport.ts +5 -18
package/package.json
CHANGED
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@databricks/appkit": "0.81.0",
|
|
31
|
-
"@dbx-tools/appkit": "^0.9.
|
|
32
|
-
"@dbx-tools/core": "^0.9.
|
|
33
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
34
|
-
"@dbx-tools/shared-email": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-email-template": "^0.9.
|
|
31
|
+
"@dbx-tools/appkit": "^0.9.7",
|
|
32
|
+
"@dbx-tools/core": "^0.9.7",
|
|
33
|
+
"@dbx-tools/shared-core": "^0.9.7",
|
|
34
|
+
"@dbx-tools/shared-email": "^0.9.7",
|
|
35
|
+
"@dbx-tools/shared-email-template": "^0.9.7",
|
|
36
36
|
"@mastra/core": "1.71.0",
|
|
37
37
|
"@react-email/render": "^2.1.0",
|
|
38
38
|
"nodemailer": "^7.0.13",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"./package.json": "./package.json"
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
|
-
"version": "0.9.
|
|
56
|
+
"version": "0.9.7",
|
|
57
57
|
"types": "./lib/index.d.ts",
|
|
58
58
|
"type": "module",
|
|
59
59
|
"exports": {
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"files": [
|
|
67
67
|
"index.ts",
|
|
68
68
|
"src",
|
|
69
|
+
"exports.ts",
|
|
69
70
|
"lib"
|
|
70
71
|
],
|
|
71
72
|
"dbxToolsConfig": {
|
package/src/config.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { resolve } from "node:path";
|
|
28
28
|
import { ConfigurationError, ValidationError, type BasePluginConfig } from "@databricks/appkit";
|
|
29
|
-
import {
|
|
29
|
+
import { configUtils } from "@dbx-tools/core";
|
|
30
30
|
import { object } from "@dbx-tools/shared-core";
|
|
31
31
|
import type { JSONSchema7 } from "json-schema";
|
|
32
32
|
import { defaultEmailBrand, type EmailBrand } from "./brand.ts";
|
|
@@ -272,13 +272,15 @@ export const EMAIL_CONFIG_SCHEMA: JSONSchema7 = {
|
|
|
272
272
|
/** Parse the `SMTP_SECURE` env / config flag, defaulting by port. */
|
|
273
273
|
function resolveSecure(flag: boolean | undefined, port: number): boolean {
|
|
274
274
|
return (
|
|
275
|
-
|
|
275
|
+
configUtils.boolean(flag, "SMTP_SECURE", configUtils.ENV_ONLY) ??
|
|
276
|
+
port === IMPLICIT_TLS_SMTP_PORT
|
|
276
277
|
);
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
/** Parse the `EMAIL_SENDER_POLICY` env / config value, defaulting to deny-by-default. */
|
|
280
281
|
function resolveSenderPolicy(policy: SenderPolicy | undefined): SenderPolicy {
|
|
281
|
-
const raw =
|
|
282
|
+
const raw =
|
|
283
|
+
policy ?? configUtils.text("EMAIL_SENDER_POLICY", configUtils.ENV_ONLY)?.toLowerCase();
|
|
282
284
|
if (raw === "unrestricted") return "unrestricted";
|
|
283
285
|
if (raw === undefined || raw === "" || raw === "allowlist") return "allowlist";
|
|
284
286
|
throw ValidationError.invalidValue("senderPolicy", raw, '"allowlist" or "unrestricted"');
|
|
@@ -311,7 +313,7 @@ function impliedSenderPatterns(sender: {
|
|
|
311
313
|
|
|
312
314
|
/** Whether `EMAIL_OUTBOX_MODE` explicitly opts into the file/outbox fallback. */
|
|
313
315
|
function isOutboxModeEnabled(): boolean {
|
|
314
|
-
return
|
|
316
|
+
return configUtils.boolean(undefined, "EMAIL_OUTBOX_MODE", configUtils.ENV_ONLY) ?? false;
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
const SMTP_REQUIRED_FIELDS = ["SMTP_HOST", "SMTP_USER", "SMTP_PASSWORD"] as const;
|
|
@@ -347,15 +349,19 @@ function missingSmtpFields(
|
|
|
347
349
|
*/
|
|
348
350
|
export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmailConfig {
|
|
349
351
|
const smtp = config.smtp ?? {};
|
|
350
|
-
const host =
|
|
351
|
-
const user =
|
|
352
|
-
const pass =
|
|
353
|
-
const domain =
|
|
354
|
-
const from =
|
|
355
|
-
const systemFrom =
|
|
352
|
+
const host = configUtils.string(smtp.host, "SMTP_HOST", configUtils.ENV_ONLY);
|
|
353
|
+
const user = configUtils.string(smtp.user, "SMTP_USER", configUtils.ENV_ONLY);
|
|
354
|
+
const pass = configUtils.string(smtp.password, "SMTP_PASSWORD", configUtils.ENV_ONLY);
|
|
355
|
+
const domain = configUtils.string(config.domain, "EMAIL_DOMAIN", configUtils.ENV_ONLY);
|
|
356
|
+
const from = configUtils.string(config.from, "EMAIL_FROM", configUtils.ENV_ONLY);
|
|
357
|
+
const systemFrom = configUtils.string(
|
|
358
|
+
config.systemFrom,
|
|
359
|
+
"EMAIL_SYSTEM_FROM",
|
|
360
|
+
configUtils.ENV_ONLY,
|
|
361
|
+
);
|
|
356
362
|
const senderPolicy = resolveSenderPolicy(config.senderPolicy);
|
|
357
363
|
const configuredSenders = parseAllowedSenders(
|
|
358
|
-
config.allowedSenders ??
|
|
364
|
+
config.allowedSenders ?? configUtils.text("EMAIL_ALLOWED_SENDERS", configUtils.ENV_ONLY),
|
|
359
365
|
);
|
|
360
366
|
const allowedSenders =
|
|
361
367
|
configuredSenders.length > 0 || senderPolicy === "unrestricted"
|
|
@@ -384,7 +390,7 @@ export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmai
|
|
|
384
390
|
"Set EMAIL_DOMAIN to derive <user-local-part>@<domain> (and no-reply@<domain> for system mail), or EMAIL_FROM for a fixed address.",
|
|
385
391
|
);
|
|
386
392
|
}
|
|
387
|
-
const port =
|
|
393
|
+
const port = configUtils.port(smtp.port, "SMTP_PORT", DEFAULT_SMTP_PORT, configUtils.ENV_ONLY);
|
|
388
394
|
return {
|
|
389
395
|
mode: "smtp",
|
|
390
396
|
host: host!,
|
|
@@ -403,7 +409,7 @@ export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmai
|
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
const outDir = resolve(
|
|
406
|
-
|
|
412
|
+
configUtils.string(config.outDir, "EMAIL_OUTBOX_DIR", configUtils.ENV_ONLY) ??
|
|
407
413
|
resolve(process.cwd(), "tmp"),
|
|
408
414
|
);
|
|
409
415
|
return { mode: "file", outDir, ...sender };
|
package/src/email-html.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Node rendering adapters for the shared React Email document. @module */
|
|
2
|
-
import {
|
|
2
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
3
3
|
import { EmailDocument, type EmailDocumentProps } from "@dbx-tools/shared-email-template";
|
|
4
4
|
import { render } from "@react-email/render";
|
|
5
5
|
import { createElement } from "react";
|
|
6
6
|
|
|
7
7
|
/** Escape HTML-significant characters (re-exported from shared-core). */
|
|
8
|
-
export const escapeHtml =
|
|
8
|
+
export const escapeHtml = stringUtils.escapeHtml;
|
|
9
9
|
|
|
10
10
|
/** Options accepted by the shared React Email document. */
|
|
11
11
|
export type EmailHtmlOptions = EmailDocumentProps;
|
package/src/outbox.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
18
|
-
import {
|
|
18
|
+
import { stringUtils } from "@dbx-tools/shared-core";
|
|
19
19
|
import type { EmailMessage } from "@dbx-tools/shared-email";
|
|
20
20
|
import type { EmailBrand } from "./brand.ts";
|
|
21
21
|
import { renderEmailHtml, type EmailHtmlOptions } from "./email-html.ts";
|
|
@@ -28,7 +28,7 @@ const SUBJECT_SLUG_MAX_LENGTH = 48;
|
|
|
28
28
|
|
|
29
29
|
/** Filesystem-safe slug of the subject for the file name. */
|
|
30
30
|
function subjectSlug(subject: string): string {
|
|
31
|
-
return
|
|
31
|
+
return stringUtils.toSlugWithOptions({ maxLength: SUBJECT_SLUG_MAX_LENGTH }, subject) || "email";
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** The envelope rows shown above the body in the preview file. */
|
package/src/plugin.ts
CHANGED
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
ConnectionError,
|
|
30
30
|
ExecutionError,
|
|
31
31
|
getExecutionContext,
|
|
32
|
-
Plugin,
|
|
33
32
|
toPlugin,
|
|
34
33
|
ValidationError,
|
|
35
34
|
type AppKitError,
|
|
@@ -37,16 +36,9 @@ import {
|
|
|
37
36
|
type IAppRouter,
|
|
38
37
|
type PluginManifest,
|
|
39
38
|
} from "@databricks/appkit";
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
toolsFromRegistry,
|
|
44
|
-
type AgentToolDefinition,
|
|
45
|
-
type ToolProvider,
|
|
46
|
-
type ToolRegistry,
|
|
47
|
-
} from "@databricks/appkit/beta";
|
|
48
|
-
import { brand as appkitBrand } from "@dbx-tools/appkit";
|
|
49
|
-
import { log, string, token } from "@dbx-tools/shared-core";
|
|
39
|
+
import { defineTool, type ToolRegistry } from "@databricks/appkit/beta";
|
|
40
|
+
import { brandContext, ToolRegistryPlugin } from "@dbx-tools/appkit";
|
|
41
|
+
import { log, stringUtils, token } from "@dbx-tools/shared-core";
|
|
50
42
|
import {
|
|
51
43
|
email as sharedEmail,
|
|
52
44
|
type EmailMessage,
|
|
@@ -82,12 +74,12 @@ const logger = log.logger("email");
|
|
|
82
74
|
* @example
|
|
83
75
|
* ```ts
|
|
84
76
|
* import { createApp, server } from "@databricks/appkit";
|
|
85
|
-
* import {
|
|
77
|
+
* import { email } from "@dbx-tools/email";
|
|
86
78
|
*
|
|
87
79
|
* await createApp({
|
|
88
80
|
* plugins: [
|
|
89
81
|
* server(),
|
|
90
|
-
*
|
|
82
|
+
* email({
|
|
91
83
|
* smtp: { host: "smtp.example.com", user: "apikey", password: process.env.SMTP_KEY },
|
|
92
84
|
* domain: "mail.example.com",
|
|
93
85
|
* }),
|
|
@@ -95,7 +87,7 @@ const logger = log.logger("email");
|
|
|
95
87
|
* });
|
|
96
88
|
* ```
|
|
97
89
|
*/
|
|
98
|
-
export class EmailPlugin extends
|
|
90
|
+
export class EmailPlugin extends ToolRegistryPlugin<EmailPluginConfig> {
|
|
99
91
|
static manifest = {
|
|
100
92
|
name: "email",
|
|
101
93
|
displayName: "Email",
|
|
@@ -121,7 +113,7 @@ export class EmailPlugin extends Plugin<EmailPluginConfig> implements ToolProvid
|
|
|
121
113
|
* against the same schema first, but re-parsing is what gives the body typed
|
|
122
114
|
* arguments instead of `unknown`.
|
|
123
115
|
*/
|
|
124
|
-
|
|
116
|
+
protected readonly toolRegistry: ToolRegistry = {
|
|
125
117
|
[SEND_TOOL]: defineTool({
|
|
126
118
|
description: SEND_EMAIL_DESCRIPTION,
|
|
127
119
|
schema: sharedEmail.emailMessageSchema,
|
|
@@ -146,7 +138,7 @@ export class EmailPlugin extends Plugin<EmailPluginConfig> implements ToolProvid
|
|
|
146
138
|
override async setup(): Promise<void> {
|
|
147
139
|
const { transporter, config } = getEmailRuntime({
|
|
148
140
|
...this.config,
|
|
149
|
-
brand: this.config.brand ?? emailBrandFromContext(
|
|
141
|
+
brand: this.config.brand ?? emailBrandFromContext(brandContext.getBrandContext()),
|
|
150
142
|
});
|
|
151
143
|
setEmailExecutor((fn, settings) => this.execute(fn, settings));
|
|
152
144
|
const policy = {
|
|
@@ -230,7 +222,7 @@ export class EmailPlugin extends Plugin<EmailPluginConfig> implements ToolProvid
|
|
|
230
222
|
method: "get",
|
|
231
223
|
path: SENDERS_ROUTE,
|
|
232
224
|
handler: async (req, res) => {
|
|
233
|
-
const oboToken =
|
|
225
|
+
const oboToken = stringUtils.trimToNull(req.header(token.ACCESS_TOKEN_HEADER));
|
|
234
226
|
const scoped = oboToken === null ? this : this.asUser(req);
|
|
235
227
|
const result = await scoped.executeListSenders();
|
|
236
228
|
if (!result.ok) {
|
|
@@ -263,20 +255,6 @@ export class EmailPlugin extends Plugin<EmailPluginConfig> implements ToolProvid
|
|
|
263
255
|
};
|
|
264
256
|
}
|
|
265
257
|
|
|
266
|
-
/** AppKit `ToolProvider`: the tool definitions offered to an agent. */
|
|
267
|
-
getAgentTools(): AgentToolDefinition[] {
|
|
268
|
-
return toolsFromRegistry(this.tools);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* AppKit `ToolProvider`: run one tool call. Arguments are validated against
|
|
273
|
-
* the tool's schema first, and a validation failure comes back as an
|
|
274
|
-
* LLM-friendly string so the model can correct itself on the next turn.
|
|
275
|
-
*/
|
|
276
|
-
async executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown> {
|
|
277
|
-
return executeFromRegistry(this.tools, name, args, signal);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
258
|
/**
|
|
281
259
|
* Send one message, resolving the sender for the caller in scope when
|
|
282
260
|
* `from` is not pinned. The interceptor chain is applied inside
|
|
@@ -362,15 +340,15 @@ function unwrap<T>(result: ExecutionResult<T>): T {
|
|
|
362
340
|
* @example
|
|
363
341
|
* ```ts
|
|
364
342
|
* import { createApp, server } from "@databricks/appkit";
|
|
365
|
-
* import {
|
|
343
|
+
* import { defaultEmailBrand, email } from "@dbx-tools/email";
|
|
366
344
|
*
|
|
367
345
|
* await createApp({
|
|
368
346
|
* plugins: [
|
|
369
347
|
* server(),
|
|
370
|
-
*
|
|
348
|
+
* email({
|
|
371
349
|
* domain: "mail.example.com",
|
|
372
350
|
* allowedSenders: ["*@mail.example.com"],
|
|
373
|
-
* brand:
|
|
351
|
+
* brand: defaultEmailBrand,
|
|
374
352
|
* }),
|
|
375
353
|
* ],
|
|
376
354
|
* });
|
package/src/sender.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { ConfigurationError, ValidationError } from "@databricks/appkit";
|
|
31
|
-
import { log, net,
|
|
31
|
+
import { log, net, stringUtils } from "@dbx-tools/shared-core";
|
|
32
32
|
|
|
33
33
|
import type { ResolvedEmailConfig, ResolvedSender } from "./config.ts";
|
|
34
34
|
|
|
@@ -132,11 +132,11 @@ type SenderSource = Pick<ResolvedSender, "systemFrom" | "domain" | "from">;
|
|
|
132
132
|
* result into the sender allow-list before any send happens.
|
|
133
133
|
*/
|
|
134
134
|
export function systemSenderAddress(source: SenderSource): string | undefined {
|
|
135
|
-
const explicit =
|
|
135
|
+
const explicit = stringUtils.trimToNull(source.systemFrom);
|
|
136
136
|
if (explicit) return explicit;
|
|
137
|
-
const domain =
|
|
137
|
+
const domain = stringUtils.trimToNull(source.domain);
|
|
138
138
|
if (domain) return `${SYSTEM_SENDER_LOCAL_PART}@${domain}`;
|
|
139
|
-
return
|
|
139
|
+
return stringUtils.trimToNull(source.from) ?? undefined;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
@@ -166,7 +166,7 @@ export function resolveSenderAddress(
|
|
|
166
166
|
config: ResolvedEmailConfig,
|
|
167
167
|
userEmail: string | undefined,
|
|
168
168
|
): string {
|
|
169
|
-
const email =
|
|
169
|
+
const email = stringUtils.trimToNull(userEmail);
|
|
170
170
|
if (!email) return resolveSystemSenderAddress(config);
|
|
171
171
|
if (config.from) return config.from;
|
|
172
172
|
if (config.domain) return deriveSenderAddress(email, config.domain);
|
package/src/tool.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { getExecutionContext } from "@databricks/appkit";
|
|
21
|
-
import { log,
|
|
21
|
+
import { log, stringUtils } from "@dbx-tools/shared-core";
|
|
22
22
|
import { email } from "@dbx-tools/shared-email";
|
|
23
23
|
import { createTool, type ToolAction } from "@mastra/core/tools";
|
|
24
24
|
import { resolveSenderAddress } from "./sender.ts";
|
|
@@ -31,7 +31,7 @@ const logger = log.logger("email/tool/send-email");
|
|
|
31
31
|
* {@link emailTool} and the AppKit `email.send` tool so both agents get the
|
|
32
32
|
* same guidance about approval and scope.
|
|
33
33
|
*/
|
|
34
|
-
export const SEND_EMAIL_DESCRIPTION =
|
|
34
|
+
export const SEND_EMAIL_DESCRIPTION = stringUtils.toDescription(`
|
|
35
35
|
Send an email on the user's behalf. Pass one or more recipient
|
|
36
36
|
addresses (with optional cc / bcc and file attachments), a subject,
|
|
37
37
|
and a body; the configured approval policy runs before it goes out.
|
package/src/transport.ts
CHANGED
|
@@ -22,12 +22,8 @@
|
|
|
22
22
|
* @module
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
ExecutionError,
|
|
28
|
-
ValidationError,
|
|
29
|
-
type ExecutionResult,
|
|
30
|
-
} from "@databricks/appkit";
|
|
25
|
+
import { ConfigurationError, ValidationError, type ExecutionResult } from "@databricks/appkit";
|
|
26
|
+
import { pluginExecution } from "@dbx-tools/appkit";
|
|
31
27
|
import { execution, log } from "@dbx-tools/shared-core";
|
|
32
28
|
import type { EmailAttachment, EmailMessage, EmailResult } from "@dbx-tools/shared-email";
|
|
33
29
|
import nodemailer, { type SendMailOptions, type Transporter } from "nodemailer";
|
|
@@ -146,23 +142,14 @@ export async function executeWrite<T>(
|
|
|
146
142
|
signal?: AbortSignal,
|
|
147
143
|
): Promise<T> {
|
|
148
144
|
const { execute } = getEmailRuntime();
|
|
149
|
-
return
|
|
145
|
+
return pluginExecution.runPluginExecution({
|
|
146
|
+
plugin: "email",
|
|
147
|
+
logger,
|
|
150
148
|
operation,
|
|
151
149
|
settings,
|
|
152
150
|
execute,
|
|
153
151
|
fn,
|
|
154
152
|
signal,
|
|
155
|
-
canceled: ExecutionError.canceled,
|
|
156
|
-
failed: (failure) => {
|
|
157
|
-
logger.warn("execution-failed", {
|
|
158
|
-
operation: failure.operation,
|
|
159
|
-
status: failure.status,
|
|
160
|
-
error: failure.message,
|
|
161
|
-
});
|
|
162
|
-
return new ExecutionError(`email: ${failure.operation} failed`, {
|
|
163
|
-
context: { operation: failure.operation, status: failure.status },
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
153
|
});
|
|
167
154
|
}
|
|
168
155
|
|