@dbx-tools/email 0.3.44 → 0.4.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/lib/index.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export * as brand from "./src/brand.js";
2
+ export * as config from "./src/config.js";
3
+ export * as defaults from "./src/defaults.js";
4
+ export * as emailHtml from "./src/email-html.js";
5
+ export * as markdown from "./src/markdown.js";
6
+ export * as outbox from "./src/outbox.js";
7
+ export * as plugin from "./src/plugin.js";
8
+ export * as sender from "./src/sender.js";
9
+ export * as tool from "./src/tool.js";
10
+ export * as transport from "./src/transport.js";
11
+ export { emailBrandFromContext, defaultEmailBrand } from "./src/brand.js";
12
+ export type { EmailBrand } from "./src/brand.js";
13
+ export { DEFAULT_SMTP_PORT, IMPLICIT_TLS_SMTP_PORT, EMAIL_CONFIG_SCHEMA, resolveEmailConfig } from "./src/config.js";
14
+ export type { SenderPolicy, SmtpConfig, EmailPluginConfig, ResolvedSmtpConfig, ResolvedFileConfig, ResolvedEmailConfig } from "./src/config.js";
15
+ export { SEND_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_ATTEMPTS, MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_TOTAL_BYTES, MAX_ATTACHMENT_COUNT, MAX_BODY_CHARS, EMAIL_SEND_SETTINGS, EMAIL_VERIFY_SETTINGS, EMAIL_SENDERS_SETTINGS } from "./src/defaults.js";
16
+ export type { EmailExecuteConfig, EmailExecutionSettings } from "./src/defaults.js";
17
+ export { escapeHtml, renderEmailHtml } from "./src/email-html.js";
18
+ export type { EmailHtmlOptions } from "./src/email-html.js";
19
+ export { normalizeMarkdown, markdownToHtml } from "./src/markdown.js";
20
+ export { writeOutboxEmail } from "./src/outbox.js";
21
+ export { EmailPlugin, email } from "./src/plugin.js";
22
+ export { deriveSenderAddress, parseAllowedSenders, isSenderAllowed, assertSenderAllowed, resolveSenderAddress, listSenderOptions } from "./src/sender.js";
23
+ export { SEND_EMAIL_DESCRIPTION, emailTool } from "./src/tool.js";
24
+ export type { EmailToolOptions } from "./src/tool.js";
25
+ export { getEmailRuntime, setEmailExecutor, resetEmailRuntime, executeWrite, verifyEmailTransport, sendEmail } from "./src/transport.js";
26
+ export type { EmailExecutor, EmailRuntime } from "./src/transport.js";
package/lib/index.js ADDED
@@ -0,0 +1,24 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as brand from "./src/brand.js";
5
+ export * as config from "./src/config.js";
6
+ export * as defaults from "./src/defaults.js";
7
+ export * as emailHtml from "./src/email-html.js";
8
+ export * as markdown from "./src/markdown.js";
9
+ export * as outbox from "./src/outbox.js";
10
+ export * as plugin from "./src/plugin.js";
11
+ export * as sender from "./src/sender.js";
12
+ export * as tool from "./src/tool.js";
13
+ export * as transport from "./src/transport.js";
14
+ export { emailBrandFromContext, defaultEmailBrand } from "./src/brand.js";
15
+ export { DEFAULT_SMTP_PORT, IMPLICIT_TLS_SMTP_PORT, EMAIL_CONFIG_SCHEMA, resolveEmailConfig } from "./src/config.js";
16
+ export { SEND_TIMEOUT_MS, VERIFY_TIMEOUT_MS, VERIFY_ATTEMPTS, MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_TOTAL_BYTES, MAX_ATTACHMENT_COUNT, MAX_BODY_CHARS, EMAIL_SEND_SETTINGS, EMAIL_VERIFY_SETTINGS, EMAIL_SENDERS_SETTINGS } from "./src/defaults.js";
17
+ export { escapeHtml, renderEmailHtml } from "./src/email-html.js";
18
+ export { normalizeMarkdown, markdownToHtml } from "./src/markdown.js";
19
+ export { writeOutboxEmail } from "./src/outbox.js";
20
+ export { EmailPlugin, email } from "./src/plugin.js";
21
+ export { deriveSenderAddress, parseAllowedSenders, isSenderAllowed, assertSenderAllowed, resolveSenderAddress, listSenderOptions } from "./src/sender.js";
22
+ export { SEND_EMAIL_DESCRIPTION, emailTool } from "./src/tool.js";
23
+ export { getEmailRuntime, setEmailExecutor, resetEmailRuntime, executeWrite, verifyEmailTransport, sendEmail } from "./src/transport.js";
24
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssS0FBSyxNQUFNLGFBQWEsQ0FBQztBQUNyQyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssUUFBUSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxTQUFTLE1BQU0sa0JBQWtCLENBQUM7QUFDOUMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssSUFBSSxNQUFNLFlBQVksQ0FBQztBQUNuQyxPQUFPLEtBQUssU0FBUyxNQUFNLGlCQUFpQixDQUFDO0FBQzdDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUV2RSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsbUJBQW1CLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFbEgsT0FBTyxFQUFFLGVBQWUsRUFBRSxpQkFBaUIsRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsMkJBQTJCLEVBQUUsb0JBQW9CLEVBQUUsY0FBYyxFQUFFLG1CQUFtQixFQUFFLHFCQUFxQixFQUFFLHNCQUFzQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFbFAsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUUvRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2hELE9BQU8sRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ2xELE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxtQkFBbUIsRUFBRSxlQUFlLEVBQUUsbUJBQW1CLEVBQUUsb0JBQW9CLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdkosT0FBTyxFQUFFLHNCQUFzQixFQUFFLFNBQVMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUvRCxPQUFPLEVBQUUsZUFBZSxFQUFFLGdCQUFnQixFQUFFLGlCQUFpQixFQUFFLFlBQVksRUFBRSxvQkFBb0IsRUFBRSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIEdFTkVSQVRFRCBieSBwcm9qZW4gd2F0Y2ggLSBETyBOT1QgRURJVC5cbi8vIFJlZ2VuZXJhdGVkIGZyb20gdGhlIGV4cG9ydGluZyBtb2R1bGVzIGluIC4vc3JjLlxuLy8gSGFuZCBlZGl0cyBhcmUgb3ZlcndyaXR0ZW4gb24gdGhlIG5leHQgd2F0Y2g7IHRoaXMgZmlsZSBpcyByZWFkLW9ubHkuXG5cbmV4cG9ydCAqIGFzIGJyYW5kIGZyb20gXCIuL3NyYy9icmFuZFwiO1xuZXhwb3J0ICogYXMgY29uZmlnIGZyb20gXCIuL3NyYy9jb25maWdcIjtcbmV4cG9ydCAqIGFzIGRlZmF1bHRzIGZyb20gXCIuL3NyYy9kZWZhdWx0c1wiO1xuZXhwb3J0ICogYXMgZW1haWxIdG1sIGZyb20gXCIuL3NyYy9lbWFpbC1odG1sXCI7XG5leHBvcnQgKiBhcyBtYXJrZG93biBmcm9tIFwiLi9zcmMvbWFya2Rvd25cIjtcbmV4cG9ydCAqIGFzIG91dGJveCBmcm9tIFwiLi9zcmMvb3V0Ym94XCI7XG5leHBvcnQgKiBhcyBwbHVnaW4gZnJvbSBcIi4vc3JjL3BsdWdpblwiO1xuZXhwb3J0ICogYXMgc2VuZGVyIGZyb20gXCIuL3NyYy9zZW5kZXJcIjtcbmV4cG9ydCAqIGFzIHRvb2wgZnJvbSBcIi4vc3JjL3Rvb2xcIjtcbmV4cG9ydCAqIGFzIHRyYW5zcG9ydCBmcm9tIFwiLi9zcmMvdHJhbnNwb3J0XCI7XG5leHBvcnQgeyBlbWFpbEJyYW5kRnJvbUNvbnRleHQsIGRlZmF1bHRFbWFpbEJyYW5kIH0gZnJvbSBcIi4vc3JjL2JyYW5kXCI7XG5leHBvcnQgdHlwZSB7IEVtYWlsQnJhbmQgfSBmcm9tIFwiLi9zcmMvYnJhbmRcIjtcbmV4cG9ydCB7IERFRkFVTFRfU01UUF9QT1JULCBJTVBMSUNJVF9UTFNfU01UUF9QT1JULCBFTUFJTF9DT05GSUdfU0NIRU1BLCByZXNvbHZlRW1haWxDb25maWcgfSBmcm9tIFwiLi9zcmMvY29uZmlnXCI7XG5leHBvcnQgdHlwZSB7IFNlbmRlclBvbGljeSwgU210cENvbmZpZywgRW1haWxQbHVnaW5Db25maWcsIFJlc29sdmVkU210cENvbmZpZywgUmVzb2x2ZWRGaWxlQ29uZmlnLCBSZXNvbHZlZEVtYWlsQ29uZmlnIH0gZnJvbSBcIi4vc3JjL2NvbmZpZ1wiO1xuZXhwb3J0IHsgU0VORF9USU1FT1VUX01TLCBWRVJJRllfVElNRU9VVF9NUywgVkVSSUZZX0FUVEVNUFRTLCBNQVhfQVRUQUNITUVOVF9CWVRFUywgTUFYX0FUVEFDSE1FTlRTX1RPVEFMX0JZVEVTLCBNQVhfQVRUQUNITUVOVF9DT1VOVCwgTUFYX0JPRFlfQ0hBUlMsIEVNQUlMX1NFTkRfU0VUVElOR1MsIEVNQUlMX1ZFUklGWV9TRVRUSU5HUywgRU1BSUxfU0VOREVSU19TRVRUSU5HUyB9IGZyb20gXCIuL3NyYy9kZWZhdWx0c1wiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbEV4ZWN1dGVDb25maWcsIEVtYWlsRXhlY3V0aW9uU2V0dGluZ3MgfSBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCB7IGVzY2FwZUh0bWwsIHJlbmRlckVtYWlsSHRtbCB9IGZyb20gXCIuL3NyYy9lbWFpbC1odG1sXCI7XG5leHBvcnQgdHlwZSB7IEVtYWlsSHRtbE9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvZW1haWwtaHRtbFwiO1xuZXhwb3J0IHsgbm9ybWFsaXplTWFya2Rvd24sIG1hcmtkb3duVG9IdG1sIH0gZnJvbSBcIi4vc3JjL21hcmtkb3duXCI7XG5leHBvcnQgeyB3cml0ZU91dGJveEVtYWlsIH0gZnJvbSBcIi4vc3JjL291dGJveFwiO1xuZXhwb3J0IHsgRW1haWxQbHVnaW4sIGVtYWlsIH0gZnJvbSBcIi4vc3JjL3BsdWdpblwiO1xuZXhwb3J0IHsgZGVyaXZlU2VuZGVyQWRkcmVzcywgcGFyc2VBbGxvd2VkU2VuZGVycywgaXNTZW5kZXJBbGxvd2VkLCBhc3NlcnRTZW5kZXJBbGxvd2VkLCByZXNvbHZlU2VuZGVyQWRkcmVzcywgbGlzdFNlbmRlck9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvc2VuZGVyXCI7XG5leHBvcnQgeyBTRU5EX0VNQUlMX0RFU0NSSVBUSU9OLCBlbWFpbFRvb2wgfSBmcm9tIFwiLi9zcmMvdG9vbFwiO1xuZXhwb3J0IHR5cGUgeyBFbWFpbFRvb2xPcHRpb25zIH0gZnJvbSBcIi4vc3JjL3Rvb2xcIjtcbmV4cG9ydCB7IGdldEVtYWlsUnVudGltZSwgc2V0RW1haWxFeGVjdXRvciwgcmVzZXRFbWFpbFJ1bnRpbWUsIGV4ZWN1dGVXcml0ZSwgdmVyaWZ5RW1haWxUcmFuc3BvcnQsIHNlbmRFbWFpbCB9IGZyb20gXCIuL3NyYy90cmFuc3BvcnRcIjtcbmV4cG9ydCB0eXBlIHsgRW1haWxFeGVjdXRvciwgRW1haWxSdW50aW1lIH0gZnJvbSBcIi4vc3JjL3RyYW5zcG9ydFwiO1xuIl19
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Optional brand styling for outbound email. {@link EmailBrand} is the
3
+ * small, email-safe slice of a brand a message actually needs - an accent
4
+ * color, a font stack, a display name, and an optional logo image - and
5
+ * {@link emailBrandFromContext} derives it from the portable `BrandContext`
6
+ * shared across the UI and libraries.
7
+ *
8
+ * Email can't use the `[data-brand]` CSS bridge the browser UI uses (mail
9
+ * clients strip `<style>` blocks and ignore `var()`), so branding is applied
10
+ * by inlining these values at render time. A logo is only emitted when it's
11
+ * a fetchable `http(s):` or `data:` URL - a package-export path (the default
12
+ * asset form) can't resolve in an inbox, so it's dropped rather than shown
13
+ * as a broken image.
14
+ *
15
+ * @module
16
+ */
17
+ import { type BrandContext } from "@dbx-tools/shared-core";
18
+ /** Email-safe brand values inlined into the message layout at render time. */
19
+ export interface EmailBrand {
20
+ /** Header-band background and link color. */
21
+ accent: string;
22
+ /** Text and logo color rendered on the accent band. Defaults to white. */
23
+ onAccent?: string;
24
+ /** Body font stack. */
25
+ fontFamily: string;
26
+ /** Product/display name, used as the header text and the logo `alt`. */
27
+ name?: string;
28
+ /**
29
+ * Logo image rendered in the header band. Only an `http(s):` or `data:`
30
+ * URL renders; other values (e.g. a package-export path) are ignored,
31
+ * since they can't load in a mail client.
32
+ */
33
+ logoUrl?: string;
34
+ }
35
+ /**
36
+ * Derive the email-safe {@link EmailBrand} from a full brand context: the
37
+ * primary color as the accent, the sans font stack, the display name, and
38
+ * the dark-surface logo (the header band is dark) when it's a renderable URL.
39
+ */
40
+ export declare function emailBrandFromContext(context: BrandContext): EmailBrand;
41
+ /**
42
+ * The dbx-tools brand as an {@link EmailBrand}, ready to pass to the email
43
+ * plugin (`email({ brand: defaultEmailBrand })`). Convenience so a consumer
44
+ * needs only `@dbx-tools/email`, not the shared brand context, for the
45
+ * common case of the default brand.
46
+ */
47
+ export declare const defaultEmailBrand: EmailBrand;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Optional brand styling for outbound email. {@link EmailBrand} is the
3
+ * small, email-safe slice of a brand a message actually needs - an accent
4
+ * color, a font stack, a display name, and an optional logo image - and
5
+ * {@link emailBrandFromContext} derives it from the portable `BrandContext`
6
+ * shared across the UI and libraries.
7
+ *
8
+ * Email can't use the `[data-brand]` CSS bridge the browser UI uses (mail
9
+ * clients strip `<style>` blocks and ignore `var()`), so branding is applied
10
+ * by inlining these values at render time. A logo is only emitted when it's
11
+ * a fetchable `http(s):` or `data:` URL - a package-export path (the default
12
+ * asset form) can't resolve in an inbox, so it's dropped rather than shown
13
+ * as a broken image.
14
+ *
15
+ * @module
16
+ */
17
+ import { brand } from "@dbx-tools/shared-core";
18
+ /** Whether `value` is an image reference a mail client can actually load. */
19
+ function isRenderableImageUrl(value) {
20
+ return typeof value === "string" && /^(?:https?:|data:)/i.test(value);
21
+ }
22
+ /**
23
+ * Derive the email-safe {@link EmailBrand} from a full brand context: the
24
+ * primary color as the accent, the sans font stack, the display name, and
25
+ * the dark-surface logo (the header band is dark) when it's a renderable URL.
26
+ */
27
+ export function emailBrandFromContext(context) {
28
+ const logo = context.assets.logo.dark ?? context.assets.logo.light;
29
+ return {
30
+ accent: context.colors.primary,
31
+ onAccent: "#ffffff",
32
+ fontFamily: context.typography.sans,
33
+ name: context.name,
34
+ ...(isRenderableImageUrl(logo) ? { logoUrl: logo } : {}),
35
+ };
36
+ }
37
+ /**
38
+ * The dbx-tools brand as an {@link EmailBrand}, ready to pass to the email
39
+ * plugin (`email({ brand: defaultEmailBrand })`). Convenience so a consumer
40
+ * needs only `@dbx-tools/email`, not the shared brand context, for the
41
+ * common case of the default brand.
42
+ */
43
+ export const defaultEmailBrand = emailBrandFromContext(brand.defaultBrandContext);
44
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnJhbmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYnJhbmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBQ0gsT0FBTyxFQUFFLEtBQUssRUFBcUIsTUFBTSx3QkFBd0IsQ0FBQztBQW9CbEUsNkVBQTZFO0FBQzdFLFNBQVMsb0JBQW9CLENBQUMsS0FBeUI7SUFDckQsT0FBTyxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUkscUJBQXFCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQ3hFLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLE9BQXFCO0lBQ3pELE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksSUFBSSxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDbkUsT0FBTztRQUNMLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDLE9BQU87UUFDOUIsUUFBUSxFQUFFLFNBQVM7UUFDbkIsVUFBVSxFQUFFLE9BQU8sQ0FBQyxVQUFVLENBQUMsSUFBSTtRQUNuQyxJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUk7UUFDbEIsR0FBRyxDQUFDLG9CQUFvQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQ3pELENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBZSxxQkFBcUIsQ0FBQyxLQUFLLENBQUMsbUJBQW1CLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogT3B0aW9uYWwgYnJhbmQgc3R5bGluZyBmb3Igb3V0Ym91bmQgZW1haWwuIHtAbGluayBFbWFpbEJyYW5kfSBpcyB0aGVcbiAqIHNtYWxsLCBlbWFpbC1zYWZlIHNsaWNlIG9mIGEgYnJhbmQgYSBtZXNzYWdlIGFjdHVhbGx5IG5lZWRzIC0gYW4gYWNjZW50XG4gKiBjb2xvciwgYSBmb250IHN0YWNrLCBhIGRpc3BsYXkgbmFtZSwgYW5kIGFuIG9wdGlvbmFsIGxvZ28gaW1hZ2UgLSBhbmRcbiAqIHtAbGluayBlbWFpbEJyYW5kRnJvbUNvbnRleHR9IGRlcml2ZXMgaXQgZnJvbSB0aGUgcG9ydGFibGUgYEJyYW5kQ29udGV4dGBcbiAqIHNoYXJlZCBhY3Jvc3MgdGhlIFVJIGFuZCBsaWJyYXJpZXMuXG4gKlxuICogRW1haWwgY2FuJ3QgdXNlIHRoZSBgW2RhdGEtYnJhbmRdYCBDU1MgYnJpZGdlIHRoZSBicm93c2VyIFVJIHVzZXMgKG1haWxcbiAqIGNsaWVudHMgc3RyaXAgYDxzdHlsZT5gIGJsb2NrcyBhbmQgaWdub3JlIGB2YXIoKWApLCBzbyBicmFuZGluZyBpcyBhcHBsaWVkXG4gKiBieSBpbmxpbmluZyB0aGVzZSB2YWx1ZXMgYXQgcmVuZGVyIHRpbWUuIEEgbG9nbyBpcyBvbmx5IGVtaXR0ZWQgd2hlbiBpdCdzXG4gKiBhIGZldGNoYWJsZSBgaHR0cChzKTpgIG9yIGBkYXRhOmAgVVJMIC0gYSBwYWNrYWdlLWV4cG9ydCBwYXRoICh0aGUgZGVmYXVsdFxuICogYXNzZXQgZm9ybSkgY2FuJ3QgcmVzb2x2ZSBpbiBhbiBpbmJveCwgc28gaXQncyBkcm9wcGVkIHJhdGhlciB0aGFuIHNob3duXG4gKiBhcyBhIGJyb2tlbiBpbWFnZS5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cbmltcG9ydCB7IGJyYW5kLCB0eXBlIEJyYW5kQ29udGV4dCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5cbi8qKiBFbWFpbC1zYWZlIGJyYW5kIHZhbHVlcyBpbmxpbmVkIGludG8gdGhlIG1lc3NhZ2UgbGF5b3V0IGF0IHJlbmRlciB0aW1lLiAqL1xuZXhwb3J0IGludGVyZmFjZSBFbWFpbEJyYW5kIHtcbiAgLyoqIEhlYWRlci1iYW5kIGJhY2tncm91bmQgYW5kIGxpbmsgY29sb3IuICovXG4gIGFjY2VudDogc3RyaW5nO1xuICAvKiogVGV4dCBhbmQgbG9nbyBjb2xvciByZW5kZXJlZCBvbiB0aGUgYWNjZW50IGJhbmQuIERlZmF1bHRzIHRvIHdoaXRlLiAqL1xuICBvbkFjY2VudD86IHN0cmluZztcbiAgLyoqIEJvZHkgZm9udCBzdGFjay4gKi9cbiAgZm9udEZhbWlseTogc3RyaW5nO1xuICAvKiogUHJvZHVjdC9kaXNwbGF5IG5hbWUsIHVzZWQgYXMgdGhlIGhlYWRlciB0ZXh0IGFuZCB0aGUgbG9nbyBgYWx0YC4gKi9cbiAgbmFtZT86IHN0cmluZztcbiAgLyoqXG4gICAqIExvZ28gaW1hZ2UgcmVuZGVyZWQgaW4gdGhlIGhlYWRlciBiYW5kLiBPbmx5IGFuIGBodHRwKHMpOmAgb3IgYGRhdGE6YFxuICAgKiBVUkwgcmVuZGVyczsgb3RoZXIgdmFsdWVzIChlLmcuIGEgcGFja2FnZS1leHBvcnQgcGF0aCkgYXJlIGlnbm9yZWQsXG4gICAqIHNpbmNlIHRoZXkgY2FuJ3QgbG9hZCBpbiBhIG1haWwgY2xpZW50LlxuICAgKi9cbiAgbG9nb1VybD86IHN0cmluZztcbn1cblxuLyoqIFdoZXRoZXIgYHZhbHVlYCBpcyBhbiBpbWFnZSByZWZlcmVuY2UgYSBtYWlsIGNsaWVudCBjYW4gYWN0dWFsbHkgbG9hZC4gKi9cbmZ1bmN0aW9uIGlzUmVuZGVyYWJsZUltYWdlVXJsKHZhbHVlOiBzdHJpbmcgfCB1bmRlZmluZWQpOiB2YWx1ZSBpcyBzdHJpbmcge1xuICByZXR1cm4gdHlwZW9mIHZhbHVlID09PSBcInN0cmluZ1wiICYmIC9eKD86aHR0cHM/OnxkYXRhOikvaS50ZXN0KHZhbHVlKTtcbn1cblxuLyoqXG4gKiBEZXJpdmUgdGhlIGVtYWlsLXNhZmUge0BsaW5rIEVtYWlsQnJhbmR9IGZyb20gYSBmdWxsIGJyYW5kIGNvbnRleHQ6IHRoZVxuICogcHJpbWFyeSBjb2xvciBhcyB0aGUgYWNjZW50LCB0aGUgc2FucyBmb250IHN0YWNrLCB0aGUgZGlzcGxheSBuYW1lLCBhbmRcbiAqIHRoZSBkYXJrLXN1cmZhY2UgbG9nbyAodGhlIGhlYWRlciBiYW5kIGlzIGRhcmspIHdoZW4gaXQncyBhIHJlbmRlcmFibGUgVVJMLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZW1haWxCcmFuZEZyb21Db250ZXh0KGNvbnRleHQ6IEJyYW5kQ29udGV4dCk6IEVtYWlsQnJhbmQge1xuICBjb25zdCBsb2dvID0gY29udGV4dC5hc3NldHMubG9nby5kYXJrID8/IGNvbnRleHQuYXNzZXRzLmxvZ28ubGlnaHQ7XG4gIHJldHVybiB7XG4gICAgYWNjZW50OiBjb250ZXh0LmNvbG9ycy5wcmltYXJ5LFxuICAgIG9uQWNjZW50OiBcIiNmZmZmZmZcIixcbiAgICBmb250RmFtaWx5OiBjb250ZXh0LnR5cG9ncmFwaHkuc2FucyxcbiAgICBuYW1lOiBjb250ZXh0Lm5hbWUsXG4gICAgLi4uKGlzUmVuZGVyYWJsZUltYWdlVXJsKGxvZ28pID8geyBsb2dvVXJsOiBsb2dvIH0gOiB7fSksXG4gIH07XG59XG5cbi8qKlxuICogVGhlIGRieC10b29scyBicmFuZCBhcyBhbiB7QGxpbmsgRW1haWxCcmFuZH0sIHJlYWR5IHRvIHBhc3MgdG8gdGhlIGVtYWlsXG4gKiBwbHVnaW4gKGBlbWFpbCh7IGJyYW5kOiBkZWZhdWx0RW1haWxCcmFuZCB9KWApLiBDb252ZW5pZW5jZSBzbyBhIGNvbnN1bWVyXG4gKiBuZWVkcyBvbmx5IGBAZGJ4LXRvb2xzL2VtYWlsYCwgbm90IHRoZSBzaGFyZWQgYnJhbmQgY29udGV4dCwgZm9yIHRoZVxuICogY29tbW9uIGNhc2Ugb2YgdGhlIGRlZmF1bHQgYnJhbmQuXG4gKi9cbmV4cG9ydCBjb25zdCBkZWZhdWx0RW1haWxCcmFuZDogRW1haWxCcmFuZCA9IGVtYWlsQnJhbmRGcm9tQ29udGV4dChicmFuZC5kZWZhdWx0QnJhbmRDb250ZXh0KTtcbiJdfQ==
@@ -0,0 +1,143 @@
1
+ import { type BasePluginConfig } from "@databricks/appkit";
2
+ import type { JSONSchema7 } from "json-schema";
3
+ import type { EmailBrand } from "./brand.js";
4
+ /** SMTP submission port used when none is configured. */
5
+ export declare const DEFAULT_SMTP_PORT = 587;
6
+ /** SMTP port that implies a TLS-on-connect socket rather than STARTTLS. */
7
+ export declare const IMPLICIT_TLS_SMTP_PORT = 465;
8
+ /**
9
+ * How the sender (`From`) address is restricted.
10
+ *
11
+ * `"allowlist"` is the default and the deny-by-default posture: a send is
12
+ * permitted only from an address the configuration names, either through
13
+ * {@link EmailPluginConfig.allowedSenders} or, when that is empty, through
14
+ * the configured sender source ({@link EmailPluginConfig.from} as an exact
15
+ * address, {@link EmailPluginConfig.domain} as a `*@domain` wildcard).
16
+ *
17
+ * `"unrestricted"` is the explicit opt-out: any `From` a caller supplies is
18
+ * accepted. Only reach for it when an upstream system already vets the
19
+ * sender.
20
+ */
21
+ export type SenderPolicy = "allowlist" | "unrestricted";
22
+ /** SMTP connection + credentials. All fields fall back to env when unset. */
23
+ export interface SmtpConfig {
24
+ /** SMTP server hostname (`SMTP_HOST`). */
25
+ host?: string;
26
+ /** SMTP server port (`SMTP_PORT`). Defaults to {@link DEFAULT_SMTP_PORT}. */
27
+ port?: number;
28
+ /**
29
+ * Use a TLS-on-connect socket (`SMTP_SECURE`). Defaults to
30
+ * `port === ` {@link IMPLICIT_TLS_SMTP_PORT}.
31
+ */
32
+ secure?: boolean;
33
+ /** SMTP auth username (`SMTP_USER`). */
34
+ user?: string;
35
+ /** SMTP auth password / API key (`SMTP_PASSWORD`). */
36
+ password?: string;
37
+ }
38
+ /** AppKit config accepted by the email plugin. */
39
+ export interface EmailPluginConfig extends BasePluginConfig {
40
+ /** SMTP connection + credentials. Omit to run in file/outbox mode. */
41
+ smtp?: SmtpConfig;
42
+ /**
43
+ * Domain used to build the sender address from the on-behalf-of user
44
+ * (`<local-part>@<domain>`). Falls back to `EMAIL_DOMAIN`. Required in
45
+ * SMTP mode unless {@link from} is set; optional in file mode (the
46
+ * outbox falls back to the user's own email address).
47
+ */
48
+ domain?: string;
49
+ /**
50
+ * Explicit `From` address. When set, the sender is used verbatim and
51
+ * the per-user derivation is skipped. Falls back to `EMAIL_FROM`.
52
+ */
53
+ from?: string;
54
+ /**
55
+ * Directory for the file/outbox fallback. Falls back to
56
+ * `EMAIL_OUTBOX_DIR`, then `<cwd>/tmp`. Only used when SMTP
57
+ * credentials are absent.
58
+ */
59
+ outDir?: string;
60
+ /**
61
+ * Allow-list restricting the sender (`From`) address. Each entry is an
62
+ * exact address (`user@domain.com`), a domain wildcard (`*@domain.com`
63
+ * or the bare `domain.com`, matching any local part on that domain), or
64
+ * `*` (any). A resolved / chosen sender that matches no entry is
65
+ * rejected at send time. Accepts a `string[]` or a single comma- /
66
+ * whitespace-separated string; falls back to `EMAIL_ALLOWED_SENDERS`.
67
+ * When empty, the {@link senderPolicy} decides what is permitted.
68
+ */
69
+ allowedSenders?: string | string[];
70
+ /**
71
+ * How the sender address is restricted when {@link allowedSenders} is
72
+ * empty (`EMAIL_SENDER_POLICY`). Defaults to `"allowlist"`, which
73
+ * narrows sends to the configured sender source. See
74
+ * {@link SenderPolicy}.
75
+ */
76
+ senderPolicy?: SenderPolicy;
77
+ /**
78
+ * Optional brand styling (accent, font, header logo) applied to the
79
+ * rendered HTML of every message. Omit for the neutral default layout.
80
+ * Pass {@link emailBrandFromContext} to derive it from a shared
81
+ * `BrandContext`.
82
+ */
83
+ brand?: EmailBrand;
84
+ }
85
+ /** Config shared by both resolved modes. */
86
+ interface ResolvedSender {
87
+ /** Sender domain; present whenever {@link from} is absent. */
88
+ domain?: string;
89
+ /** Explicit sender override; present skips per-user derivation. */
90
+ from?: string;
91
+ /**
92
+ * Effective sender allow-list: the configured patterns when any were
93
+ * given, else the patterns implied by the sender source under an
94
+ * `"allowlist"` policy. Empty means unenforceable (an `"unrestricted"`
95
+ * policy, or an outbox with no sender source to narrow to).
96
+ */
97
+ allowedSenders: string[];
98
+ /** The restriction mode the allow-list was resolved under. */
99
+ senderPolicy: SenderPolicy;
100
+ /** Brand styling applied to rendered HTML; absent for the default layout. */
101
+ brand?: EmailBrand;
102
+ }
103
+ /** Resolved config for real SMTP delivery. */
104
+ export interface ResolvedSmtpConfig extends ResolvedSender {
105
+ mode: "smtp";
106
+ host: string;
107
+ port: number;
108
+ secure: boolean;
109
+ auth: {
110
+ user: string;
111
+ pass: string;
112
+ };
113
+ }
114
+ /** Resolved config for the file/outbox fallback (no SMTP credentials). */
115
+ export interface ResolvedFileConfig extends ResolvedSender {
116
+ mode: "file";
117
+ /** Absolute directory messages are written under. */
118
+ outDir: string;
119
+ }
120
+ /** Concrete, validated config the runtime dispatches through. */
121
+ export type ResolvedEmailConfig = ResolvedSmtpConfig | ResolvedFileConfig;
122
+ /** JSON Schema published on the manifest's `config.schema`. */
123
+ export declare const EMAIL_CONFIG_SCHEMA: JSONSchema7;
124
+ /**
125
+ * Resolve plugin config over environment defaults.
126
+ *
127
+ * When SMTP host + user + password are all present, returns `mode:
128
+ * "smtp"` for real delivery (and throws if no sender source - domain or
129
+ * from - is configured, since SMTP can't derive one from nothing).
130
+ * When all three are absent and `EMAIL_OUTBOX_MODE` is enabled, returns
131
+ * `mode: "file"` so the runtime writes messages to the outbox directory
132
+ * for local testing; in that mode a sender source is optional (the
133
+ * outbox falls back to the OBO user's own address). Partial SMTP
134
+ * configuration or a send with no credentials and no outbox opt-in
135
+ * throws.
136
+ *
137
+ * The sender allow-list is resolved here too: under the default
138
+ * `"allowlist"` policy an empty list is filled in from the sender source,
139
+ * so a deployment that only sets `EMAIL_DOMAIN` still rejects a `From` on
140
+ * any other domain. See {@link SenderPolicy}.
141
+ */
142
+ export declare function resolveEmailConfig(config?: EmailPluginConfig): ResolvedEmailConfig;
143
+ export {};
@@ -0,0 +1,212 @@
1
+ /**
2
+ * SMTP configuration for the email plugin: the typed
3
+ * {@link EmailPluginConfig} (the plugin's slice of AppKit config), the
4
+ * JSON Schema the manifest publishes for it, and {@link resolveEmailConfig}
5
+ * which layers that config over environment defaults into the concrete
6
+ * {@link ResolvedEmailConfig} the runtime needs.
7
+ *
8
+ * Two modes fall out of the resolution. When SMTP credentials (host +
9
+ * user + password) are all present it resolves to `mode: "smtp"` and
10
+ * mail is sent for real. When they are absent and `EMAIL_OUTBOX_MODE`
11
+ * is explicitly enabled it resolves to `mode: "file"` (an "outbox")
12
+ * and each message is written to disk as HTML instead of sent. Any
13
+ * partial SMTP configuration or a send attempt with no credentials and
14
+ * no outbox opt-in throws.
15
+ *
16
+ * Precedence per field: explicit plugin config wins, then the matching
17
+ * environment variable. Env names are unprefixed because the app talks
18
+ * to a single SMTP server (e.g. SMTP2GO): `SMTP_HOST`, `SMTP_PORT`,
19
+ * `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`, plus `EMAIL_DOMAIN` for
20
+ * the derived sender's domain, `EMAIL_FROM` for an explicit override,
21
+ * `EMAIL_SENDER_POLICY` for the sender restriction mode, and
22
+ * `EMAIL_OUTBOX_DIR` for the outbox directory.
23
+ *
24
+ * @module
25
+ */
26
+ import { resolve } from "node:path";
27
+ import { ConfigurationError, ValidationError } from "@databricks/appkit";
28
+ import { object } from "@dbx-tools/shared-core";
29
+ import { parseAllowedSenders } from "./sender.js";
30
+ /** SMTP submission port used when none is configured. */
31
+ export const DEFAULT_SMTP_PORT = 587;
32
+ /** SMTP port that implies a TLS-on-connect socket rather than STARTTLS. */
33
+ export const IMPLICIT_TLS_SMTP_PORT = 465;
34
+ /** JSON Schema published on the manifest's `config.schema`. */
35
+ export const EMAIL_CONFIG_SCHEMA = {
36
+ type: "object",
37
+ properties: {
38
+ smtp: {
39
+ type: "object",
40
+ description: "SMTP connection and credentials (env fallbacks: SMTP_*).",
41
+ properties: {
42
+ host: { type: "string", description: "SMTP server hostname (SMTP_HOST)." },
43
+ port: {
44
+ type: "number",
45
+ description: `SMTP server port (SMTP_PORT). Defaults to ${DEFAULT_SMTP_PORT}.`,
46
+ },
47
+ secure: {
48
+ type: "boolean",
49
+ description: `TLS-on-connect socket (SMTP_SECURE). Defaults to port === ${IMPLICIT_TLS_SMTP_PORT}.`,
50
+ },
51
+ user: { type: "string", description: "SMTP auth username (SMTP_USER)." },
52
+ password: {
53
+ type: "string",
54
+ description: "SMTP auth password / API key (SMTP_PASSWORD).",
55
+ },
56
+ },
57
+ },
58
+ domain: {
59
+ type: "string",
60
+ description: "Domain for the derived sender address (<user-local-part>@<domain>). Falls back to EMAIL_DOMAIN.",
61
+ },
62
+ from: {
63
+ type: "string",
64
+ description: "Explicit From address; skips per-user derivation. Falls back to EMAIL_FROM.",
65
+ },
66
+ outDir: {
67
+ type: "string",
68
+ description: "Directory for the file/outbox fallback when SMTP is unconfigured. Falls back to EMAIL_OUTBOX_DIR, then <cwd>/tmp.",
69
+ },
70
+ allowedSenders: {
71
+ type: "array",
72
+ items: { type: "string" },
73
+ description: 'Allow-list of permitted sender (From) patterns: exact addresses ("user@domain.com"), domain wildcards ("*@domain.com"), or "*". Also accepts a comma/space-separated string. Falls back to EMAIL_ALLOWED_SENDERS. When empty, senderPolicy decides.',
74
+ },
75
+ senderPolicy: {
76
+ type: "string",
77
+ enum: ["allowlist", "unrestricted"],
78
+ description: 'How the sender is restricted when allowedSenders is empty (EMAIL_SENDER_POLICY). "allowlist" (default) narrows sends to the configured sender source; "unrestricted" accepts any From.',
79
+ },
80
+ brand: {
81
+ type: "object",
82
+ description: "Brand styling inlined into every rendered message. Omit for the neutral default layout.",
83
+ properties: {
84
+ accent: {
85
+ type: "string",
86
+ description: 'Header-band background and body link color (e.g. "#FF3621").',
87
+ },
88
+ onAccent: {
89
+ type: "string",
90
+ description: "Text and logo color rendered on the accent band. Defaults to white.",
91
+ },
92
+ fontFamily: {
93
+ type: "string",
94
+ description: 'Body font stack (e.g. "Inter, ui-sans-serif, system-ui, sans-serif").',
95
+ },
96
+ name: {
97
+ type: "string",
98
+ description: "Product / display name used as the header text and the logo alt text.",
99
+ },
100
+ logoUrl: {
101
+ type: "string",
102
+ description: "Logo image for the header band. Only an http(s): or data: URL renders; other values are dropped because a mail client cannot load them.",
103
+ },
104
+ },
105
+ required: ["accent", "fontFamily"],
106
+ },
107
+ },
108
+ };
109
+ /** Parse the `SMTP_SECURE` env / config flag, defaulting by port. */
110
+ function resolveSecure(flag, port) {
111
+ if (typeof flag === "boolean")
112
+ return flag;
113
+ return object.toBoolean(process.env.SMTP_SECURE) ?? port === IMPLICIT_TLS_SMTP_PORT;
114
+ }
115
+ /** Parse the `EMAIL_SENDER_POLICY` env / config value, defaulting to deny-by-default. */
116
+ function resolveSenderPolicy(policy) {
117
+ const raw = policy ?? process.env.EMAIL_SENDER_POLICY?.trim().toLowerCase();
118
+ if (raw === "unrestricted")
119
+ return "unrestricted";
120
+ if (raw === undefined || raw === "" || raw === "allowlist")
121
+ return "allowlist";
122
+ throw ValidationError.invalidValue("senderPolicy", raw, '"allowlist" or "unrestricted"');
123
+ }
124
+ /**
125
+ * Effective allow-list for an `"allowlist"` policy with no explicit patterns:
126
+ * the sender source itself, so a configured domain or fixed address is the
127
+ * boundary rather than "anything goes". Yields nothing when neither is set,
128
+ * which only happens in outbox mode (where the sender falls back to the
129
+ * on-behalf-of user's own address and cannot be enumerated up front).
130
+ */
131
+ function impliedSenderPatterns(domain, from) {
132
+ const patterns = [];
133
+ if (from)
134
+ patterns.push(from.trim().toLowerCase());
135
+ if (domain)
136
+ patterns.push(`*@${domain.trim().toLowerCase()}`);
137
+ return patterns;
138
+ }
139
+ /** Whether `EMAIL_OUTBOX_MODE` explicitly opts into the file/outbox fallback. */
140
+ function isOutboxModeEnabled() {
141
+ return object.toBoolean(process.env.EMAIL_OUTBOX_MODE) ?? false;
142
+ }
143
+ const SMTP_REQUIRED_FIELDS = ["SMTP_HOST", "SMTP_USER", "SMTP_PASSWORD"];
144
+ /** List env keys for SMTP fields that are unset in the resolved credential set. */
145
+ function missingSmtpFields(host, user, pass) {
146
+ const values = [host, user, pass];
147
+ return SMTP_REQUIRED_FIELDS.filter((_, index) => !values[index]);
148
+ }
149
+ /**
150
+ * Resolve plugin config over environment defaults.
151
+ *
152
+ * When SMTP host + user + password are all present, returns `mode:
153
+ * "smtp"` for real delivery (and throws if no sender source - domain or
154
+ * from - is configured, since SMTP can't derive one from nothing).
155
+ * When all three are absent and `EMAIL_OUTBOX_MODE` is enabled, returns
156
+ * `mode: "file"` so the runtime writes messages to the outbox directory
157
+ * for local testing; in that mode a sender source is optional (the
158
+ * outbox falls back to the OBO user's own address). Partial SMTP
159
+ * configuration or a send with no credentials and no outbox opt-in
160
+ * throws.
161
+ *
162
+ * The sender allow-list is resolved here too: under the default
163
+ * `"allowlist"` policy an empty list is filled in from the sender source,
164
+ * so a deployment that only sets `EMAIL_DOMAIN` still rejects a `From` on
165
+ * any other domain. See {@link SenderPolicy}.
166
+ */
167
+ export function resolveEmailConfig(config = {}) {
168
+ const smtp = config.smtp ?? {};
169
+ const host = smtp.host ?? process.env.SMTP_HOST;
170
+ const user = smtp.user ?? process.env.SMTP_USER;
171
+ const pass = smtp.password ?? process.env.SMTP_PASSWORD;
172
+ const domain = config.domain ?? process.env.EMAIL_DOMAIN;
173
+ const from = config.from ?? process.env.EMAIL_FROM;
174
+ const senderPolicy = resolveSenderPolicy(config.senderPolicy);
175
+ const configuredSenders = parseAllowedSenders(config.allowedSenders ?? process.env.EMAIL_ALLOWED_SENDERS);
176
+ const allowedSenders = configuredSenders.length > 0 || senderPolicy === "unrestricted"
177
+ ? configuredSenders
178
+ : impliedSenderPatterns(domain, from);
179
+ const sender = {
180
+ ...(domain ? { domain } : {}),
181
+ ...(from ? { from } : {}),
182
+ allowedSenders,
183
+ senderPolicy,
184
+ ...(config.brand ? { brand: config.brand } : {}),
185
+ };
186
+ const hasAllSmtp = Boolean(host && user && pass);
187
+ const hasAnySmtp = Boolean(host || user || pass);
188
+ if (hasAnySmtp && !hasAllSmtp) {
189
+ throw ValidationError.missingEnvVars(missingSmtpFields(host, user, pass));
190
+ }
191
+ if (hasAllSmtp) {
192
+ if (!domain && !from) {
193
+ throw ConfigurationError.resourceNotFound("Email sender source", "Set EMAIL_DOMAIN to derive <user-local-part>@<domain>, or EMAIL_FROM for a fixed address.");
194
+ }
195
+ const portRaw = smtp.port ?? Number(process.env.SMTP_PORT);
196
+ const port = Number.isFinite(portRaw) && portRaw ? Number(portRaw) : DEFAULT_SMTP_PORT;
197
+ return {
198
+ mode: "smtp",
199
+ host: host,
200
+ port,
201
+ secure: resolveSecure(smtp.secure, port),
202
+ auth: { user: user, pass: pass },
203
+ ...sender,
204
+ };
205
+ }
206
+ if (!isOutboxModeEnabled()) {
207
+ throw ConfigurationError.invalidConnection("SMTP", `Set ${SMTP_REQUIRED_FIELDS.join(", ")}, or EMAIL_OUTBOX_MODE=1 to write messages to a local outbox instead.`);
208
+ }
209
+ const outDir = resolve(config.outDir ?? process.env.EMAIL_OUTBOX_DIR ?? resolve(process.cwd(), "tmp"));
210
+ return { mode: "file", outDir, ...sender };
211
+ }
212
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBQ0gsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNwQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsZUFBZSxFQUF5QixNQUFNLG9CQUFvQixDQUFDO0FBQ2hHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUdoRCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFFL0MseURBQXlEO0FBQ3pELE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLEdBQUcsQ0FBQztBQUVyQywyRUFBMkU7QUFDM0UsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsR0FBRyxDQUFDO0FBd0gxQywrREFBK0Q7QUFDL0QsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQWdCO0lBQzlDLElBQUksRUFBRSxRQUFRO0lBQ2QsVUFBVSxFQUFFO1FBQ1YsSUFBSSxFQUFFO1lBQ0osSUFBSSxFQUFFLFFBQVE7WUFDZCxXQUFXLEVBQUUsMERBQTBEO1lBQ3ZFLFVBQVUsRUFBRTtnQkFDVixJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxtQ0FBbUMsRUFBRTtnQkFDMUUsSUFBSSxFQUFFO29CQUNKLElBQUksRUFBRSxRQUFRO29CQUNkLFdBQVcsRUFBRSw2Q0FBNkMsaUJBQWlCLEdBQUc7aUJBQy9FO2dCQUNELE1BQU0sRUFBRTtvQkFDTixJQUFJLEVBQUUsU0FBUztvQkFDZixXQUFXLEVBQUUsNkRBQTZELHNCQUFzQixHQUFHO2lCQUNwRztnQkFDRCxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxpQ0FBaUMsRUFBRTtnQkFDeEUsUUFBUSxFQUFFO29CQUNSLElB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bmZpZ3VyYXRpb24gZm9yIHRoZSBlbWFpbCBwbHVnaW46IHRoZSB0eXBlZFxuICoge0BsaW5rIEVtYWlsUGx1Z2luQ29uZmlnfSAodGhlIHBsdWdpbidzIHNsaWNlIG9mIEFwcEtpdCBjb25maWcpLCB0aGVcbiAqIEpTT04gU2NoZW1hIHRoZSBtYW5pZmVzdCBwdWJsaXNoZXMgZm9yIGl0LCBhbmQge0BsaW5rIHJlc29sdmVFbWFpbENvbmZpZ31cbiAqIHdoaWNoIGxheWVycyB0aGF0IGNvbmZpZyBvdmVyIGVudmlyb25tZW50IGRlZmF1bHRzIGludG8gdGhlIGNvbmNyZXRlXG4gKiB7QGxpbmsgUmVzb2x2ZWRFbWFpbENvbmZpZ30gdGhlIHJ1bnRpbWUgbmVlZHMuXG4gKlxuICogVHdvIG1vZGVzIGZhbGwgb3V0IG9mIHRoZSByZXNvbHV0aW9uLiBXaGVuIFNNVFAgY3JlZGVudGlhbHMgKGhvc3QgK1xuICogdXNlciArIHBhc3N3b3JkKSBhcmUgYWxsIHByZXNlbnQgaXQgcmVzb2x2ZXMgdG8gYG1vZGU6IFwic210cFwiYCBhbmRcbiAqIG1haWwgaXMgc2VudCBmb3IgcmVhbC4gV2hlbiB0aGV5IGFyZSBhYnNlbnQgYW5kIGBFTUFJTF9PVVRCT1hfTU9ERWBcbiAqIGlzIGV4cGxpY2l0bHkgZW5hYmxlZCBpdCByZXNvbHZlcyB0byBgbW9kZTogXCJmaWxlXCJgIChhbiBcIm91dGJveFwiKVxuICogYW5kIGVhY2ggbWVzc2FnZSBpcyB3cml0dGVuIHRvIGRpc2sgYXMgSFRNTCBpbnN0ZWFkIG9mIHNlbnQuIEFueVxuICogcGFydGlhbCBTTVRQIGNvbmZpZ3VyYXRpb24gb3IgYSBzZW5kIGF0dGVtcHQgd2l0aCBubyBjcmVkZW50aWFscyBhbmRcbiAqIG5vIG91dGJveCBvcHQtaW4gdGhyb3dzLlxuICpcbiAqIFByZWNlZGVuY2UgcGVyIGZpZWxkOiBleHBsaWNpdCBwbHVnaW4gY29uZmlnIHdpbnMsIHRoZW4gdGhlIG1hdGNoaW5nXG4gKiBlbnZpcm9ubWVudCB2YXJpYWJsZS4gRW52IG5hbWVzIGFyZSB1bnByZWZpeGVkIGJlY2F1c2UgdGhlIGFwcCB0YWxrc1xuICogdG8gYSBzaW5nbGUgU01UUCBzZXJ2ZXIgKGUuZy4gU01UUDJHTyk6IGBTTVRQX0hPU1RgLCBgU01UUF9QT1JUYCxcbiAqIGBTTVRQX1NFQ1VSRWAsIGBTTVRQX1VTRVJgLCBgU01UUF9QQVNTV09SRGAsIHBsdXMgYEVNQUlMX0RPTUFJTmAgZm9yXG4gKiB0aGUgZGVyaXZlZCBzZW5kZXIncyBkb21haW4sIGBFTUFJTF9GUk9NYCBmb3IgYW4gZXhwbGljaXQgb3ZlcnJpZGUsXG4gKiBgRU1BSUxfU0VOREVSX1BPTElDWWAgZm9yIHRoZSBzZW5kZXIgcmVzdHJpY3Rpb24gbW9kZSwgYW5kXG4gKiBgRU1BSUxfT1VUQk9YX0RJUmAgZm9yIHRoZSBvdXRib3ggZGlyZWN0b3J5LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuaW1wb3J0IHsgcmVzb2x2ZSB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IENvbmZpZ3VyYXRpb25FcnJvciwgVmFsaWRhdGlvbkVycm9yLCB0eXBlIEJhc2VQbHVnaW5Db25maWcgfSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0XCI7XG5pbXBvcnQgeyBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHR5cGUgeyBKU09OU2NoZW1hNyB9IGZyb20gXCJqc29uLXNjaGVtYVwiO1xuaW1wb3J0IHR5cGUgeyBFbWFpbEJyYW5kIH0gZnJvbSBcIi4vYnJhbmRcIjtcbmltcG9ydCB7IHBhcnNlQWxsb3dlZFNlbmRlcnMgfSBmcm9tIFwiLi9zZW5kZXJcIjtcblxuLyoqIFNNVFAgc3VibWlzc2lvbiBwb3J0IHVzZWQgd2hlbiBub25lIGlzIGNvbmZpZ3VyZWQuICovXG5leHBvcnQgY29uc3QgREVGQVVMVF9TTVRQX1BPUlQgPSA1ODc7XG5cbi8qKiBTTVRQIHBvcnQgdGhhdCBpbXBsaWVzIGEgVExTLW9uLWNvbm5lY3Qgc29ja2V0IHJhdGhlciB0aGFuIFNUQVJUVExTLiAqL1xuZXhwb3J0IGNvbnN0IElNUExJQ0lUX1RMU19TTVRQX1BPUlQgPSA0NjU7XG5cbi8qKlxuICogSG93IHRoZSBzZW5kZXIgKGBGcm9tYCkgYWRkcmVzcyBpcyByZXN0cmljdGVkLlxuICpcbiAqIGBcImFsbG93bGlzdFwiYCBpcyB0aGUgZGVmYXVsdCBhbmQgdGhlIGRlbnktYnktZGVmYXVsdCBwb3N0dXJlOiBhIHNlbmQgaXNcbiAqIHBlcm1pdHRlZCBvbmx5IGZyb20gYW4gYWRkcmVzcyB0aGUgY29uZmlndXJhdGlvbiBuYW1lcywgZWl0aGVyIHRocm91Z2hcbiAqIHtAbGluayBFbWFpbFBsdWdpbkNvbmZpZy5hbGxvd2VkU2VuZGVyc30gb3IsIHdoZW4gdGhhdCBpcyBlbXB0eSwgdGhyb3VnaFxuICogdGhlIGNvbmZpZ3VyZWQgc2VuZGVyIHNvdXJjZSAoe0BsaW5rIEVtYWlsUGx1Z2luQ29uZmlnLmZyb219IGFzIGFuIGV4YWN0XG4gKiBhZGRyZXNzLCB7QGxpbmsgRW1haWxQbHVnaW5Db25maWcuZG9tYWlufSBhcyBhIGAqQGRvbWFpbmAgd2lsZGNhcmQpLlxuICpcbiAqIGBcInVucmVzdHJpY3RlZFwiYCBpcyB0aGUgZXhwbGljaXQgb3B0LW91dDogYW55IGBGcm9tYCBhIGNhbGxlciBzdXBwbGllcyBpc1xuICogYWNjZXB0ZWQuIE9ubHkgcmVhY2ggZm9yIGl0IHdoZW4gYW4gdXBzdHJlYW0gc3lzdGVtIGFscmVhZHkgdmV0cyB0aGVcbiAqIHNlbmRlci5cbiAqL1xuZXhwb3J0IHR5cGUgU2VuZGVyUG9saWN5ID0gXCJhbGxvd2xpc3RcIiB8IFwidW5yZXN0cmljdGVkXCI7XG5cbi8qKiBTTVRQIGNvbm5lY3Rpb24gKyBjcmVkZW50aWFscy4gQWxsIGZpZWxkcyBmYWxsIGJhY2sgdG8gZW52IHdoZW4gdW5zZXQuICovXG5leHBvcnQgaW50ZXJmYWNlIFNtdHBDb25maWcge1xuICAvKiogU01UUCBzZXJ2ZXIgaG9zdG5hbWUgKGBTTVRQX0hPU1RgKS4gKi9cbiAgaG9zdD86IHN0cmluZztcbiAgLyoqIFNNVFAgc2VydmVyIHBvcnQgKGBTTVRQX1BPUlRgKS4gRGVmYXVsdHMgdG8ge0BsaW5rIERFRkFVTFRfU01UUF9QT1JUfS4gKi9cbiAgcG9ydD86IG51bWJlcjtcbiAgLyoqXG4gICAqIFVzZSBhIFRMUy1vbi1jb25uZWN0IHNvY2tldCAoYFNNVFBfU0VDVVJFYCkuIERlZmF1bHRzIHRvXG4gICAqIGBwb3J0ID09PSBgIHtAbGluayBJTVBMSUNJVF9UTFNfU01UUF9QT1JUfS5cbiAgICovXG4gIHNlY3VyZT86IGJvb2xlYW47XG4gIC8qKiBTTVRQIGF1dGggdXNlcm5hbWUgKGBTTVRQX1VTRVJgKS4gKi9cbiAgdXNlcj86IHN0cmluZztcbiAgLyoqIFNNVFAgYXV0aCBwYXNzd29yZCAvIEFQSSBrZXkgKGBTTVRQX1BBU1NXT1JEYCkuICovXG4gIHBhc3N3b3JkPzogc3RyaW5nO1xufVxuXG4vKiogQXBwS2l0IGNvbmZpZyBhY2NlcHRlZCBieSB0aGUgZW1haWwgcGx1Z2luLiAqL1xuZXhwb3J0IGludGVyZmFjZSBFbWFpbFBsdWdpbkNvbmZpZyBleHRlbmRzIEJhc2VQbHVnaW5Db25maWcge1xuICAvKiogU01UUCBjb25uZWN0aW9uICsgY3JlZGVudGlhbHMuIE9taXQgdG8gcnVuIGluIGZpbGUvb3V0Ym94IG1vZGUuICovXG4gIHNtdHA/OiBTbXRwQ29uZmlnO1xuICAvKipcbiAgICogRG9tYWluIHVzZWQgdG8gYnVpbGQgdGhlIHNlbmRlciBhZGRyZXNzIGZyb20gdGhlIG9uLWJlaGFsZi1vZiB1c2VyXG4gICAqIChgPGxvY2FsLXBhcnQ+QDxkb21haW4+YCkuIEZhbGxzIGJhY2sgdG8gYEVNQUlMX0RPTUFJTmAuIFJlcXVpcmVkIGluXG4gICAqIFNNVFAgbW9kZSB1bmxlc3Mge0BsaW5rIGZyb219IGlzIHNldDsgb3B0aW9uYWwgaW4gZmlsZSBtb2RlICh0aGVcbiAgICogb3V0Ym94IGZhbGxzIGJhY2sgdG8gdGhlIHVzZXIncyBvd24gZW1haWwgYWRkcmVzcykuXG4gICAqL1xuICBkb21haW4/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBFeHBsaWNpdCBgRnJvbWAgYWRkcmVzcy4gV2hlbiBzZXQsIHRoZSBzZW5kZXIgaXMgdXNlZCB2ZXJiYXRpbSBhbmRcbiAgICogdGhlIHBlci11c2VyIGRlcml2YXRpb24gaXMgc2tpcHBlZC4gRmFsbHMgYmFjayB0byBgRU1BSUxfRlJPTWAuXG4gICAqL1xuICBmcm9tPzogc3RyaW5nO1xuICAvKipcbiAgICogRGlyZWN0b3J5IGZvciB0aGUgZmlsZS9vdXRib3ggZmFsbGJhY2suIEZhbGxzIGJhY2sgdG9cbiAgICogYEVNQUlMX09VVEJPWF9ESVJgLCB0aGVuIGA8Y3dkPi90bXBgLiBPbmx5IHVzZWQgd2hlbiBTTVRQXG4gICAqIGNyZWRlbnRpYWxzIGFyZSBhYnNlbnQuXG4gICAqL1xuICBvdXREaXI/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBBbGxvdy1saXN0IHJlc3RyaWN0aW5nIHRoZSBzZW5kZXIgKGBGcm9tYCkgYWRkcmVzcy4gRWFjaCBlbnRyeSBpcyBhblxuICAgKiBleGFjdCBhZGRyZXNzIChgdXNlckBkb21haW4uY29tYCksIGEgZG9tYWluIHdpbGRjYXJkIChgKkBkb21haW4uY29tYFxuICAgKiBvciB0aGUgYmFyZSBgZG9tYWluLmNvbWAsIG1hdGNoaW5nIGFueSBsb2NhbCBwYXJ0IG9uIHRoYXQgZG9tYWluKSwgb3JcbiAgICogYCpgIChhbnkpLiBBIHJlc29sdmVkIC8gY2hvc2VuIHNlbmRlciB0aGF0IG1hdGNoZXMgbm8gZW50cnkgaXNcbiAgICogcmVqZWN0ZWQgYXQgc2VuZCB0aW1lLiBBY2NlcHRzIGEgYHN0cmluZ1tdYCBvciBhIHNpbmdsZSBjb21tYS0gL1xuICAgKiB3aGl0ZXNwYWNlLXNlcGFyYXRlZCBzdHJpbmc7IGZhbGxzIGJhY2sgdG8gYEVNQUlMX0FMTE9XRURfU0VOREVSU2AuXG4gICAqIFdoZW4gZW1wdHksIHRoZSB7QGxpbmsgc2VuZGVyUG9saWN5fSBkZWNpZGVzIHdoYXQgaXMgcGVybWl0dGVkLlxuICAgKi9cbiAgYWxsb3dlZFNlbmRlcnM/OiBzdHJpbmcgfCBzdHJpbmdbXTtcbiAgLyoqXG4gICAqIEhvdyB0aGUgc2VuZGVyIGFkZHJlc3MgaXMgcmVzdHJpY3RlZCB3aGVuIHtAbGluayBhbGxvd2VkU2VuZGVyc30gaXNcbiAgICogZW1wdHkgKGBFTUFJTF9TRU5ERVJfUE9MSUNZYCkuIERlZmF1bHRzIHRvIGBcImFsbG93bGlzdFwiYCwgd2hpY2hcbiAgICogbmFycm93cyBzZW5kcyB0byB0aGUgY29uZmlndXJlZCBzZW5kZXIgc291cmNlLiBTZWVcbiAgICoge0BsaW5rIFNlbmRlclBvbGljeX0uXG4gICAqL1xuICBzZW5kZXJQb2xpY3k/OiBTZW5kZXJQb2xpY3k7XG4gIC8qKlxuICAgKiBPcHRpb25hbCBicmFuZCBzdHlsaW5nIChhY2NlbnQsIGZvbnQsIGhlYWRlciBsb2dvKSBhcHBsaWVkIHRvIHRoZVxuICAgKiByZW5kZXJlZCBIVE1MIG9mIGV2ZXJ5IG1lc3NhZ2UuIE9taXQgZm9yIHRoZSBuZXV0cmFsIGRlZmF1bHQgbGF5b3V0LlxuICAgKiBQYXNzIHtAbGluayBlbWFpbEJyYW5kRnJvbUNvbnRleHR9IHRvIGRlcml2ZSBpdCBmcm9tIGEgc2hhcmVkXG4gICAqIGBCcmFuZENvbnRleHRgLlxuICAgKi9cbiAgYnJhbmQ/OiBFbWFpbEJyYW5kO1xufVxuXG4vKiogQ29uZmlnIHNoYXJlZCBieSBib3RoIHJlc29sdmVkIG1vZGVzLiAqL1xuaW50ZXJmYWNlIFJlc29sdmVkU2VuZGVyIHtcbiAgLyoqIFNlbmRlciBkb21haW47IHByZXNlbnQgd2hlbmV2ZXIge0BsaW5rIGZyb219IGlzIGFic2VudC4gKi9cbiAgZG9tYWluPzogc3RyaW5nO1xuICAvKiogRXhwbGljaXQgc2VuZGVyIG92ZXJyaWRlOyBwcmVzZW50IHNraXBzIHBlci11c2VyIGRlcml2YXRpb24uICovXG4gIGZyb20/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBFZmZlY3RpdmUgc2VuZGVyIGFsbG93LWxpc3Q6IHRoZSBjb25maWd1cmVkIHBhdHRlcm5zIHdoZW4gYW55IHdlcmVcbiAgICogZ2l2ZW4sIGVsc2UgdGhlIHBhdHRlcm5zIGltcGxpZWQgYnkgdGhlIHNlbmRlciBzb3VyY2UgdW5kZXIgYW5cbiAgICogYFwiYWxsb3dsaXN0XCJgIHBvbGljeS4gRW1wdHkgbWVhbnMgdW5lbmZvcmNlYWJsZSAoYW4gYFwidW5yZXN0cmljdGVkXCJgXG4gICAqIHBvbGljeSwgb3IgYW4gb3V0Ym94IHdpdGggbm8gc2VuZGVyIHNvdXJjZSB0byBuYXJyb3cgdG8pLlxuICAgKi9cbiAgYWxsb3dlZFNlbmRlcnM6IHN0cmluZ1tdO1xuICAvKiogVGhlIHJlc3RyaWN0aW9uIG1vZGUgdGhlIGFsbG93LWxpc3Qgd2FzIHJlc29sdmVkIHVuZGVyLiAqL1xuICBzZW5kZXJQb2xpY3k6IFNlbmRlclBvbGljeTtcbiAgLyoqIEJyYW5kIHN0eWxpbmcgYXBwbGllZCB0byByZW5kZXJlZCBIVE1MOyBhYnNlbnQgZm9yIHRoZSBkZWZhdWx0IGxheW91dC4gKi9cbiAgYnJhbmQ/OiBFbWFpbEJyYW5kO1xufVxuXG4vKiogUmVzb2x2ZWQgY29uZmlnIGZvciByZWFsIFNNVFAgZGVsaXZlcnkuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVkU210cENvbmZpZyBleHRlbmRzIFJlc29sdmVkU2VuZGVyIHtcbiAgbW9kZTogXCJzbXRwXCI7XG4gIGhvc3Q6IHN0cmluZztcbiAgcG9ydDogbnVtYmVyO1xuICBzZWN1cmU6IGJvb2xlYW47XG4gIGF1dGg6IHsgdXNlcjogc3RyaW5nOyBwYXNzOiBzdHJpbmcgfTtcbn1cblxuLyoqIFJlc29sdmVkIGNvbmZpZyBmb3IgdGhlIGZpbGUvb3V0Ym94IGZhbGxiYWNrIChubyBTTVRQIGNyZWRlbnRpYWxzKS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRGaWxlQ29uZmlnIGV4dGVuZHMgUmVzb2x2ZWRTZW5kZXIge1xuICBtb2RlOiBcImZpbGVcIjtcbiAgLyoqIEFic29sdXRlIGRpcmVjdG9yeSBtZXNzYWdlcyBhcmUgd3JpdHRlbiB1bmRlci4gKi9cbiAgb3V0RGlyOiBzdHJpbmc7XG59XG5cbi8qKiBDb25jcmV0ZSwgdmFsaWRhdGVkIGNvbmZpZyB0aGUgcnVudGltZSBkaXNwYXRjaGVzIHRocm91Z2guICovXG5leHBvcnQgdHlwZSBSZXNvbHZlZEVtYWlsQ29uZmlnID0gUmVzb2x2ZWRTbXRwQ29uZmlnIHwgUmVzb2x2ZWRGaWxlQ29uZmlnO1xuXG4vKiogSlNPTiBTY2hlbWEgcHVibGlzaGVkIG9uIHRoZSBtYW5pZmVzdCdzIGBjb25maWcuc2NoZW1hYC4gKi9cbmV4cG9ydCBjb25zdCBFTUFJTF9DT05GSUdfU0NIRU1BOiBKU09OU2NoZW1hNyA9IHtcbiAgdHlwZTogXCJvYmplY3RcIixcbiAgcHJvcGVydGllczoge1xuICAgIHNtdHA6IHtcbiAgICAgIHR5cGU6IFwib2JqZWN0XCIsXG4gICAgICBkZXNjcmlwdGlvbjogXCJTTVRQIGNvbm5lY3Rpb24gYW5kIGNyZWRlbnRpYWxzIChlbnYgZmFsbGJhY2tzOiBTTVRQXyopLlwiLFxuICAgICAgcHJvcGVydGllczoge1xuICAgICAgICBob3N0OiB7IHR5cGU6IFwic3RyaW5nXCIsIGRlc2NyaXB0aW9uOiBcIlNNVFAgc2VydmVyIGhvc3RuYW1lIChTTVRQX0hPU1QpLlwiIH0sXG4gICAgICAgIHBvcnQ6IHtcbiAgICAgICAgICB0eXBlOiBcIm51bWJlclwiLFxuICAgICAgICAgIGRlc2NyaXB0aW9uOiBgU01UUCBzZXJ2ZXIgcG9ydCAoU01UUF9QT1JUKS4gRGVmYXVsdHMgdG8gJHtERUZBVUxUX1NNVFBfUE9SVH0uYCxcbiAgICAgICAgfSxcbiAgICAgICAgc2VjdXJlOiB7XG4gICAgICAgICAgdHlwZTogXCJib29sZWFuXCIsXG4gICAgICAgICAgZGVzY3JpcHRpb246IGBUTFMtb24tY29ubmVjdCBzb2NrZXQgKFNNVFBfU0VDVVJFKS4gRGVmYXVsdHMgdG8gcG9ydCA9PT0gJHtJTVBMSUNJVF9UTFNfU01UUF9QT1JUfS5gLFxuICAgICAgICB9LFxuICAgICAgICB1c2VyOiB7IHR5cGU6IFwic3RyaW5nXCIsIGRlc2NyaXB0aW9uOiBcIlNNVFAgYXV0aCB1c2VybmFtZSAoU01UUF9VU0VSKS5cIiB9LFxuICAgICAgICBwYXNzd29yZDoge1xuICAgICAgICAgIHR5cGU6IFwic3RyaW5nXCIsXG4gICAgICAgICAgZGVzY3JpcHRpb246IFwiU01UUCBhdXRoIHBhc3N3b3JkIC8gQVBJIGtleSAoU01UUF9QQVNTV09SRCkuXCIsXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgIH0sXG4gICAgZG9tYWluOiB7XG4gICAgICB0eXBlOiBcInN0cmluZ1wiLFxuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgIFwiRG9tYWluIGZvciB0aGUgZGVyaXZlZCBzZW5kZXIgYWRkcmVzcyAoPHVzZXItbG9jYWwtcGFydD5APGRvbWFpbj4pLiBGYWxscyBiYWNrIHRvIEVNQUlMX0RPTUFJTi5cIixcbiAgICB9LFxuICAgIGZyb206IHtcbiAgICAgIHR5cGU6IFwic3RyaW5nXCIsXG4gICAgICBkZXNjcmlwdGlvbjogXCJFeHBsaWNpdCBGcm9tIGFkZHJlc3M7IHNraXBzIHBlci11c2VyIGRlcml2YXRpb24uIEZhbGxzIGJhY2sgdG8gRU1BSUxfRlJPTS5cIixcbiAgICB9LFxuICAgIG91dERpcjoge1xuICAgICAgdHlwZTogXCJzdHJpbmdcIixcbiAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICBcIkRpcmVjdG9yeSBmb3IgdGhlIGZpbGUvb3V0Ym94IGZhbGxiYWNrIHdoZW4gU01UUCBpcyB1bmNvbmZpZ3VyZWQuIEZhbGxzIGJhY2sgdG8gRU1BSUxfT1VUQk9YX0RJUiwgdGhlbiA8Y3dkPi90bXAuXCIsXG4gICAgfSxcbiAgICBhbGxvd2VkU2VuZGVyczoge1xuICAgICAgdHlwZTogXCJhcnJheVwiLFxuICAgICAgaXRlbXM6IHsgdHlwZTogXCJzdHJpbmdcIiB9LFxuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdBbGxvdy1saXN0IG9mIHBlcm1pdHRlZCBzZW5kZXIgKEZyb20pIHBhdHRlcm5zOiBleGFjdCBhZGRyZXNzZXMgKFwidXNlckBkb21haW4uY29tXCIpLCBkb21haW4gd2lsZGNhcmRzIChcIipAZG9tYWluLmNvbVwiKSwgb3IgXCIqXCIuIEFsc28gYWNjZXB0cyBhIGNvbW1hL3NwYWNlLXNlcGFyYXRlZCBzdHJpbmcuIEZhbGxzIGJhY2sgdG8gRU1BSUxfQUxMT1dFRF9TRU5ERVJTLiBXaGVuIGVtcHR5LCBzZW5kZXJQb2xpY3kgZGVjaWRlcy4nLFxuICAgIH0sXG4gICAgc2VuZGVyUG9saWN5OiB7XG4gICAgICB0eXBlOiBcInN0cmluZ1wiLFxuICAgICAgZW51bTogW1wiYWxsb3dsaXN0XCIsIFwidW5yZXN0cmljdGVkXCJdLFxuICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICdIb3cgdGhlIHNlbmRlciBpcyByZXN0cmljdGVkIHdoZW4gYWxsb3dlZFNlbmRlcnMgaXMgZW1wdHkgKEVNQUlMX1NFTkRFUl9QT0xJQ1kpLiBcImFsbG93bGlzdFwiIChkZWZhdWx0KSBuYXJyb3dzIHNlbmRzIHRvIHRoZSBjb25maWd1cmVkIHNlbmRlciBzb3VyY2U7IFwidW5yZXN0cmljdGVkXCIgYWNjZXB0cyBhbnkgRnJvbS4nLFxuICAgIH0sXG4gICAgYnJhbmQ6IHtcbiAgICAgIHR5cGU6IFwib2JqZWN0XCIsXG4gICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgXCJCcmFuZCBzdHlsaW5nIGlubGluZWQgaW50byBldmVyeSByZW5kZXJlZCBtZXNzYWdlLiBPbWl0IGZvciB0aGUgbmV1dHJhbCBkZWZhdWx0IGxheW91dC5cIixcbiAgICAgIHByb3BlcnRpZXM6IHtcbiAgICAgICAgYWNjZW50OiB7XG4gICAgICAgICAgdHlwZTogXCJzdHJpbmdcIixcbiAgICAgICAgICBkZXNjcmlwdGlvbjogJ0hlYWRlci1iYW5kIGJhY2tncm91bmQgYW5kIGJvZHkgbGluayBjb2xvciAoZS5nLiBcIiNGRjM2MjFcIikuJyxcbiAgICAgICAgfSxcbiAgICAgICAgb25BY2NlbnQ6IHtcbiAgICAgICAgICB0eXBlOiBcInN0cmluZ1wiLFxuICAgICAgICAgIGRlc2NyaXB0aW9uOiBcIlRleHQgYW5kIGxvZ28gY29sb3IgcmVuZGVyZWQgb24gdGhlIGFjY2VudCBiYW5kLiBEZWZhdWx0cyB0byB3aGl0ZS5cIixcbiAgICAgICAgfSxcbiAgICAgICAgZm9udEZhbWlseToge1xuICAgICAgICAgIHR5cGU6IFwic3RyaW5nXCIsXG4gICAgICAgICAgZGVzY3JpcHRpb246ICdCb2R5IGZvbnQgc3RhY2sgKGUuZy4gXCJJbnRlciwgdWktc2Fucy1zZXJpZiwgc3lzdGVtLXVpLCBzYW5zLXNlcmlmXCIpLicsXG4gICAgICAgIH0sXG4gICAgICAgIG5hbWU6IHtcbiAgICAgICAgICB0eXBlOiBcInN0cmluZ1wiLFxuICAgICAgICAgIGRlc2NyaXB0aW9uOiBcIlByb2R1Y3QgLyBkaXNwbGF5IG5hbWUgdXNlZCBhcyB0aGUgaGVhZGVyIHRleHQgYW5kIHRoZSBsb2dvIGFsdCB0ZXh0LlwiLFxuICAgICAgICB9LFxuICAgICAgICBsb2dvVXJsOiB7XG4gICAgICAgICAgdHlwZTogXCJzdHJpbmdcIixcbiAgICAgICAgICBkZXNjcmlwdGlvbjpcbiAgICAgICAgICAgIFwiTG9nbyBpbWFnZSBmb3IgdGhlIGhlYWRlciBiYW5kLiBPbmx5IGFuIGh0dHAocyk6IG9yIGRhdGE6IFVSTCByZW5kZXJzOyBvdGhlciB2YWx1ZXMgYXJlIGRyb3BwZWQgYmVjYXVzZSBhIG1haWwgY2xpZW50IGNhbm5vdCBsb2FkIHRoZW0uXCIsXG4gICAgICAgIH0sXG4gICAgICB9LFxuICAgICAgcmVxdWlyZWQ6IFtcImFjY2VudFwiLCBcImZvbnRGYW1pbHlcIl0sXG4gICAgfSxcbiAgfSxcbn07XG5cbi8qKiBQYXJzZSB0aGUgYFNNVFBfU0VDVVJFYCBlbnYgLyBjb25maWcgZmxhZywgZGVmYXVsdGluZyBieSBwb3J0LiAqL1xuZnVuY3Rpb24gcmVzb2x2ZVNlY3VyZShmbGFnOiBib29sZWFuIHwgdW5kZWZpbmVkLCBwb3J0OiBudW1iZXIpOiBib29sZWFuIHtcbiAgaWYgKHR5cGVvZiBmbGFnID09PSBcImJvb2xlYW5cIikgcmV0dXJuIGZsYWc7XG4gIHJldHVybiBvYmplY3QudG9Cb29sZWFuKHByb2Nlc3MuZW52LlNNVFBfU0VDVVJFKSA/PyBwb3J0ID09PSBJTVBMSUNJVF9UTFNfU01UUF9QT1JUO1xufVxuXG4vKiogUGFyc2UgdGhlIGBFTUFJTF9TRU5ERVJfUE9MSUNZYCBlbnYgLyBjb25maWcgdmFsdWUsIGRlZmF1bHRpbmcgdG8gZGVueS1ieS1kZWZhdWx0LiAqL1xuZnVuY3Rpb24gcmVzb2x2ZVNlbmRlclBvbGljeShwb2xpY3k6IFNlbmRlclBvbGljeSB8IHVuZGVmaW5lZCk6IFNlbmRlclBvbGljeSB7XG4gIGNvbnN0IHJhdyA9IHBvbGljeSA/PyBwcm9jZXNzLmVudi5FTUFJTF9TRU5ERVJfUE9MSUNZPy50cmltKCkudG9Mb3dlckNhc2UoKTtcbiAgaWYgKHJhdyA9PT0gXCJ1bnJlc3RyaWN0ZWRcIikgcmV0dXJuIFwidW5yZXN0cmljdGVkXCI7XG4gIGlmIChyYXcgPT09IHVuZGVmaW5lZCB8fCByYXcgPT09IFwiXCIgfHwgcmF3ID09PSBcImFsbG93bGlzdFwiKSByZXR1cm4gXCJhbGxvd2xpc3RcIjtcbiAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLmludmFsaWRWYWx1ZShcInNlbmRlclBvbGljeVwiLCByYXcsICdcImFsbG93bGlzdFwiIG9yIFwidW5yZXN0cmljdGVkXCInKTtcbn1cblxuLyoqXG4gKiBFZmZlY3RpdmUgYWxsb3ctbGlzdCBmb3IgYW4gYFwiYWxsb3dsaXN0XCJgIHBvbGljeSB3aXRoIG5vIGV4cGxpY2l0IHBhdHRlcm5zOlxuICogdGhlIHNlbmRlciBzb3VyY2UgaXRzZWxmLCBzbyBhIGNvbmZpZ3VyZWQgZG9tYWluIG9yIGZpeGVkIGFkZHJlc3MgaXMgdGhlXG4gKiBib3VuZGFyeSByYXRoZXIgdGhhbiBcImFueXRoaW5nIGdvZXNcIi4gWWllbGRzIG5vdGhpbmcgd2hlbiBuZWl0aGVyIGlzIHNldCxcbiAqIHdoaWNoIG9ubHkgaGFwcGVucyBpbiBvdXRib3ggbW9kZSAod2hlcmUgdGhlIHNlbmRlciBmYWxscyBiYWNrIHRvIHRoZVxuICogb24tYmVoYWxmLW9mIHVzZXIncyBvd24gYWRkcmVzcyBhbmQgY2Fubm90IGJlIGVudW1lcmF0ZWQgdXAgZnJvbnQpLlxuICovXG5mdW5jdGlvbiBpbXBsaWVkU2VuZGVyUGF0dGVybnMoZG9tYWluOiBzdHJpbmcgfCB1bmRlZmluZWQsIGZyb206IHN0cmluZyB8IHVuZGVmaW5lZCk6IHN0cmluZ1tdIHtcbiAgY29uc3QgcGF0dGVybnM6IHN0cmluZ1tdID0gW107XG4gIGlmIChmcm9tKSBwYXR0ZXJucy5wdXNoKGZyb20udHJpbSgpLnRvTG93ZXJDYXNlKCkpO1xuICBpZiAoZG9tYWluKSBwYXR0ZXJucy5wdXNoKGAqQCR7ZG9tYWluLnRyaW0oKS50b0xvd2VyQ2FzZSgpfWApO1xuICByZXR1cm4gcGF0dGVybnM7XG59XG5cbi8qKiBXaGV0aGVyIGBFTUFJTF9PVVRCT1hfTU9ERWAgZXhwbGljaXRseSBvcHRzIGludG8gdGhlIGZpbGUvb3V0Ym94IGZhbGxiYWNrLiAqL1xuZnVuY3Rpb24gaXNPdXRib3hNb2RlRW5hYmxlZCgpOiBib29sZWFuIHtcbiAgcmV0dXJuIG9iamVjdC50b0Jvb2xlYW4ocHJvY2Vzcy5lbnYuRU1BSUxfT1VUQk9YX01PREUpID8/IGZhbHNlO1xufVxuXG5jb25zdCBTTVRQX1JFUVVJUkVEX0ZJRUxEUyA9IFtcIlNNVFBfSE9TVFwiLCBcIlNNVFBfVVNFUlwiLCBcIlNNVFBfUEFTU1dPUkRcIl0gYXMgY29uc3Q7XG5cbi8qKiBMaXN0IGVudiBrZXlzIGZvciBTTVRQIGZpZWxkcyB0aGF0IGFyZSB1bnNldCBpbiB0aGUgcmVzb2x2ZWQgY3JlZGVudGlhbCBzZXQuICovXG5mdW5jdGlvbiBtaXNzaW5nU210cEZpZWxkcyhcbiAgaG9zdDogc3RyaW5nIHwgdW5kZWZpbmVkLFxuICB1c2VyOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4gIHBhc3M6IHN0cmluZyB8IHVuZGVmaW5lZCxcbik6IHN0cmluZ1tdIHtcbiAgY29uc3QgdmFsdWVzID0gW2hvc3QsIHVzZXIsIHBhc3NdIGFzIGNvbnN0O1xuICByZXR1cm4gU01UUF9SRVFVSVJFRF9GSUVMRFMuZmlsdGVyKChfLCBpbmRleCkgPT4gIXZhbHVlc1tpbmRleF0pO1xufVxuXG4vKipcbiAqIFJlc29sdmUgcGx1Z2luIGNvbmZpZyBvdmVyIGVudmlyb25tZW50IGRlZmF1bHRzLlxuICpcbiAqIFdoZW4gU01UUCBob3N0ICsgdXNlciArIHBhc3N3b3JkIGFyZSBhbGwgcHJlc2VudCwgcmV0dXJucyBgbW9kZTpcbiAqIFwic210cFwiYCBmb3IgcmVhbCBkZWxpdmVyeSAoYW5kIHRocm93cyBpZiBubyBzZW5kZXIgc291cmNlIC0gZG9tYWluIG9yXG4gKiBmcm9tIC0gaXMgY29uZmlndXJlZCwgc2luY2UgU01UUCBjYW4ndCBkZXJpdmUgb25lIGZyb20gbm90aGluZykuXG4gKiBXaGVuIGFsbCB0aHJlZSBhcmUgYWJzZW50IGFuZCBgRU1BSUxfT1VUQk9YX01PREVgIGlzIGVuYWJsZWQsIHJldHVybnNcbiAqIGBtb2RlOiBcImZpbGVcImAgc28gdGhlIHJ1bnRpbWUgd3JpdGVzIG1lc3NhZ2VzIHRvIHRoZSBvdXRib3ggZGlyZWN0b3J5XG4gKiBmb3IgbG9jYWwgdGVzdGluZzsgaW4gdGhhdCBtb2RlIGEgc2VuZGVyIHNvdXJjZSBpcyBvcHRpb25hbCAodGhlXG4gKiBvdXRib3ggZmFsbHMgYmFjayB0byB0aGUgT0JPIHVzZXIncyBvd24gYWRkcmVzcykuIFBhcnRpYWwgU01UUFxuICogY29uZmlndXJhdGlvbiBvciBhIHNlbmQgd2l0aCBubyBjcmVkZW50aWFscyBhbmQgbm8gb3V0Ym94IG9wdC1pblxuICogdGhyb3dzLlxuICpcbiAqIFRoZSBzZW5kZXIgYWxsb3ctbGlzdCBpcyByZXNvbHZlZCBoZXJlIHRvbzogdW5kZXIgdGhlIGRlZmF1bHRcbiAqIGBcImFsbG93bGlzdFwiYCBwb2xpY3kgYW4gZW1wdHkgbGlzdCBpcyBmaWxsZWQgaW4gZnJvbSB0aGUgc2VuZGVyIHNvdXJjZSxcbiAqIHNvIGEgZGVwbG95bWVudCB0aGF0IG9ubHkgc2V0cyBgRU1BSUxfRE9NQUlOYCBzdGlsbCByZWplY3RzIGEgYEZyb21gIG9uXG4gKiBhbnkgb3RoZXIgZG9tYWluLiBTZWUge0BsaW5rIFNlbmRlclBvbGljeX0uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRW1haWxDb25maWcoY29uZmlnOiBFbWFpbFBsdWdpbkNvbmZpZyA9IHt9KTogUmVzb2x2ZWRFbWFpbENvbmZpZyB7XG4gIGNvbnN0IHNtdHAgPSBjb25maWcuc210cCA/PyB7fTtcbiAgY29uc3QgaG9zdCA9IHNtdHAuaG9zdCA/PyBwcm9jZXNzLmVudi5TTVRQX0hPU1Q7XG4gIGNvbnN0IHVzZXIgPSBzbXRwLnVzZXIgPz8gcHJvY2Vzcy5lbnYuU01UUF9VU0VSO1xuICBjb25zdCBwYXNzID0gc210cC5wYXNzd29yZCA/PyBwcm9jZXNzLmVudi5TTVRQX1BBU1NXT1JEO1xuICBjb25zdCBkb21haW4gPSBjb25maWcuZG9tYWluID8/IHByb2Nlc3MuZW52LkVNQUlMX0RPTUFJTjtcbiAgY29uc3QgZnJvbSA9IGNvbmZpZy5mcm9tID8/IHByb2Nlc3MuZW52LkVNQUlMX0ZST007XG4gIGNvbnN0IHNlbmRlclBvbGljeSA9IHJlc29sdmVTZW5kZXJQb2xpY3koY29uZmlnLnNlbmRlclBvbGljeSk7XG4gIGNvbnN0IGNvbmZpZ3VyZWRTZW5kZXJzID0gcGFyc2VBbGxvd2VkU2VuZGVycyhcbiAgICBjb25maWcuYWxsb3dlZFNlbmRlcnMgPz8gcHJvY2Vzcy5lbnYuRU1BSUxfQUxMT1dFRF9TRU5ERVJTLFxuICApO1xuICBjb25zdCBhbGxvd2VkU2VuZGVycyA9XG4gICAgY29uZmlndXJlZFNlbmRlcnMubGVuZ3RoID4gMCB8fCBzZW5kZXJQb2xpY3kgPT09IFwidW5yZXN0cmljdGVkXCJcbiAgICAgID8gY29uZmlndXJlZFNlbmRlcnNcbiAgICAgIDogaW1wbGllZFNlbmRlclBhdHRlcm5zKGRvbWFpbiwgZnJvbSk7XG4gIGNvbnN0IHNlbmRlcjogUmVzb2x2ZWRTZW5kZXIgPSB7XG4gICAgLi4uKGRvbWFpbiA/IHsgZG9tYWluIH0gOiB7fSksXG4gICAgLi4uKGZyb20gPyB7IGZyb20gfSA6IHt9KSxcbiAgICBhbGxvd2VkU2VuZGVycyxcbiAgICBzZW5kZXJQb2xpY3ksXG4gICAgLi4uKGNvbmZpZy5icmFuZCA/IHsgYnJhbmQ6IGNvbmZpZy5icmFuZCB9IDoge30pLFxuICB9O1xuXG4gIGNvbnN0IGhhc0FsbFNtdHAgPSBCb29sZWFuKGhvc3QgJiYgdXNlciAmJiBwYXNzKTtcbiAgY29uc3QgaGFzQW55U210cCA9IEJvb2xlYW4oaG9zdCB8fCB1c2VyIHx8IHBhc3MpO1xuXG4gIGlmIChoYXNBbnlTbXRwICYmICFoYXNBbGxTbXRwKSB7XG4gICAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLm1pc3NpbmdFbnZWYXJzKG1pc3NpbmdTbXRwRmllbGRzKGhvc3QsIHVzZXIsIHBhc3MpKTtcbiAgfVxuXG4gIGlmIChoYXNBbGxTbXRwKSB7XG4gICAgaWYgKCFkb21haW4gJiYgIWZyb20pIHtcbiAgICAgIHRocm93IENvbmZpZ3VyYXRpb25FcnJvci5yZXNvdXJjZU5vdEZvdW5kKFxuICAgICAgICBcIkVtYWlsIHNlbmRlciBzb3VyY2VcIixcbiAgICAgICAgXCJTZXQgRU1BSUxfRE9NQUlOIHRvIGRlcml2ZSA8dXNlci1sb2NhbC1wYXJ0PkA8ZG9tYWluPiwgb3IgRU1BSUxfRlJPTSBmb3IgYSBmaXhlZCBhZGRyZXNzLlwiLFxuICAgICAgKTtcbiAgICB9XG4gICAgY29uc3QgcG9ydFJhdyA9IHNtdHAucG9ydCA/PyBOdW1iZXIocHJvY2Vzcy5lbnYuU01UUF9QT1JUKTtcbiAgICBjb25zdCBwb3J0ID0gTnVtYmVyLmlzRmluaXRlKHBvcnRSYXcpICYmIHBvcnRSYXcgPyBOdW1iZXIocG9ydFJhdykgOiBERUZBVUxUX1NNVFBfUE9SVDtcbiAgICByZXR1cm4ge1xuICAgICAgbW9kZTogXCJzbXRwXCIsXG4gICAgICBob3N0OiBob3N0ISxcbiAgICAgIHBvcnQsXG4gICAgICBzZWN1cmU6IHJlc29sdmVTZWN1cmUoc210cC5zZWN1cmUsIHBvcnQpLFxuICAgICAgYXV0aDogeyB1c2VyOiB1c2VyISwgcGFzczogcGFzcyEgfSxcbiAgICAgIC4uLnNlbmRlcixcbiAgICB9O1xuICB9XG5cbiAgaWYgKCFpc091dGJveE1vZGVFbmFibGVkKCkpIHtcbiAgICB0aHJvdyBDb25maWd1cmF0aW9uRXJyb3IuaW52YWxpZENvbm5lY3Rpb24oXG4gICAgICBcIlNNVFBcIixcbiAgICAgIGBTZXQgJHtTTVRQX1JFUVVJUkVEX0ZJRUxEUy5qb2luKFwiLCBcIil9LCBvciBFTUFJTF9PVVRCT1hfTU9ERT0xIHRvIHdyaXRlIG1lc3NhZ2VzIHRvIGEgbG9jYWwgb3V0Ym94IGluc3RlYWQuYCxcbiAgICApO1xuICB9XG5cbiAgY29uc3Qgb3V0RGlyID0gcmVzb2x2ZShcbiAgICBjb25maWcub3V0RGlyID8/IHByb2Nlc3MuZW52LkVNQUlMX09VVEJPWF9ESVIgPz8gcmVzb2x2ZShwcm9jZXNzLmN3ZCgpLCBcInRtcFwiKSxcbiAgKTtcbiAgcmV0dXJuIHsgbW9kZTogXCJmaWxlXCIsIG91dERpciwgLi4uc2VuZGVyIH07XG59XG4iXX0=
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Interceptor defaults and hard payload caps for the email plugin.
3
+ *
4
+ * The execution settings are what the runtime's executor hands
5
+ * `Plugin.execute()`, kept here rather than at the call sites so the
6
+ * caching / retry / timeout posture of every outbound operation is
7
+ * reviewable in one place. The caps bound the one unbounded input the
8
+ * plugin accepts: a model-drafted message with attachments.
9
+ *
10
+ * @module
11
+ */
12
+ /**
13
+ * The `PluginExecuteConfig` slice this package sets. Mirrored structurally
14
+ * because AppKit's `PluginExecuteConfig` lives behind a subpath its `exports`
15
+ * map does not publish, so the nominal type cannot be imported. Written as a
16
+ * type alias rather than an interface so it stays assignable to the nominal
17
+ * type's index signature.
18
+ */
19
+ export type EmailExecuteConfig = {
20
+ cache?: {
21
+ enabled?: boolean;
22
+ ttl?: number;
23
+ cacheKey?: (string | number | object)[];
24
+ };
25
+ retry?: {
26
+ enabled?: boolean;
27
+ attempts?: number;
28
+ initialDelay?: number;
29
+ maxDelay?: number;
30
+ };
31
+ timeout?: number;
32
+ };
33
+ /**
34
+ * The `PluginExecutionSettings` shape accepted by AppKit's `Plugin.execute()`.
35
+ * Mirrored structurally for the same reason as {@link EmailExecuteConfig}.
36
+ */
37
+ export type EmailExecutionSettings = {
38
+ default: EmailExecuteConfig;
39
+ user?: EmailExecuteConfig;
40
+ };
41
+ /** Ceiling on how long a single SMTP conversation may take. */
42
+ export declare const SEND_TIMEOUT_MS = 30000;
43
+ /** Ceiling on the SMTP handshake performed at plugin setup. */
44
+ export declare const VERIFY_TIMEOUT_MS = 15000;
45
+ /** Attempts allowed for the setup-time SMTP handshake, including the first. */
46
+ export declare const VERIFY_ATTEMPTS = 3;
47
+ /** Largest single attachment accepted, in decoded bytes (10 MiB). */
48
+ export declare const MAX_ATTACHMENT_BYTES = 10485760;
49
+ /** Largest combined attachment payload accepted, in decoded bytes (20 MiB). */
50
+ export declare const MAX_ATTACHMENTS_TOTAL_BYTES = 20971520;
51
+ /** Largest number of attachments accepted on one message. */
52
+ export declare const MAX_ATTACHMENT_COUNT = 20;
53
+ /** Largest markdown body accepted, in characters. */
54
+ export declare const MAX_BODY_CHARS = 200000;
55
+ /** Execution settings for a send (SMTP dispatch or an outbox write). */
56
+ export declare const EMAIL_SEND_SETTINGS: EmailExecutionSettings;
57
+ /** Execution settings for the setup-time SMTP connectivity check. */
58
+ export declare const EMAIL_VERIFY_SETTINGS: EmailExecutionSettings;
59
+ /** Execution settings for the sender-options lookup. */
60
+ export declare const EMAIL_SENDERS_SETTINGS: EmailExecutionSettings;