@dbx-tools/email 0.3.21 → 0.3.23
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/package.json +3 -3
- package/src/config.ts +12 -12
- package/src/outbox.ts +2 -2
- package/src/plugin.ts +1 -1
- package/src/tool.ts +1 -1
- package/test/brand.test.ts +46 -0
- package/test/tsconfig.json +14 -0
package/package.json
CHANGED
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"juice": "^12.1.1",
|
|
20
20
|
"marked": "^18.0.5",
|
|
21
21
|
"nodemailer": "^7.0.13",
|
|
22
|
-
"@dbx-tools/shared-
|
|
23
|
-
"@dbx-tools/shared-
|
|
22
|
+
"@dbx-tools/shared-email": "0.3.23",
|
|
23
|
+
"@dbx-tools/shared-core": "0.3.23"
|
|
24
24
|
},
|
|
25
25
|
"main": "index.ts",
|
|
26
26
|
"license": "UNLICENSED",
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
|
-
"version": "0.3.
|
|
30
|
+
"version": "0.3.23",
|
|
31
31
|
"types": "index.ts",
|
|
32
32
|
"type": "module",
|
|
33
33
|
"exports": {
|
package/src/config.ts
CHANGED
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
|
+
import { resolve } from "node:path";
|
|
25
26
|
import type { BasePluginConfig } from "@databricks/appkit";
|
|
26
|
-
import type { JSONSchema7 } from "json-schema";
|
|
27
27
|
import { object } from "@dbx-tools/shared-core";
|
|
28
|
-
import {
|
|
28
|
+
import type { JSONSchema7 } from "json-schema";
|
|
29
29
|
import type { EmailBrand } from "./brand";
|
|
30
30
|
import { parseAllowedSenders } from "./sender";
|
|
31
31
|
|
|
@@ -162,12 +162,12 @@ export const EMAIL_CONFIG_SCHEMA: JSONSchema7 = {
|
|
|
162
162
|
/** Parse the `SMTP_SECURE` env / config flag, defaulting to `port === 465`. */
|
|
163
163
|
function resolveSecure(flag: boolean | undefined, port: number): boolean {
|
|
164
164
|
if (typeof flag === "boolean") return flag;
|
|
165
|
-
return object.toBoolean(process.env
|
|
165
|
+
return object.toBoolean(process.env.SMTP_SECURE) ?? port === 465;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/** Whether `EMAIL_OUTBOX_MODE` explicitly opts into the file/outbox fallback. */
|
|
169
169
|
function isOutboxModeEnabled(): boolean {
|
|
170
|
-
return object.toBoolean(process.env
|
|
170
|
+
return object.toBoolean(process.env.EMAIL_OUTBOX_MODE) ?? false;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
const SMTP_REQUIRED_FIELDS = ["SMTP_HOST", "SMTP_USER", "SMTP_PASSWORD"] as const;
|
|
@@ -197,13 +197,13 @@ function missingSmtpFields(
|
|
|
197
197
|
*/
|
|
198
198
|
export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmailConfig {
|
|
199
199
|
const smtp = config.smtp ?? {};
|
|
200
|
-
const host = smtp.host ?? process.env
|
|
201
|
-
const user = smtp.user ?? process.env
|
|
202
|
-
const pass = smtp.password ?? process.env
|
|
203
|
-
const domain = config.domain ?? process.env
|
|
204
|
-
const from = config.from ?? process.env
|
|
200
|
+
const host = smtp.host ?? process.env.SMTP_HOST;
|
|
201
|
+
const user = smtp.user ?? process.env.SMTP_USER;
|
|
202
|
+
const pass = smtp.password ?? process.env.SMTP_PASSWORD;
|
|
203
|
+
const domain = config.domain ?? process.env.EMAIL_DOMAIN;
|
|
204
|
+
const from = config.from ?? process.env.EMAIL_FROM;
|
|
205
205
|
const allowedSenders = parseAllowedSenders(
|
|
206
|
-
config.allowedSenders ?? process.env
|
|
206
|
+
config.allowedSenders ?? process.env.EMAIL_ALLOWED_SENDERS,
|
|
207
207
|
);
|
|
208
208
|
const sender: ResolvedSender = {
|
|
209
209
|
...(domain ? { domain } : {}),
|
|
@@ -227,7 +227,7 @@ export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmai
|
|
|
227
227
|
"email: SMTP is configured but no sender source - set EMAIL_DOMAIN (to derive <user>@<domain>) or EMAIL_FROM (a fixed address)",
|
|
228
228
|
);
|
|
229
229
|
}
|
|
230
|
-
const portRaw = smtp.port ?? Number(process.env
|
|
230
|
+
const portRaw = smtp.port ?? Number(process.env.SMTP_PORT);
|
|
231
231
|
const port = Number.isFinite(portRaw) && portRaw ? Number(portRaw) : 587;
|
|
232
232
|
return {
|
|
233
233
|
mode: "smtp",
|
|
@@ -246,7 +246,7 @@ export function resolveEmailConfig(config: EmailPluginConfig = {}): ResolvedEmai
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
const outDir = resolve(
|
|
249
|
-
config.outDir ?? process.env
|
|
249
|
+
config.outDir ?? process.env.EMAIL_OUTBOX_DIR ?? resolve(process.cwd(), "tmp"),
|
|
250
250
|
);
|
|
251
251
|
return { mode: "file", outDir, ...sender };
|
|
252
252
|
}
|
package/src/outbox.ts
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* @module
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { EmailMessage } from "@dbx-tools/shared-email";
|
|
17
|
-
import { string } from "@dbx-tools/shared-core";
|
|
18
16
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
19
17
|
import { join, resolve } from "node:path";
|
|
18
|
+
import { string } from "@dbx-tools/shared-core";
|
|
19
|
+
import type { EmailMessage } from "@dbx-tools/shared-email";
|
|
20
20
|
import type { EmailBrand } from "./brand";
|
|
21
21
|
import { renderEmailHtml } from "./email-html";
|
|
22
22
|
|
package/src/plugin.ts
CHANGED
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
type IAppRouter,
|
|
28
28
|
type PluginManifest,
|
|
29
29
|
} from "@databricks/appkit";
|
|
30
|
-
import type { EmailMessage, EmailResult, EmailSenders } from "@dbx-tools/shared-email";
|
|
31
30
|
import { error, log } from "@dbx-tools/shared-core";
|
|
31
|
+
import type { EmailMessage, EmailResult, EmailSenders } from "@dbx-tools/shared-email";
|
|
32
32
|
import type express from "express";
|
|
33
33
|
import { EMAIL_CONFIG_SCHEMA, type EmailPluginConfig } from "./config";
|
|
34
34
|
import { isSenderAllowed, listSenderOptions, resolveSenderAddress } from "./sender";
|
package/src/tool.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { getExecutionContext } from "@databricks/appkit";
|
|
16
|
-
import { email, type EmailMessage } from "@dbx-tools/shared-email";
|
|
17
16
|
import { log, string } from "@dbx-tools/shared-core";
|
|
17
|
+
import { email, type EmailMessage } from "@dbx-tools/shared-email";
|
|
18
18
|
import { createTool } from "@mastra/core/tools";
|
|
19
19
|
import { resolveSenderAddress } from "./sender";
|
|
20
20
|
import { getEmailRuntime, sendEmail } from "./transport";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { brand } from "@dbx-tools/shared-core";
|
|
4
|
+
import { defaultEmailBrand, emailBrandFromContext } from "../src/brand";
|
|
5
|
+
import { renderEmailHtml } from "../src/email-html";
|
|
6
|
+
|
|
7
|
+
describe("email brand", () => {
|
|
8
|
+
it("derives accent, font, and name from a brand context", () => {
|
|
9
|
+
const b = emailBrandFromContext(brand.defaultBrandContext);
|
|
10
|
+
assert.equal(b.accent, brand.defaultBrandContext.colors.primary);
|
|
11
|
+
assert.equal(b.fontFamily, brand.defaultBrandContext.typography.sans);
|
|
12
|
+
assert.equal(b.name, brand.defaultBrandContext.name);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("drops a logo that is not a fetchable URL (package-export path)", () => {
|
|
16
|
+
// The default brand's logo is an `@dbx-tools/ui-branding/...svg` export
|
|
17
|
+
// path, which can't load in a mail client, so no logoUrl is emitted.
|
|
18
|
+
assert.equal(defaultEmailBrand.logoUrl, undefined);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("keeps an http(s) or data URL logo", () => {
|
|
22
|
+
const ctx = brand.parseBrandContext({
|
|
23
|
+
assets: { logo: { light: "https://ex.com/l.svg", dark: "https://ex.com/d.svg" } },
|
|
24
|
+
});
|
|
25
|
+
// Dark logo wins - the header band is dark.
|
|
26
|
+
assert.equal(emailBrandFromContext(ctx).logoUrl, "https://ex.com/d.svg");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("renderEmailHtml branding", () => {
|
|
31
|
+
it("inlines the brand accent and font, not the default blue", () => {
|
|
32
|
+
const html = renderEmailHtml({ subject: "S", body: "b", brand: defaultEmailBrand });
|
|
33
|
+
assert.ok(html.includes(defaultEmailBrand.accent));
|
|
34
|
+
assert.ok(html.includes("Inter"));
|
|
35
|
+
assert.ok(!html.includes("#0b6bcb"));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("renders an <img> when the brand supplies a URL logo", () => {
|
|
39
|
+
const b = { ...defaultEmailBrand, logoUrl: "https://ex.com/logo.svg" };
|
|
40
|
+
assert.ok(renderEmailHtml({ subject: "S", body: "b", brand: b }).includes("<img"));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("falls back to the neutral default palette with no brand", () => {
|
|
44
|
+
assert.ok(renderEmailHtml({ subject: "S", body: "b" }).includes("#0b6bcb"));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
|
|
2
|
+
{
|
|
3
|
+
"extends": "../tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"rootDir": ".."
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"**/*.ts"
|
|
10
|
+
],
|
|
11
|
+
"exclude": [
|
|
12
|
+
"node_modules"
|
|
13
|
+
]
|
|
14
|
+
}
|