@beignet/provider-mail-smtp 0.0.30 → 0.0.32

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/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # @beignet/provider-mail-smtp
2
2
 
3
+ ## 0.0.32
4
+
5
+ ### Patch Changes
6
+
7
+ - f759611: Provider factory options now resolve through one framework-owned rule:
8
+ `ProviderConfigDef` gains an `overrides` map that merges defined values over
9
+ env-derived input before validation, and every first-party provider factory
10
+ uses it instead of hand-rolled schema defaults and setup-time fallbacks. The
11
+ precedence is now uniform — defined factory options win over environment
12
+ variables in all thirteen providers. This flips the previous env-wins
13
+ behavior of `provider-cache-redis`, `provider-locks-redis`, and the storage,
14
+ search, and blob providers; set the env var (or drop the option) if you
15
+ relied on env winning. `installProviderForTest(...)` gains an `env` option
16
+ that resolves config through the same loader, and unreachable missing-config
17
+ guards in the pino and inngest factories are gone.
18
+ - eb680ef: Provider naming now follows one implementation-first order matching the
19
+ factories. `@beignet/provider-redis` is renamed `@beignet/provider-cache-redis`
20
+ (the package fills the cache port; the raw client stays as the
21
+ `ctx.ports.redis` escape hatch) with `redisCacheProvider`,
22
+ `createRedisCacheProvider`, and provider name `cache-redis` matching
23
+ `locks-redis`. Default consts flip to implementation-first:
24
+ `loggerPinoProvider` is now `pinoLoggerProvider`, `mailResendProvider` is now
25
+ `resendMailProvider`, and `mailSmtpProvider` is now `smtpMailProvider`.
26
+ CLI starter templates and the `redis-cache` provider preset wire the new
27
+ names. No aliases are kept.
28
+ - 671b986: Every provider package now exposes a `createXProvider(options)` factory, so
29
+ env-only configuration is a convenience rather than the only path. New
30
+ factories: `createPinoLoggerProvider`, `createResendMailProvider`,
31
+ `createSmtpMailProvider`, `createStripePaymentsProvider`,
32
+ `createUpstashRateLimitProvider`, and `createInngestJobsProvider`. Options
33
+ mirror each provider's env config fields and override env-derived values; the
34
+ default consts keep their behavior and now simply call their factory. (The
35
+ consts themselves are renamed to implementation-first order —
36
+ `pinoLoggerProvider`, `resendMailProvider`, `smtpMailProvider` — in this
37
+ release's provider naming normalization; `stripePaymentsProvider`,
38
+ `upstashRateLimitProvider`, and `inngestJobsProvider` already followed it.)
39
+ - f759611: Provider resilience baseline. The S3 storage provider now surfaces the AWS
40
+ SDK's built-in retry configuration: pass `maxAttempts` and `retryMode`
41
+ (`"standard"` or `"adaptive"`) as provider options or set
42
+ `STORAGE_S3_MAX_ATTEMPTS` and `STORAGE_S3_RETRY_MODE` env vars; the SDK
43
+ default stays `standard` mode with 3 attempts. Mail providers deliberately do
44
+ not retry — mail delivery is not idempotent — and now throw `MailDeliveryError`
45
+ messages that include the provider name and recipient count while preserving
46
+ the original vendor error as `cause`; dispatch mail from jobs or the outbox
47
+ when retries are needed.
48
+
49
+ ## 0.0.31
50
+
3
51
  ## 0.0.30
4
52
 
5
53
  ### Patch Changes
package/README.md CHANGED
@@ -19,12 +19,12 @@ bun add @beignet/provider-mail-smtp @beignet/core nodemailer
19
19
  ## Setup
20
20
 
