@beignet/provider-mail-resend 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-resend
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
@@ -21,12 +21,12 @@ the SDK's Node.js 20+ runtime requirement.
21
21
  ## Setup
22
22
 
23
23
  ```typescript
24
- import { mailResendProvider } from "@beignet/provider-mail-resend";
24
+ import { resendMailProvider } from "@beignet/provider-mail-resend";
25
25
  import { createServer } from "@beignet/core/server";
26
26
 
27
27
  const server = await createServer({
28
28
  ports: basePorts,
29
- providers: [mailResendProvider],
29
+ providers: [resendMailProvider],
30
30
  context: ({ ports }) => ({ ports }),
31
31
  routes,
32
32
  });
@@ -43,6 +43,23 @@ Required environment variables:
43
43
  registered in `server/providers.ts` and that both required env vars are present
44
44
  in app env examples or config.
45
45
 
46
+ Use `createResendMailProvider(...)` when you want to pass config directly
47
+ instead of reading `RESEND_*` env vars. Options override env-derived values:
48
+
49
+ ```typescript
50
+ import { createResendMailProvider } from "@beignet/provider-mail-resend";
51
+
52
+ export const providers = [
53
+ createResendMailProvider({
54
+ apiKey: secrets.resendApiKey,
55
+ from: "My App <no-reply@example.com>",
56
+ }),
57
+ ];
58
+ ```
59
+
60
+ `resendMailProvider` is the default env-backed provider, equivalent to
61
+ `createResendMailProvider()`.
62
+
46
63
  ## Use in application code
47
64
 
48
65
  ```typescript
@@ -96,10 +113,20 @@ The provider contributes:
96
113
  When `ctx.ports.devtools` is installed, this provider records `mail.send`,
97
114
  `mail.sent`, and `mail.failed` events under the `mail` watcher.
98
115
 
99
- ## Errors
116
+ ## Failure and retry semantics
117
+
118
+ The provider fails fast and does not retry. Mail delivery is not idempotent: a
119
+ timed-out send may still have been delivered, so a blind provider retry risks
120
+ duplicate emails.
121
+
122
+ Delivery failures throw `MailDeliveryError` from `@beignet/core/mail` with the
123
+ provider name, the recipient count, and the original Resend error preserved as
124
+ `cause`. Message bodies and credentials are never included. Startup
125
+ configuration problems throw during provider setup.
100
126
 
101
- Delivery failures throw `MailDeliveryError` from `@beignet/core/mail`.
102
- Startup configuration problems throw during provider setup.
127
+ When mail needs retries, dispatch it from a job or an outbox-backed listener
128
+ and own idempotency there, for example by recording one delivery per business
129
+ event before sending.
103
130
 
104
131
  ## Local and tests
105
132
 
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export interface ResendMailEscapeHatch {
25
25
  client: Resend;
26
26
  }
27
27
  /**
28
- * Ports contributed by `mailResendProvider`.
28
+ * Ports contributed by `resendMailProvider`.
29
29
  */
30
30
  export interface ResendMailProviderPorts {
31
31
  /**
@@ -57,17 +57,45 @@ export interface CreateResendMailerOptions {
57
57
  /**
58
58
  * Create a Beignet mailer port backed by Resend.
59
59
  *
60
- * Use this directly for custom wiring, or install `mailResendProvider` to load
60
+ * Use this directly for custom wiring, or install `resendMailProvider` to load
61
61
  * config from env and contribute ports during server startup.
62
62
  */
63
63
  export declare function createResendMailer({ client, from, instrumentation: instrumentationTarget, }: CreateResendMailerOptions): MailerPort;
64
64
  /**
65
- * Env-backed Resend mail provider.
65
+ * Options for the Resend mail provider factory.
66
+ */
67
+ export interface CreateResendMailProviderOptions {
68
+ /**
69
+ * Resend API key. Overrides `RESEND_API_KEY`.
70
+ */
71
+ apiKey?: string;
72
+ /**
73
+ * Default sender address, e.g. `My App <no-reply@example.com>`.
74
+ * Overrides `RESEND_FROM`.
75
+ */
76
+ from?: string;
77
+ }
78
+ /**
79
+ * Create an env-backed Resend mail provider.
80
+ *
81
+ * Options override the corresponding `RESEND_*` env values.
82
+ */
83
+ export declare function createResendMailProvider(options?: CreateResendMailProviderOptions): import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
84
+ API_KEY: z.ZodString;
85
+ FROM: z.ZodString;
86
+ }, z.core.$strip>, {
87
+ mailer: MailerPort;
88
+ resend: {
89
+ client: Resend;
90
+ };
91
+ }, unknown, void>;
92
+ /**
93
+ * Default env-backed Resend mail provider.
66
94
  *
67
95
  * Reads `RESEND_API_KEY` and `RESEND_FROM`, contributes `ports.mailer`, and
68
96
  * exposes `ports.resend.client` as an escape hatch.
69
97
  */
