@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 +48 -0
- package/README.md +32 -5
- package/dist/index.d.ts +32 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -35
- package/package.json +2 -2
- package/src/index.ts +81 -41
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 {
|
|
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: [
|
|
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
|
-
##
|
|
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
|
-
|
|
102
|
-
|
|
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 `
|
|
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 `
|
|
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
|
-
*
|
|
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
|
|
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>, {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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 `
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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 `
|
|
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 `
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
230
|
-
* exposes `ports.resend.client` as an escape hatch.
|
|
252
|
+
* Options override the corresponding `RESEND_*` env values.
|
|
231
253
|
*/
|
|
232
|
-
export
|
|
233
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
290
|
+
return {
|
|
291
|
+
ports: {
|
|
292
|
+
mailer,
|
|
293
|
+
resend: { client },
|
|
294
|
+
} satisfies ResendMailProviderPorts,
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
258
299
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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();
|