21
21
  ```typescript
22
- import { mailSmtpProvider } from "@beignet/provider-mail-smtp";
22
+ import { smtpMailProvider } from "@beignet/provider-mail-smtp";
23
23
  import { createServer } from "@beignet/core/server";
24
24
 
25
25
  const server = await createServer({
26
26
  ports: basePorts,
27
- providers: [mailSmtpProvider],
27
+ providers: [smtpMailProvider],
28
28
  context: ({ ports }) => ({ ports }),
29
29
  routes,
30
30
  });
@@ -44,6 +44,26 @@ Required environment variables:
44
44
  registered in `server/providers.ts` and that all required `MAIL_*` env vars are
45
45
  present in app env examples or config.
46
46
 
47
+ Use `createSmtpMailProvider(...)` when you want to pass config directly
48
+ instead of reading `MAIL_*` env vars. Options override env-derived values:
49
+
50
+ ```typescript
51
+ import { createSmtpMailProvider } from "@beignet/provider-mail-smtp";
52
+
53
+ export const providers = [
54
+ createSmtpMailProvider({
55
+ host: "smtp.example.com",
56
+ port: 465,
57
+ user: secrets.smtpUser,
58
+ pass: secrets.smtpPass,
59
+ from: "My App <no-reply@example.com>",
60
+ }),
61
+ ];
62
+ ```
63
+
64
+ `smtpMailProvider` is the default env-backed provider, equivalent to
65
+ `createSmtpMailProvider()`.
66
+
47
67
  ## Use in application code
48
68
 
49
69
  ```typescript