70
- export declare const mailResendProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
98
+ export declare const resendMailProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
71
99
  API_KEY: z.ZodString;
72
100
  FROM: z.ZodString;
73
101
  }, z.core.$strip>, {
@@ -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,EAA2B,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,sBAAsB;;;iBAG1B,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;OAEG;IACH,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAqDD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAI,EACJ,eAAe,EAAE,qBAAqB,GACvC,EAAE,yBAAyB,GAAG,UAAU,CAsFxC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;iBAkC7B,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,EAA2B,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,sBAAsB;;;iBAG1B,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,MAAM,EAAE,UAAU,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,IAAI,EAAE,WAAW,CAAC;IAClB;;OAEG;IACH,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AA2DD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAI,EACJ,eAAe,EAAE,qBAAqB,GACvC,EAAE,yBAAyB,GAAG,UAAU,CAwFxC;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,+BAAoC;;;;;;;;kBA2C9C;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;iBAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -16,6 +16,10 @@ function addOptional(target, key, value) {
16
16
  Object.assign(target, { [key]: value });
17
17
  }
18
18
  }
19
+ function countRecipients(message) {
20
+ const count = (list) => list === undefined ? 0 : Array.isArray(list) ? list.length : 1;
21
+ return count(message.to) + count(message.cc) + count(message.bcc);
22
+ }
19
23
  function createResendPayload(message, defaultFrom) {
20
24
  const normalized = normalizeMailMessage(message, { defaultFrom });
21
25
  if (!normalized.from) {
@@ -40,7 +44,7 @@ function createResendPayload(message, defaultFrom) {
40
44
  /**
41
45
  * Create a Beignet mailer port backed by Resend.
42
46
  *
43
- * Use this directly for custom wiring, or install `mailResendProvider` to load
47
+ * Use this directly for custom wiring, or install `resendMailProvider` to load
44
48
  * config from env and contribute ports during server startup.
45
49
  */
46
50
  export function createResendMailer({ client, from, instrumentation: instrumentationTarget, }) {
@@ -51,6 +55,7 @@ export function createResendMailer({ client, from, instrumentation: instrumentat
51
55
  return {
52
56
  async send(message) {
53
57
  const payload = createResendPayload(message, from);
58
+ const recipientCount = countRecipients(message);
54
59
  instrumentation.custom({
55
60
  name: "mail.send",
56
61
  label: "Mail send",
@@ -77,7 +82,7 @@ export function createResendMailer({ client, from, instrumentation: instrumentat
77
82
  });
78
83
  throw new MailDeliveryError({
79
84
  provider: "resend",
80
- message: result.error.message || "Resend failed to send email.",
85
+ message: `Resend failed to send email to ${recipientCount} recipient(s): ${result.error.message || "unknown Resend error"}`,
81
86
  cause: result.error,
82
87
  });
83
88
  }
@@ -114,9 +119,7 @@ export function createResendMailer({ client, from, instrumentation: instrumentat
114
119
  });
115
120
  throw new MailDeliveryError({
116
121
  provider: "resend",
117
- message: error instanceof Error
118
- ? error.message
119
- : "Resend failed to send email.",
122
+ message: `Resend failed to send email to ${recipientCount} recipient(s): ${error instanceof Error ? error.message : String(error)}`,
120
123
  cause: error,
121
124
  });
122
125
  }
@@ -124,37 +127,51 @@ export function createResendMailer({ client, from, instrumentation: instrumentat
124
127
  };
125
128
  }
126
129
  /**
127
- * Env-backed Resend mail provider.
130
+ * Create an env-backed Resend mail provider.
131
+ *
132
+ * Options override the corresponding `RESEND_*` env values.
133
+ */
134
+ export function createResendMailProvider(options = {}) {
135
+ return createProvider({
136
+ name: "mail-resend",
137
+ config: {
138
+ schema: ResendMailConfigSchema,
139
+ envPrefix: "RESEND_",
140
+ // Defined options win over env-derived values at config load time.
141
+ overrides: {
142
+ API_KEY: options.apiKey,
143
+ FROM: options.from,
144
+ },
145
+ },
146
+ async setup({ config, ports }) {
147
+ if (!config) {
148
+ throw new Error("[resendMailProvider] Missing Resend config. " +
149
+ "Please set RESEND_API_KEY and RESEND_FROM environment variables.");
150
+ }
151
+ const { API_KEY: apiKey, FROM: from } = config;
152
+ const client = new Resend(apiKey);
153
+ const instrumentation = createProviderInstrumentation(ports, {
154
+ providerName: "mail-resend",
155
+ watcher: "mail",
156
+ });
157
+ const mailer = createResendMailer({
158
+ client,
159
+ from,
160
+ instrumentation,
161
+ });
162
+ return {
163
+ ports: {
164
+ mailer,
165
+ resend: { client },
166
+ },
167
+ };
168
+ },
169
+ });
170
+ }
171
+ /**
172
+ * Default env-backed Resend mail provider.
128
173
  *
129
174
  * Reads `RESEND_API_KEY` and `RESEND_FROM`, contributes `ports.mailer`, and
130
175
  * exposes `ports.resend.client` as an escape hatch.
131
176
  */
132
- export const mailResendProvider = createProvider({
133
- name: "mail-resend",
134
- config: {
135
- schema: ResendMailConfigSchema,
136
- envPrefix: "RESEND_",
137
- },
138
- async setup({ config, ports }) {
139
- if (!config) {
140
- throw new Error("[mailResendProvider] Missing Resend config. " +
141
- "Please set RESEND_API_KEY and RESEND_FROM environment variables.");
142
- }
143
- const client = new Resend(config.API_KEY);
144
- const instrumentation = createProviderInstrumentation(ports, {
145
- providerName: "mail-resend",
146
- watcher: "mail",
147
- });
148
- const mailer = createResendMailer({
149
- client,
150
- from: config.FROM,
151
- instrumentation,
152
- });
153
- return {
154
- ports: {
155
- mailer,
156
- resend: { client },
157
- },
158
- };
159
- },
160
- });
177
+ export const resendMailProvider = createResendMailProvider();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beignet/provider-mail-resend",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "description": "Resend mail provider for Beignet - adds mailer port using Resend",
6
6
  "main": "./dist/index.js",
@@ -94,7 +94,7 @@
94
94
  "registration": {
95
95
  "required": true,
96
96
  "tokens": [
97
- "mailResendProvider"
97
+ "resendMailProvider"
98
98
  ]
99
99
  }
100
100
  }
package/src/index.ts CHANGED
@@ -43,7 +43,7 @@ export interface ResendMailEscapeHatch {
43
43
  }
44
44
 
45
45
  /**
46
- * Ports contributed by `mailResendProvider`.
46
+ * Ports contributed by `resendMailProvider`.
47
47
  */
48
48
  export interface ResendMailProviderPorts {
49
49
  /**
@@ -84,6 +84,12 @@ function addOptional<T extends Record<string, unknown>, K extends string, V>(
84
84
  }
85
85
  }
86
86
 
87
+ function countRecipients(message: SendMailOptions): number {
88
+ const count = (list: SendMailOptions["cc"]): number =>
89
+ list === undefined ? 0 : Array.isArray(list) ? list.length : 1;
90
+ return count(message.to) + count(message.cc) + count(message.bcc);
91
+ }
92
+
87
93
  function createResendPayload(
88
94
  message: SendMailOptions,
89
95
  defaultFrom: MailAddress,
@@ -128,7 +134,7 @@ function createResendPayload(
128
134
  /**
129
135
  * Create a Beignet mailer port backed by Resend.
130
136
  *
131
- * Use this directly for custom wiring, or install `mailResendProvider` to load
137
+ * Use this directly for custom wiring, or install `resendMailProvider` to load
132
138
  * config from env and contribute ports during server startup.
133
139
  */
134
140
  export function createResendMailer({
@@ -144,6 +150,7 @@ export function createResendMailer({
144
150
  return {
145
151
  async send(message): Promise<SendMailResult> {
146
152
  const payload = createResendPayload(message, from);
153
+ const recipientCount = countRecipients(message);
147
154
  instrumentation.custom({
148
155
  name: "mail.send",
149
156
  label: "Mail send",
@@ -172,7 +179,9 @@ export function createResendMailer({
172
179
  });
173
180
  throw new MailDeliveryError({
174
181
  provider: "resend",
175
- message: result.error.message || "Resend failed to send email.",
182
+ message: `Resend failed to send email to ${recipientCount} recipient(s): ${
183
+ result.error.message || "unknown Resend error"
184
+ }`,
176
185
  cause: result.error,
177
186
  });
178
187
  }
@@ -212,10 +221,9 @@ export function createResendMailer({
212
221
 
213
222
  throw new MailDeliveryError({
214
223
  provider: "resend",
215
- message:
216
- error instanceof Error
217
- ? error.message
218
- : "Resend failed to send email.",
224
+ message: `Resend failed to send email to ${recipientCount} recipient(s): ${
225
+ error instanceof Error ? error.message : String(error)
226
+ }`,
219
227
  cause: error,
220
228
  });
221
229
  }
@@ -224,43 +232,75 @@ export function createResendMailer({
224
232
  }
225
233
 
226
234
  /**
227
- * Env-backed Resend mail provider.
235
+ * Options for the Resend mail provider factory.
236
+ */
237
+ export interface CreateResendMailProviderOptions {
238
+ /**
239
+ * Resend API key. Overrides `RESEND_API_KEY`.
240
+ */
241
+ apiKey?: string;
242
+ /**
243
+ * Default sender address, e.g. `My App <no-reply@example.com>`.
244
+ * Overrides `RESEND_FROM`.
245
+ */
246
+ from?: string;
247
+ }
248
+
249
+ /**
250
+ * Create an env-backed Resend mail provider.
228
251
  *
229
- * Reads `RESEND_API_KEY` and `RESEND_FROM`, contributes `ports.mailer`, and
230
- * exposes `ports.resend.client` as an escape hatch.
252
+ * Options override the corresponding `RESEND_*` env values.
231
253
  */
232
- export const mailResendProvider = createProvider({
233
- name: "mail-resend",
254
+ export function createResendMailProvider(
255
+ options: CreateResendMailProviderOptions = {},
256
+ ) {
257
+ return createProvider({
258
+ name: "mail-resend",
259
+
260
+ config: {
261
+ schema: ResendMailConfigSchema,
262
+ envPrefix: "RESEND_",
263
+ // Defined options win over env-derived values at config load time.
264
+ overrides: {
265
+ API_KEY: options.apiKey,
266
+ FROM: options.from,
267
+ },
268
+ },
234
269
 
235
- config: {
236
- schema: ResendMailConfigSchema,
237
- envPrefix: "RESEND_",
238
- },
270
+ async setup({ config, ports }) {
271
+ if (!config) {
272
+ throw new Error(
273
+ "[resendMailProvider] Missing Resend config. " +
274
+ "Please set RESEND_API_KEY and RESEND_FROM environment variables.",
275
+ );
276
+ }
277
+ const { API_KEY: apiKey, FROM: from } = config;
239
278
 
240
- async setup({ config, ports }) {
241
- if (!config) {
242
- throw new Error(
243
- "[mailResendProvider] Missing Resend config. " +
244
- "Please set RESEND_API_KEY and RESEND_FROM environment variables.",
245
- );
246
- }
279
+ const client = new Resend(apiKey);
280
+ const instrumentation = createProviderInstrumentation(ports, {
281
+ providerName: "mail-resend",
282
+ watcher: "mail",
283
+ });
284
+ const mailer = createResendMailer({
285
+ client,
286
+ from,
287
+ instrumentation,
288
+ });
247
289
 
248
- const client = new Resend(config.API_KEY);
249
- const instrumentation = createProviderInstrumentation(ports, {
250
- providerName: "mail-resend",
251
- watcher: "mail",
252
- });
253
- const mailer = createResendMailer({
254
- client,
255
- from: config.FROM,
256
- instrumentation,
257
- });
290
+ return {
291
+ ports: {
292
+ mailer,
293
+ resend: { client },
294
+ } satisfies ResendMailProviderPorts,
295
+ };
296
+ },
297
+ });
298
+ }
258
299
 
259
- return {
260
- ports: {
261
- mailer,
262
- resend: { client },
263
- } satisfies ResendMailProviderPorts,
264
- };
265
- },
266
- });
300
+ /**
301
+ * Default env-backed Resend mail provider.
302
+ *
303
+ * Reads `RESEND_API_KEY` and `RESEND_FROM`, contributes `ports.mailer`, and
304
+ * exposes `ports.resend.client` as an escape hatch.
305
+ */
306
+ export const resendMailProvider = createResendMailProvider();