@nymbalhq/email-smtp 0.0.0-stage → 1.5.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/index.d.ts +41 -0
- package/dist/index.js +127 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -3
- package/README.md +0 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HealthReport, TransactionalEmail, EmailSendResult, CustomerProfile, Logger, EmailAdapter, AdapterContext, AdapterSetting } from '@nymbalhq/types';
|
|
2
|
+
|
|
3
|
+
interface SmtpTransportConfig {
|
|
4
|
+
from?: string;
|
|
5
|
+
smtp?: {
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
user?: string;
|
|
9
|
+
pass?: string;
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** The send path an email adapter that relays over SMTP shares: one lazily built nodemailer transport. */
|
|
14
|
+
interface SmtpTransportSender {
|
|
15
|
+
/** Merges `config` over what the sender was built with and builds the transport, as `initialize` does. */
|
|
16
|
+
configure(config: SmtpTransportConfig): void;
|
|
17
|
+
/** The SMTP login checked, unbounded: the caller wraps it in `boundedHealthCheck`. */
|
|
18
|
+
verify(): Promise<HealthReport>;
|
|
19
|
+
sendTransactional(params: TransactionalEmail): Promise<EmailSendResult>;
|
|
20
|
+
syncCustomerToList(customer: CustomerProfile): Promise<void>;
|
|
21
|
+
removeCustomerFromList(customerId: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
declare function createSmtpTransportSender(config: SmtpTransportConfig, logger: Logger): SmtpTransportSender;
|
|
24
|
+
|
|
25
|
+
type SmtpEmailAdapterConfig = SmtpTransportConfig;
|
|
26
|
+
declare function createSmtpEmailAdapter(deps: {
|
|
27
|
+
logger: Logger;
|
|
28
|
+
config?: SmtpEmailAdapterConfig;
|
|
29
|
+
}): EmailAdapter;
|
|
30
|
+
|
|
31
|
+
/** What this package serves: the kernel refuses it on any other slot or provider. */
|
|
32
|
+
declare const slot = "email";
|
|
33
|
+
declare const provider = "smtp";
|
|
34
|
+
|
|
35
|
+
/** The SMTP login the admin's Settings may fill; a server that takes mail without one needs neither. */
|
|
36
|
+
declare const settings: AdapterSetting[];
|
|
37
|
+
/** The kernel's entry: `email: { provider: 'smtp', config: SmtpEmailAdapterConfig }`. */
|
|
38
|
+
declare function createAdapter(ctx: AdapterContext): EmailAdapter;
|
|
39
|
+
|
|
40
|
+
export { createAdapter, createSmtpEmailAdapter, createSmtpTransportSender, provider, settings, slot };
|
|
41
|
+
export type { SmtpEmailAdapterConfig, SmtpTransportConfig, SmtpTransportSender };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
ConfigError
|
|
4
|
+
} from "@nymbalhq/types";
|
|
5
|
+
|
|
6
|
+
// src/smtp-adapter.ts
|
|
7
|
+
import {
|
|
8
|
+
boundedHealthCheck,
|
|
9
|
+
EVT_CART_ABANDONED,
|
|
10
|
+
EVT_CUSTOMER_CREATED,
|
|
11
|
+
EVT_ORDER_PLACED,
|
|
12
|
+
EVT_ORDER_SHIPPED
|
|
13
|
+
} from "@nymbalhq/types";
|
|
14
|
+
|
|
15
|
+
// src/smtp-transport-sender.ts
|
|
16
|
+
import nodemailer from "nodemailer";
|
|
17
|
+
import {
|
|
18
|
+
AdapterError,
|
|
19
|
+
errorText,
|
|
20
|
+
renderEmailTemplate
|
|
21
|
+
} from "@nymbalhq/types";
|
|
22
|
+
var SMTP_TIMEOUT_MS = 4e3;
|
|
23
|
+
function createSmtpTransportSender(config, logger) {
|
|
24
|
+
let cfg = config;
|
|
25
|
+
let transporter = null;
|
|
26
|
+
function ensureTransport() {
|
|
27
|
+
if (transporter) return transporter;
|
|
28
|
+
if (!cfg.smtp) {
|
|
29
|
+
throw new AdapterError("email.misconfigured", "SMTP provider selected but smtp.host not set");
|
|
30
|
+
}
|
|
31
|
+
transporter = nodemailer.createTransport({
|
|
32
|
+
host: cfg.smtp.host,
|
|
33
|
+
port: cfg.smtp.port,
|
|
34
|
+
secure: cfg.smtp.secure ?? cfg.smtp.port === 465,
|
|
35
|
+
// nodemailer waits two minutes to connect by default; a host that drops packets must fail inside the 6 s bound.
|
|
36
|
+
connectionTimeout: SMTP_TIMEOUT_MS,
|
|
37
|
+
greetingTimeout: SMTP_TIMEOUT_MS,
|
|
38
|
+
socketTimeout: SMTP_TIMEOUT_MS,
|
|
39
|
+
...cfg.smtp.user !== void 0 && cfg.smtp.pass !== void 0 ? { auth: { user: cfg.smtp.user, pass: cfg.smtp.pass } } : {}
|
|
40
|
+
});
|
|
41
|
+
return transporter;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
configure(next) {
|
|
45
|
+
cfg = { ...cfg, ...next };
|
|
46
|
+
ensureTransport();
|
|
47
|
+
},
|
|
48
|
+
async verify() {
|
|
49
|
+
try {
|
|
50
|
+
await ensureTransport().verify();
|
|
51
|
+
return { status: "healthy", detail: "SMTP verified" };
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const reachable = err?.code === "EAUTH";
|
|
54
|
+
return {
|
|
55
|
+
status: reachable ? "degraded" : "down",
|
|
56
|
+
detail: `SMTP verify failed: ${errorText(err)}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
async sendTransactional(params) {
|
|
61
|
+
const rendered = renderEmailTemplate(params.template, params.vars);
|
|
62
|
+
const info = await ensureTransport().sendMail({
|
|
63
|
+
from: cfg.from ?? "no-reply@nymbal.dev",
|
|
64
|
+
to: params.to,
|
|
65
|
+
subject: rendered.subject,
|
|
66
|
+
text: rendered.text,
|
|
67
|
+
html: rendered.html
|
|
68
|
+
});
|
|
69
|
+
return { messageId: info.messageId ?? null, accepted: (info.accepted ?? []).length > 0 };
|
|
70
|
+
},
|
|
71
|
+
async syncCustomerToList(customer) {
|
|
72
|
+
logger.debug({ customerId: customer.customerId }, "email.syncCustomerToList (stub)");
|
|
73
|
+
},
|
|
74
|
+
async removeCustomerFromList(customerId) {
|
|
75
|
+
logger.debug({ customerId }, "email.removeCustomerFromList (stub)");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/smtp-adapter.ts
|
|
81
|
+
function createSmtpEmailAdapter(deps) {
|
|
82
|
+
const log = deps.logger.child({ adapter: "email", provider: "smtp" });
|
|
83
|
+
const sender = createSmtpTransportSender(deps.config ?? {}, log);
|
|
84
|
+
return {
|
|
85
|
+
kind: "email",
|
|
86
|
+
providerName: "smtp",
|
|
87
|
+
capabilities: () => ["transactional", "list-sync"],
|
|
88
|
+
producesEvents: [],
|
|
89
|
+
consumesEvents: [EVT_ORDER_PLACED, EVT_ORDER_SHIPPED, EVT_CUSTOMER_CREATED, EVT_CART_ABANDONED],
|
|
90
|
+
async initialize(rawConfig) {
|
|
91
|
+
sender.configure(rawConfig);
|
|
92
|
+
},
|
|
93
|
+
healthCheck: () => boundedHealthCheck(() => sender.verify()),
|
|
94
|
+
sendTransactional: (params) => sender.sendTransactional(params),
|
|
95
|
+
syncCustomerToList: (customer) => sender.syncCustomerToList(customer),
|
|
96
|
+
removeCustomerFromList: (customerId) => sender.removeCustomerFromList(customerId)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/index.ts
|
|
101
|
+
var slot = "email";
|
|
102
|
+
var provider = "smtp";
|
|
103
|
+
var settings = [
|
|
104
|
+
{ path: "smtp.user", label: "SMTP user", secret: true, optional: true },
|
|
105
|
+
{ path: "smtp.pass", label: "SMTP password", secret: true, optional: true }
|
|
106
|
+
];
|
|
107
|
+
function createAdapter(ctx) {
|
|
108
|
+
if (ctx.slot !== "email") {
|
|
109
|
+
throw new ConfigError(`@nymbalhq/email-smtp serves email, not ${ctx.slot}`, {
|
|
110
|
+
code: "adapter.wrong_slot",
|
|
111
|
+
context: { slot: ctx.slot }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return createSmtpEmailAdapter({
|
|
115
|
+
logger: ctx.logger,
|
|
116
|
+
config: ctx.config
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
createAdapter,
|
|
121
|
+
createSmtpEmailAdapter,
|
|
122
|
+
createSmtpTransportSender,
|
|
123
|
+
provider,
|
|
124
|
+
settings,
|
|
125
|
+
slot
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/smtp-adapter.ts","../src/smtp-transport-sender.ts"],"sourcesContent":["import {\n ConfigError,\n type AdapterContext,\n type AdapterSetting,\n type EmailAdapter,\n} from '@nymbalhq/types'\nimport { createSmtpEmailAdapter, type SmtpEmailAdapterConfig } from './smtp-adapter.js'\n\n/** What this package serves: the kernel refuses it on any other slot or provider. */\nexport const slot = 'email'\nexport const provider = 'smtp'\n\nexport { createSmtpEmailAdapter, type SmtpEmailAdapterConfig }\nexport {\n createSmtpTransportSender,\n type SmtpTransportConfig,\n type SmtpTransportSender,\n} from './smtp-transport-sender.js'\n\n/** The SMTP login the admin's Settings may fill; a server that takes mail without one needs neither. */\nexport const settings: AdapterSetting[] = [\n { path: 'smtp.user', label: 'SMTP user', secret: true, optional: true },\n { path: 'smtp.pass', label: 'SMTP password', secret: true, optional: true },\n]\n\n/** The kernel's entry: `email: { provider: 'smtp', config: SmtpEmailAdapterConfig }`. */\nexport function createAdapter(ctx: AdapterContext): EmailAdapter {\n if (ctx.slot !== 'email') {\n throw new ConfigError(`@nymbalhq/email-smtp serves email, not ${ctx.slot}`, {\n code: 'adapter.wrong_slot',\n context: { slot: ctx.slot },\n })\n }\n return createSmtpEmailAdapter({\n logger: ctx.logger,\n config: ctx.config as SmtpEmailAdapterConfig,\n })\n}\n","import {\n boundedHealthCheck,\n EVT_CART_ABANDONED,\n EVT_CUSTOMER_CREATED,\n EVT_ORDER_PLACED,\n EVT_ORDER_SHIPPED,\n type EmailAdapter,\n type Logger,\n} from '@nymbalhq/types'\nimport { createSmtpTransportSender, type SmtpTransportConfig } from './smtp-transport-sender.js'\n\nexport type SmtpEmailAdapterConfig = SmtpTransportConfig\n\nexport function createSmtpEmailAdapter(deps: {\n logger: Logger\n config?: SmtpEmailAdapterConfig\n}): EmailAdapter {\n const log = deps.logger.child({ adapter: 'email', provider: 'smtp' })\n const sender = createSmtpTransportSender(deps.config ?? {}, log)\n\n return {\n kind: 'email',\n providerName: 'smtp',\n capabilities: () => ['transactional', 'list-sync'],\n producesEvents: [],\n consumesEvents: [EVT_ORDER_PLACED, EVT_ORDER_SHIPPED, EVT_CUSTOMER_CREATED, EVT_CART_ABANDONED],\n async initialize(rawConfig) {\n sender.configure(rawConfig as SmtpEmailAdapterConfig)\n },\n healthCheck: () => boundedHealthCheck(() => sender.verify()),\n sendTransactional: (params) => sender.sendTransactional(params),\n syncCustomerToList: (customer) => sender.syncCustomerToList(customer),\n removeCustomerFromList: (customerId) => sender.removeCustomerFromList(customerId),\n }\n}\n","import nodemailer, { type Transporter } from 'nodemailer'\nimport {\n AdapterError,\n errorText,\n renderEmailTemplate,\n type CustomerProfile,\n type EmailSendResult,\n type HealthReport,\n type Logger,\n type TransactionalEmail,\n} from '@nymbalhq/types'\n\nconst SMTP_TIMEOUT_MS = 4000\n\nexport interface SmtpTransportConfig {\n from?: string\n smtp?: {\n host: string\n port: number\n user?: string\n pass?: string\n secure?: boolean\n }\n}\n\n/** The send path an email adapter that relays over SMTP shares: one lazily built nodemailer transport. */\nexport interface SmtpTransportSender {\n /** Merges `config` over what the sender was built with and builds the transport, as `initialize` does. */\n configure(config: SmtpTransportConfig): void\n /** The SMTP login checked, unbounded: the caller wraps it in `boundedHealthCheck`. */\n verify(): Promise<HealthReport>\n sendTransactional(params: TransactionalEmail): Promise<EmailSendResult>\n syncCustomerToList(customer: CustomerProfile): Promise<void>\n removeCustomerFromList(customerId: string): Promise<void>\n}\n\nexport function createSmtpTransportSender(\n config: SmtpTransportConfig,\n logger: Logger,\n): SmtpTransportSender {\n let cfg: SmtpTransportConfig = config\n let transporter: Transporter | null = null\n\n function ensureTransport(): Transporter {\n if (transporter) return transporter\n if (!cfg.smtp) {\n throw new AdapterError('email.misconfigured', 'SMTP provider selected but smtp.host not set')\n }\n transporter = nodemailer.createTransport({\n host: cfg.smtp.host,\n port: cfg.smtp.port,\n secure: cfg.smtp.secure ?? cfg.smtp.port === 465,\n // nodemailer waits two minutes to connect by default; a host that drops packets must fail inside the 6 s bound.\n connectionTimeout: SMTP_TIMEOUT_MS,\n greetingTimeout: SMTP_TIMEOUT_MS,\n socketTimeout: SMTP_TIMEOUT_MS,\n ...(cfg.smtp.user !== undefined && cfg.smtp.pass !== undefined\n ? { auth: { user: cfg.smtp.user, pass: cfg.smtp.pass } }\n : {}),\n })\n return transporter\n }\n\n return {\n configure(next) {\n cfg = { ...cfg, ...next }\n ensureTransport()\n },\n\n async verify() {\n try {\n await ensureTransport().verify()\n return { status: 'healthy', detail: 'SMTP verified' }\n } catch (err) {\n // A server that answers and refuses the login is there but unusable; one that cannot be reached is down.\n const reachable = (err as { code?: unknown } | null)?.code === 'EAUTH'\n return {\n status: reachable ? 'degraded' : 'down',\n detail: `SMTP verify failed: ${errorText(err)}`,\n }\n }\n },\n\n async sendTransactional(params) {\n const rendered = renderEmailTemplate(params.template, params.vars)\n const info = await ensureTransport().sendMail({\n from: cfg.from ?? 'no-reply@nymbal.dev',\n to: params.to,\n subject: rendered.subject,\n text: rendered.text,\n html: rendered.html,\n })\n return { messageId: info.messageId ?? null, accepted: (info.accepted ?? []).length > 0 }\n },\n\n async syncCustomerToList(customer) {\n logger.debug({ customerId: customer.customerId }, 'email.syncCustomerToList (stub)')\n },\n\n async removeCustomerFromList(customerId) {\n logger.debug({ customerId }, 'email.removeCustomerFromList (stub)')\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nymbalhq/email-smtp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "SMTP email for Nymbal: the `smtp` email provider (nodemailer)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/nymbalhq/nymbal.git",
|
|
9
|
+
"directory": "packages/adapters/email/smtp"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@nymbalhq/types": "^1.5.0",
|
|
31
|
+
"nodemailer": "^6.9.16"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@nymbalhq/types": "1.5.0",
|
|
35
|
+
"@types/nodemailer": "^6.4.17",
|
|
36
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
37
|
+
"nodemailer": "^6.9.16",
|
|
38
|
+
"tsup": "^9.0.0",
|
|
39
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
40
|
+
"vitest": "^3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit --rootDir ../../../.."
|
|
46
|
+
}
|
|
6
47
|
}
|
package/README.md
DELETED