@@ -99,10 +119,24 @@ await ctx.ports.smtp.transporter.sendMail({
99
119
  When `ctx.ports.devtools` is installed, this provider records `mail.send`,
100
120
  `mail.sent`, and `mail.failed` events under the `mail` watcher.
101
121
 
102
- ## Errors
122
+ ## Failure and retry semantics
123
+
124
+ The provider fails fast and does not retry. Mail delivery is not idempotent: a
125
+ timed-out send may still have been delivered, so a blind provider retry risks
126
+ duplicate emails.
127
+
128
+ Delivery failures throw `MailDeliveryError` from `@beignet/core/mail` with the
129
+ provider name, the recipient count, and the original SMTP error preserved as
130
+ `cause`. Message bodies and credentials are never included.
131
+
132
+ The provider calls `transporter.verify()` during setup, so an unreachable SMTP
133
+ server or rejected credentials fail server startup immediately instead of
134
+ failing the first send. Startup configuration problems also throw during
135
+ provider setup.
103
136
 
104
- Delivery failures throw `MailDeliveryError` from `@beignet/core/mail`.
105
- Startup configuration and connection problems throw during provider setup.
137
+ When mail needs retries, dispatch it from a job or an outbox-backed listener
138
+ and own idempotency there, for example by recording one delivery per business
139
+ event before sending.
106
140
 
107
141
  ## Local and tests
108
142
 
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export interface SmtpMailEscapeHatch {
28
28
  transporter: Transporter;
29
29
  }
30
30
  /**
31
- * Ports contributed by `mailSmtpProvider`.
31
+ * Ports contributed by `smtpMailProvider`.
32
32
  */
33
33
  export interface SmtpMailProviderPorts {
34
34
  /**
@@ -60,18 +60,60 @@ export interface CreateSmtpMailerOptions {
60
60
  /**
61
61
  * Create a Beignet mailer port backed by Nodemailer SMTP.
62
62
  *
63
- * Use this directly for custom wiring, or install `mailSmtpProvider` to load
63
+ * Use this directly for custom wiring, or install `smtpMailProvider` to load
64
64
  * config from env and contribute ports during server startup.
65
65
  */
66
66
  export declare function createSmtpMailer({ transporter, from, instrumentation: instrumentationTarget, }: CreateSmtpMailerOptions): MailerPort;
67
67
  /**
68
- * Env-backed SMTP mail provider.
68
+ * Options for the SMTP mail provider factory.
69
+ */
70
+ export interface CreateSmtpMailProviderOptions {
71
+ /**
72
+ * SMTP server hostname. Overrides `MAIL_HOST`.
73
+ */
74
+ host?: string;
75
+ /**
76
+ * SMTP server port. Port 465 uses SSL. Overrides `MAIL_PORT`.
77
+ */
78
+ port?: number;
79
+ /**
80
+ * SMTP username. Overrides `MAIL_USER`.
81
+ */
82
+ user?: string;
83
+ /**
84
+ * SMTP password. Overrides `MAIL_PASS`.
85
+ */
86
+ pass?: string;
87
+ /**
88
+ * Default sender address. Overrides `MAIL_FROM`.
89
+ */
90
+ from?: string;
91
+ }
92
+ /**
93
+ * Create an env-backed SMTP mail provider.
94
+ *
95
+ * Options override the corresponding `MAIL_*` env values.
96
+ */
97
+ export declare function createSmtpMailProvider(options?: CreateSmtpMailProviderOptions): import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
98
+ HOST: z.ZodString;
99
+ PORT: z.ZodCoercedNumber<unknown>;
100
+ USER: z.ZodString;
101
+ PASS: z.ZodString;
102
+ FROM: z.ZodString;
103
+ }, z.core.$strip>, {
104
+ mailer: MailerPort;
105
+ smtp: {
106
+ transporter: Transporter<import("nodemailer/lib/smtp-transport/index.js").SentMessageInfo, import("nodemailer/lib/smtp-transport/index.js").Options>;
107
+ };
108
+ }, unknown, void>;
109
+ /**
110
+ * Default env-backed SMTP mail provider.
69
111
  *
70
112
  * Reads `MAIL_HOST`, `MAIL_PORT`, `MAIL_USER`, `MAIL_PASS`, and `MAIL_FROM`,
71
113
  * verifies the transporter during setup, contributes `ports.mailer`, and
72
114
  * exposes `ports.smtp.transporter` as an escape hatch.
73
115
  */
74
- export declare const mailSmtpProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
116
+ export declare const smtpMailProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
75
117
  HOST: z.ZodString;
76
118
  PORT: z.ZodCoercedNumber<unknown>;
77
119
  USER: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAIhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAEV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,gBAAgB;;;;;;iBAMpB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;IACzB;;OAEG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;OAEG;IACH,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAoDD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,IAAI,EACJ,eAAe,EAAE,qBAAqB,GACvC,EAAE,uBAAuB,GAAG,UAAU,CAqEtC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAwD3B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAIhB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAEV,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,gBAAgB;;;;;;iBAMpB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,WAAW,EAAE,WAAW,CAAC;IACzB;;OAEG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;OAEG;IACH,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AA0DD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,WAAW,EACX,IAAI,EACJ,eAAe,EAAE,qBAAqB,GACvC,EAAE,uBAAuB,GAAG,UAAU,CAqEtC;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,6BAAkC;;;;;;;;;;;kBA0E5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -19,6 +19,10 @@ function addOptional(target, key, value) {
19
19
  Object.assign(target, { [key]: value });
20
20
  }
21
21
  }
22
+ function countRecipients(message) {
23
+ const count = (list) => list === undefined ? 0 : Array.isArray(list) ? list.length : 1;
24
+ return count(message.to) + count(message.cc) + count(message.bcc);
25
+ }
22
26
  function createSmtpPayload(message, defaultFrom) {
23
27
  const normalized = normalizeMailMessage(message, { defaultFrom });
24
28
  if (!normalized.from) {
@@ -43,7 +47,7 @@ function createSmtpPayload(message, defaultFrom) {
43
47
  /**
44
48
  * Create a Beignet mailer port backed by Nodemailer SMTP.
45
49
  *
46
- * Use this directly for custom wiring, or install `mailSmtpProvider` to load
50
+ * Use this directly for custom wiring, or install `smtpMailProvider` to load
47
51
  * config from env and contribute ports during server startup.
48
52
  */
49
53
  export function createSmtpMailer({ transporter, from, instrumentation: instrumentationTarget, }) {
@@ -54,6 +58,7 @@ export function createSmtpMailer({ transporter, from, instrumentation: instrumen
54
58
  return {
55
59
  async send(message) {
56
60
  const payload = createSmtpPayload(message, from);
61
+ const recipientCount = countRecipients(message);
57
62
  instrumentation.custom({
58
63
  name: "mail.send",
59
64
  label: "Mail send",
@@ -102,9 +107,7 @@ export function createSmtpMailer({ transporter, from, instrumentation: instrumen
102
107
  });
103
108
  throw new MailDeliveryError({
104
109
  provider: "smtp",
105
- message: error instanceof Error
106
- ? error.message
107
- : "SMTP failed to send email.",
110
+ message: `SMTP failed to send email to ${recipientCount} recipient(s): ${error instanceof Error ? error.message : String(error)}`,
108
111
  cause: error,
109
112
  });
110
113
  }
@@ -112,56 +115,73 @@ export function createSmtpMailer({ transporter, from, instrumentation: instrumen
112
115
  };
113
116
  }
114
117
  /**
115
- * Env-backed SMTP mail provider.
118
+ * Create an env-backed SMTP mail provider.
119
+ *
120
+ * Options override the corresponding `MAIL_*` env values.
121
+ */
122
+ export function createSmtpMailProvider(options = {}) {
123
+ return createProvider({
124
+ name: "mail-smtp",
125
+ config: {
126
+ schema: MailConfigSchema,
127
+ envPrefix: "MAIL_",
128
+ // Defined options win over env-derived values at config load time.
129
+ overrides: {
130
+ HOST: options.host,
131
+ PORT: options.port,
132
+ USER: options.user,
133
+ PASS: options.pass,
134
+ FROM: options.from,
135
+ },
136
+ },
137
+ async setup({ config, ports }) {
138
+ if (!config) {
139
+ throw new Error("[smtpMailProvider] Missing Mail config. " +
140
+ "Please set MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, and MAIL_FROM environment variables.");
141
+ }
142
+ const { HOST: host, PORT: port, USER: user, PASS: pass, FROM: from, } = config;
143
+ const transporter = nodemailer.createTransport({
144
+ host,
145
+ port,
146
+ secure: port === 465,
147
+ auth: {
148
+ user,
149
+ pass,
150
+ },
151
+ });
152
+ try {
153
+ await transporter.verify();
154
+ }
155
+ catch (error) {
156
+ throw new Error(`[smtpMailProvider] Failed to connect to mail server at ${host}:${port}: ${error instanceof Error ? error.message : String(error)}`);
157
+ }
158
+ const instrumentation = createProviderInstrumentation(ports, {
159
+ providerName: "mail-smtp",
160
+ watcher: "mail",
161
+ });
162
+ const mailer = createSmtpMailer({
163
+ transporter,
164
+ from,
165
+ instrumentation,
166
+ });
167
+ return {
168
+ ports: {
169
+ mailer,
170
+ smtp: { transporter },
171
+ },
172
+ stop() {
173
+ transporter.close();
174
+ },
175
+ };
176
+ },
177
+ });
178
+ }
179
+ /**
180
+ * Default env-backed SMTP mail provider.
116
181
  *
117
182
  * Reads `MAIL_HOST`, `MAIL_PORT`, `MAIL_USER`, `MAIL_PASS`, and `MAIL_FROM`,
118
183
  * verifies the transporter during setup, contributes `ports.mailer`, and
119
184
  * exposes `ports.smtp.transporter` as an escape hatch.
120
185
  */
121
- export const mailSmtpProvider = createProvider({
122
- name: "mail-smtp",
123
- config: {
124
- schema: MailConfigSchema,
125
- envPrefix: "MAIL_",
126
- },
127
- async setup({ config, ports }) {
128
- if (!config) {
129
- throw new Error("[mailSmtpProvider] Missing Mail config. " +
130
- "Please set MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, and MAIL_FROM environment variables.");
131
- }
132
- const transporter = nodemailer.createTransport({
133
- host: config.HOST,
134
- port: config.PORT,
135
- secure: config.PORT === 465,
136
- auth: {
137
- user: config.USER,
138
- pass: config.PASS,
139
- },
140
- });
141
- try {
142
- await transporter.verify();
143
- }
144
- catch (error) {
145
- throw new Error(`[mailSmtpProvider] Failed to connect to mail server at ${config.HOST}:${config.PORT}: ${error instanceof Error ? error.message : String(error)}`);
146
- }
147
- const instrumentation = createProviderInstrumentation(ports, {
148
- providerName: "mail-smtp",
149
- watcher: "mail",
150
- });
151
- const mailer = createSmtpMailer({
152
- transporter,
153
- from: config.FROM,
154
- instrumentation,
155
- });
156
- return {
157
- ports: {
158
- mailer,
159
- smtp: { transporter },
160
- },
161
- stop() {
162
- transporter.close();
163
- },
164
- };
165
- },
166
- });
186
+ export const smtpMailProvider = createSmtpMailProvider();
167
187
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beignet/provider-mail-smtp",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "description": "SMTP mail provider for Beignet - adds mailer port using nodemailer",
6
6
  "main": "./dist/index.js",
@@ -102,7 +102,7 @@
102
102
  "registration": {
103
103
  "required": true,
104
104
  "tokens": [
105
- "mailSmtpProvider"
105
+ "smtpMailProvider"
106
106
  ]
107
107
  }
108
108
  }
package/src/index.ts CHANGED
@@ -50,7 +50,7 @@ export interface SmtpMailEscapeHatch {
50
50
  }
51
51
 
52
52
  /**
53
- * Ports contributed by `mailSmtpProvider`.
53
+ * Ports contributed by `smtpMailProvider`.
54
54
  */
55
55
  export interface SmtpMailProviderPorts {
56
56
  /**
@@ -90,6 +90,12 @@ function addOptional<
90
90
  }
91
91
  }
92
92
 
93
+ function countRecipients(message: SendMailOptions): number {
94
+ const count = (list: SendMailOptions["cc"]): number =>
95
+ list === undefined ? 0 : Array.isArray(list) ? list.length : 1;
96
+ return count(message.to) + count(message.cc) + count(message.bcc);
97
+ }
98
+
93
99
  function createSmtpPayload(
94
100
  message: SendMailOptions,
95
101
  defaultFrom: MailAddress,
@@ -134,7 +140,7 @@ function createSmtpPayload(
134
140
  /**
135
141
  * Create a Beignet mailer port backed by Nodemailer SMTP.
136
142
  *
137
- * Use this directly for custom wiring, or install `mailSmtpProvider` to load
143
+ * Use this directly for custom wiring, or install `smtpMailProvider` to load
138
144
  * config from env and contribute ports during server startup.
139
145
  */
140
146
  export function createSmtpMailer({
@@ -150,6 +156,7 @@ export function createSmtpMailer({
150
156
  return {
151
157
  async send(message): Promise<SendMailResult> {
152
158
  const payload = createSmtpPayload(message, from);
159
+ const recipientCount = countRecipients(message);
153
160
  instrumentation.custom({
154
161
  name: "mail.send",
155
162
  label: "Mail send",
@@ -201,10 +208,9 @@ export function createSmtpMailer({
201
208
  });
202
209
  throw new MailDeliveryError({
203
210
  provider: "smtp",
204
- message:
205
- error instanceof Error
206
- ? error.message
207
- : "SMTP failed to send email.",
211
+ message: `SMTP failed to send email to ${recipientCount} recipient(s): ${
212
+ error instanceof Error ? error.message : String(error)
213
+ }`,
208
214
  cause: error,
209
215
  });
210
216
  }
@@ -213,66 +219,118 @@ export function createSmtpMailer({
213
219
  }
214
220
 
215
221
  /**
216
- * Env-backed SMTP mail provider.
222
+ * Options for the SMTP mail provider factory.
223
+ */
224
+ export interface CreateSmtpMailProviderOptions {
225
+ /**
226
+ * SMTP server hostname. Overrides `MAIL_HOST`.
227
+ */
228
+ host?: string;
229
+ /**
230
+ * SMTP server port. Port 465 uses SSL. Overrides `MAIL_PORT`.
231
+ */
232
+ port?: number;
233
+ /**
234
+ * SMTP username. Overrides `MAIL_USER`.
235
+ */
236
+ user?: string;
237
+ /**
238
+ * SMTP password. Overrides `MAIL_PASS`.
239
+ */
240
+ pass?: string;
241
+ /**
242
+ * Default sender address. Overrides `MAIL_FROM`.
243
+ */
244
+ from?: string;
245
+ }
246
+
247
+ /**
248
+ * Create an env-backed SMTP mail provider.
217
249
  *
218
- * Reads `MAIL_HOST`, `MAIL_PORT`, `MAIL_USER`, `MAIL_PASS`, and `MAIL_FROM`,
219
- * verifies the transporter during setup, contributes `ports.mailer`, and
220
- * exposes `ports.smtp.transporter` as an escape hatch.
250
+ * Options override the corresponding `MAIL_*` env values.
221
251
  */
222
- export const mailSmtpProvider = createProvider({
223
- name: "mail-smtp",
252
+ export function createSmtpMailProvider(
253
+ options: CreateSmtpMailProviderOptions = {},
254
+ ) {
255
+ return createProvider({
256
+ name: "mail-smtp",
224
257
 
225
- config: {
226
- schema: MailConfigSchema,
227
- envPrefix: "MAIL_",
228
- },
258
+ config: {
259
+ schema: MailConfigSchema,
260
+ envPrefix: "MAIL_",
261
+ // Defined options win over env-derived values at config load time.
262
+ overrides: {
263
+ HOST: options.host,
264
+ PORT: options.port,
265
+ USER: options.user,
266
+ PASS: options.pass,
267
+ FROM: options.from,
268
+ },
269
+ },
229
270
 
230
- async setup({ config, ports }) {
231
- if (!config) {
232
- throw new Error(
233
- "[mailSmtpProvider] Missing Mail config. " +
234
- "Please set MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, and MAIL_FROM environment variables.",
235
- );
236
- }
271
+ async setup({ config, ports }) {
272
+ if (!config) {
273
+ throw new Error(
274
+ "[smtpMailProvider] Missing Mail config. " +
275
+ "Please set MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS, and MAIL_FROM environment variables.",
276
+ );
277
+ }
278
+ const {
279
+ HOST: host,
280
+ PORT: port,
281
+ USER: user,
282
+ PASS: pass,
283
+ FROM: from,
284
+ } = config;
237
285
 
238
- const transporter = nodemailer.createTransport({
239
- host: config.HOST,
240
- port: config.PORT,
241
- secure: config.PORT === 465,
242
- auth: {
243
- user: config.USER,
244
- pass: config.PASS,
245
- },
246
- });
286
+ const transporter = nodemailer.createTransport({
287
+ host,
288
+ port,
289
+ secure: port === 465,
290
+ auth: {
291
+ user,
292
+ pass,
293
+ },
294
+ });
247
295
 
248
- try {
249
- await transporter.verify();
250
- } catch (error) {
251
- throw new Error(
252
- `[mailSmtpProvider] Failed to connect to mail server at ${config.HOST}:${config.PORT}: ${
253
- error instanceof Error ? error.message : String(error)
254
- }`,
255
- );
256
- }
296
+ try {
297
+ await transporter.verify();
298
+ } catch (error) {
299
+ throw new Error(
300
+ `[smtpMailProvider] Failed to connect to mail server at ${host}:${port}: ${
301
+ error instanceof Error ? error.message : String(error)
302
+ }`,
303
+ );
304
+ }
257
305
 
258
- const instrumentation = createProviderInstrumentation(ports, {
259
- providerName: "mail-smtp",
260
- watcher: "mail",
261
- });
262
- const mailer = createSmtpMailer({
263
- transporter,
264
- from: config.FROM,
265
- instrumentation,
266
- });
306
+ const instrumentation = createProviderInstrumentation(ports, {
307
+ providerName: "mail-smtp",
308
+ watcher: "mail",
309
+ });
310
+ const mailer = createSmtpMailer({
311
+ transporter,
312
+ from,
313
+ instrumentation,
314
+ });
267
315
 
268
- return {
269
- ports: {
270
- mailer,
271
- smtp: { transporter },
272
- } satisfies SmtpMailProviderPorts,
273
- stop() {
274
- transporter.close();
275
- },
276
- };
277
- },
278
- });
316
+ return {
317
+ ports: {
318
+ mailer,
319
+ smtp: { transporter },
320
+ } satisfies SmtpMailProviderPorts,
321
+ stop() {
322
+ transporter.close();
323
+ },
324
+ };
325
+ },
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Default env-backed SMTP mail provider.
331
+ *
332
+ * Reads `MAIL_HOST`, `MAIL_PORT`, `MAIL_USER`, `MAIL_PASS`, and `MAIL_FROM`,
333
+ * verifies the transporter during setup, contributes `ports.mailer`, and
334
+ * exposes `ports.smtp.transporter` as an escape hatch.
335
+ */
336
+ export const smtpMailProvider = createSmtpMailProvider();