@beignet/provider-mail-resend 0.0.1
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 +5 -0
- package/README.md +90 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/package.json +69 -0
- package/src/index.ts +224 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @beignet/provider-mail-resend
|
|
2
|
+
|
|
3
|
+
Resend-backed mail provider for Beignet.
|
|
4
|
+
|
|
5
|
+
The provider installs the app-facing `ctx.ports.mailer` port and exposes
|
|
6
|
+
`ctx.ports.resend.client` only as an escape hatch for Resend-specific features.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun add @beignet/provider-mail-resend resend
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { mailResendProvider } from "@beignet/provider-mail-resend";
|
|
18
|
+
import { createServer } from "@beignet/core/server";
|
|
19
|
+
|
|
20
|
+
const server = await createServer({
|
|
21
|
+
ports: basePorts,
|
|
22
|
+
providers: [mailResendProvider],
|
|
23
|
+
createContext: ({ ports }) => ({ ports }),
|
|
24
|
+
routes,
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Required environment variables:
|
|
29
|
+
|
|
30
|
+
| Variable | Description |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `RESEND_API_KEY` | Resend API key |
|
|
33
|
+
| `RESEND_FROM` | Default sender, e.g. `My App <no-reply@example.com>` |
|
|
34
|
+
|
|
35
|
+
## Use in application code
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
await ctx.ports.mailer.send({
|
|
39
|
+
to: "user@example.com",
|
|
40
|
+
subject: "Welcome",
|
|
41
|
+
html: "<h1>Hello</h1>",
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The same `MailerPort` works with SMTP, memory fakes, and other adapters:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
await ctx.ports.mailer.send({
|
|
49
|
+
from: { email: "support@example.com", name: "Support" },
|
|
50
|
+
to: ["user@example.com", "admin@example.com"],
|
|
51
|
+
replyTo: "support@example.com",
|
|
52
|
+
subject: "Account updated",
|
|
53
|
+
text: "Your account was updated.",
|
|
54
|
+
html: "<p>Your account was updated.</p>",
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Escape hatch
|
|
59
|
+
|
|
60
|
+
Use the Resend client only when you need a Resend-specific feature not covered
|
|
61
|
+
by `MailerPort`:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
await ctx.ports.resend.client.emails.send({
|
|
65
|
+
from: "sender@example.com",
|
|
66
|
+
to: "user@example.com",
|
|
67
|
+
subject: "Invoice",
|
|
68
|
+
html: "<p>Attached.</p>",
|
|
69
|
+
attachments: [
|
|
70
|
+
{
|
|
71
|
+
filename: "invoice.pdf",
|
|
72
|
+
content: pdfBuffer,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Devtools
|
|
79
|
+
|
|
80
|
+
When `ctx.ports.devtools` is installed, this provider records `mail.send`,
|
|
81
|
+
`mail.sent`, and `mail.failed` events under the `mail` watcher.
|
|
82
|
+
|
|
83
|
+
## Errors
|
|
84
|
+
|
|
85
|
+
Delivery failures throw `MailDeliveryError` from `@beignet/core/mail`.
|
|
86
|
+
Startup configuration problems throw during provider setup.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-mail-resend
|
|
3
|
+
*
|
|
4
|
+
* Resend-backed mail provider for Beignet.
|
|
5
|
+
*/
|
|
6
|
+
import { type MailAddress, type MailerPort } from "@beignet/core/mail";
|
|
7
|
+
import { type ProviderInstrumentationTarget } from "@beignet/core/providers";
|
|
8
|
+
import { Resend } from "resend";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
declare const ResendMailConfigSchema: z.ZodObject<{
|
|
11
|
+
API_KEY: z.ZodString;
|
|
12
|
+
FROM: z.ZodString;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export type ResendMailConfig = z.infer<typeof ResendMailConfigSchema>;
|
|
15
|
+
export interface ResendMailEscapeHatch {
|
|
16
|
+
client: Resend;
|
|
17
|
+
}
|
|
18
|
+
export interface ResendMailProviderPorts {
|
|
19
|
+
mailer: MailerPort;
|
|
20
|
+
resend: ResendMailEscapeHatch;
|
|
21
|
+
}
|
|
22
|
+
export interface CreateResendMailerOptions {
|
|
23
|
+
client: Resend;
|
|
24
|
+
from: MailAddress;
|
|
25
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
26
|
+
}
|
|
27
|
+
export declare function createResendMailer({ client, from, instrumentation: instrumentationTarget, }: CreateResendMailerOptions): MailerPort;
|
|
28
|
+
export declare const mailResendProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
|
|
29
|
+
API_KEY: z.ZodString;
|
|
30
|
+
FROM: z.ZodString;
|
|
31
|
+
}, z.core.$strip>, {
|
|
32
|
+
mailer: MailerPort;
|
|
33
|
+
resend: {
|
|
34
|
+
client: Resend;
|
|
35
|
+
};
|
|
36
|
+
}>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAqDD,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,IAAI,EACJ,eAAe,EAAE,qBAAqB,GACvC,EAAE,yBAAyB,GAAG,UAAU,CAsFxC;AAED,eAAO,MAAM,kBAAkB;;;;;;;;EAkC7B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-mail-resend
|
|
3
|
+
*
|
|
4
|
+
* Resend-backed mail provider for Beignet.
|
|
5
|
+
*/
|
|
6
|
+
import { formatMailAddress, formatMailAddressList, MailDeliveryError, normalizeMailMessage, } from "@beignet/core/mail";
|
|
7
|
+
import { createProvider, createProviderInstrumentation, } from "@beignet/core/providers";
|
|
8
|
+
import { Resend } from "resend";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
const ResendMailConfigSchema = z.object({
|
|
11
|
+
API_KEY: z.string().min(1),
|
|
12
|
+
FROM: z.string().min(1),
|
|
13
|
+
});
|
|
14
|
+
function addOptional(target, key, value) {
|
|
15
|
+
if (value !== undefined) {
|
|
16
|
+
Object.assign(target, { [key]: value });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function createResendPayload(message, defaultFrom) {
|
|
20
|
+
const normalized = normalizeMailMessage(message, { defaultFrom });
|
|
21
|
+
if (!normalized.from) {
|
|
22
|
+
throw new MailDeliveryError({
|
|
23
|
+
provider: "resend",
|
|
24
|
+
message: "Cannot send email without a from address.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const payload = {
|
|
28
|
+
from: formatMailAddress(normalized.from),
|
|
29
|
+
to: formatMailAddressList(normalized.to),
|
|
30
|
+
subject: normalized.subject,
|
|
31
|
+
};
|
|
32
|
+
addOptional(payload, "text", normalized.text);
|
|
33
|
+
addOptional(payload, "html", normalized.html);
|
|
34
|
+
addOptional(payload, "cc", normalized.cc ? formatMailAddressList(normalized.cc) : undefined);
|
|
35
|
+
addOptional(payload, "bcc", normalized.bcc ? formatMailAddressList(normalized.bcc) : undefined);
|
|
36
|
+
addOptional(payload, "replyTo", normalized.replyTo ? formatMailAddressList(normalized.replyTo) : undefined);
|
|
37
|
+
addOptional(payload, "headers", normalized.headers);
|
|
38
|
+
return payload;
|
|
39
|
+
}
|
|
40
|
+
export function createResendMailer({ client, from, instrumentation: instrumentationTarget, }) {
|
|
41
|
+
const instrumentation = createProviderInstrumentation(instrumentationTarget, {
|
|
42
|
+
providerName: "mail-resend",
|
|
43
|
+
watcher: "mail",
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
async send(message) {
|
|
47
|
+
const payload = createResendPayload(message, from);
|
|
48
|
+
instrumentation.custom({
|
|
49
|
+
name: "mail.send",
|
|
50
|
+
label: "Mail send",
|
|
51
|
+
summary: `Sending "${message.subject}"`,
|
|
52
|
+
details: {
|
|
53
|
+
provider: "resend",
|
|
54
|
+
subject: message.subject,
|
|
55
|
+
to: payload.to,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
const result = await client.emails.send(payload);
|
|
60
|
+
if (result.error) {
|
|
61
|
+
instrumentation.custom({
|
|
62
|
+
name: "mail.failed",
|
|
63
|
+
label: "Mail failed",
|
|
64
|
+
summary: `Failed to send "${message.subject}"`,
|
|
65
|
+
details: {
|
|
66
|
+
provider: "resend",
|
|
67
|
+
subject: message.subject,
|
|
68
|
+
to: payload.to,
|
|
69
|
+
error: result.error.message,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
throw new MailDeliveryError({
|
|
73
|
+
provider: "resend",
|
|
74
|
+
message: result.error.message || "Resend failed to send email.",
|
|
75
|
+
cause: result.error,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
instrumentation.custom({
|
|
79
|
+
name: "mail.sent",
|
|
80
|
+
label: "Mail sent",
|
|
81
|
+
summary: `Sent "${message.subject}"`,
|
|
82
|
+
details: {
|
|
83
|
+
provider: "resend",
|
|
84
|
+
subject: message.subject,
|
|
85
|
+
to: payload.to,
|
|
86
|
+
id: result.data?.id,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
id: result.data?.id,
|
|
91
|
+
provider: "resend",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof MailDeliveryError) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
instrumentation.custom({
|
|
99
|
+
name: "mail.failed",
|
|
100
|
+
label: "Mail failed",
|
|
101
|
+
summary: `Failed to send "${message.subject}"`,
|
|
102
|
+
details: {
|
|
103
|
+
provider: "resend",
|
|
104
|
+
subject: message.subject,
|
|
105
|
+
to: payload.to,
|
|
106
|
+
error: error instanceof Error ? error.message : String(error),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
throw new MailDeliveryError({
|
|
110
|
+
provider: "resend",
|
|
111
|
+
message: error instanceof Error
|
|
112
|
+
? error.message
|
|
113
|
+
: "Resend failed to send email.",
|
|
114
|
+
cause: error,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export const mailResendProvider = createProvider({
|
|
121
|
+
name: "mail-resend",
|
|
122
|
+
config: {
|
|
123
|
+
schema: ResendMailConfigSchema,
|
|
124
|
+
envPrefix: "RESEND_",
|
|
125
|
+
},
|
|
126
|
+
async setup({ config, ports }) {
|
|
127
|
+
if (!config) {
|
|
128
|
+
throw new Error("[mailResendProvider] Missing Resend config. " +
|
|
129
|
+
"Please set RESEND_API_KEY and RESEND_FROM environment variables.");
|
|
130
|
+
}
|
|
131
|
+
const client = new Resend(config.API_KEY);
|
|
132
|
+
const instrumentation = createProviderInstrumentation(ports, {
|
|
133
|
+
providerName: "mail-resend",
|
|
134
|
+
watcher: "mail",
|
|
135
|
+
});
|
|
136
|
+
const mailer = createResendMailer({
|
|
137
|
+
client,
|
|
138
|
+
from: config.FROM,
|
|
139
|
+
instrumentation,
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
ports: {
|
|
143
|
+
mailer,
|
|
144
|
+
resend: { client },
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beignet/provider-mail-resend",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Resend mail provider for Beignet - adds mailer port using Resend",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"!src/**/*.test.ts",
|
|
18
|
+
"!src/**/*.test.tsx",
|
|
19
|
+
"!src/**/*.test-d.ts",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"clean": "rm -rf dist coverage .turbo",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"test:coverage": "bun test --coverage",
|
|
29
|
+
"lint": "biome check ."
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"beignet",
|
|
33
|
+
"mail",
|
|
34
|
+
"email",
|
|
35
|
+
"provider",
|
|
36
|
+
"resend",
|
|
37
|
+
"ports"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/taylorbryant/beignet.git",
|
|
43
|
+
"directory": "packages/provider-mail-resend"
|
|
44
|
+
},
|
|
45
|
+
"author": "Taylor Bryant",
|
|
46
|
+
"homepage": "https://github.com/taylorbryant/beignet#readme",
|
|
47
|
+
"bugs": "https://github.com/taylorbryant/beignet/issues",
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"resend": "^2.0.0 || ^3.0.0 || ^4.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"zod": "^4.0.0",
|
|
60
|
+
"@beignet/core": "*"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@beignet/devtools": "*",
|
|
64
|
+
"@types/bun": "^1.3.13",
|
|
65
|
+
"@types/node": "^20.10.0",
|
|
66
|
+
"resend": "^4.0.1",
|
|
67
|
+
"typescript": "^5.3.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-mail-resend
|
|
3
|
+
*
|
|
4
|
+
* Resend-backed mail provider for Beignet.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
formatMailAddress,
|
|
9
|
+
formatMailAddressList,
|
|
10
|
+
type MailAddress,
|
|
11
|
+
MailDeliveryError,
|
|
12
|
+
type MailerPort,
|
|
13
|
+
normalizeMailMessage,
|
|
14
|
+
type SendMailOptions,
|
|
15
|
+
type SendMailResult,
|
|
16
|
+
} from "@beignet/core/mail";
|
|
17
|
+
import {
|
|
18
|
+
createProvider,
|
|
19
|
+
createProviderInstrumentation,
|
|
20
|
+
type ProviderInstrumentationTarget,
|
|
21
|
+
} from "@beignet/core/providers";
|
|
22
|
+
import { type CreateEmailOptions, Resend } from "resend";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
|
|
25
|
+
const ResendMailConfigSchema = z.object({
|
|
26
|
+
API_KEY: z.string().min(1),
|
|
27
|
+
FROM: z.string().min(1),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type ResendMailConfig = z.infer<typeof ResendMailConfigSchema>;
|
|
31
|
+
|
|
32
|
+
export interface ResendMailEscapeHatch {
|
|
33
|
+
client: Resend;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ResendMailProviderPorts {
|
|
37
|
+
mailer: MailerPort;
|
|
38
|
+
resend: ResendMailEscapeHatch;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CreateResendMailerOptions {
|
|
42
|
+
client: Resend;
|
|
43
|
+
from: MailAddress;
|
|
44
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function addOptional<T extends Record<string, unknown>, K extends string, V>(
|
|
48
|
+
target: T,
|
|
49
|
+
key: K,
|
|
50
|
+
value: V | undefined,
|
|
51
|
+
): void {
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
Object.assign(target, { [key]: value });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createResendPayload(
|
|
58
|
+
message: SendMailOptions,
|
|
59
|
+
defaultFrom: MailAddress,
|
|
60
|
+
): CreateEmailOptions {
|
|
61
|
+
const normalized = normalizeMailMessage(message, { defaultFrom });
|
|
62
|
+
|
|
63
|
+
if (!normalized.from) {
|
|
64
|
+
throw new MailDeliveryError({
|
|
65
|
+
provider: "resend",
|
|
66
|
+
message: "Cannot send email without a from address.",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const payload = {
|
|
71
|
+
from: formatMailAddress(normalized.from),
|
|
72
|
+
to: formatMailAddressList(normalized.to),
|
|
73
|
+
subject: normalized.subject,
|
|
74
|
+
} satisfies Partial<CreateEmailOptions>;
|
|
75
|
+
|
|
76
|
+
addOptional(payload, "text", normalized.text);
|
|
77
|
+
addOptional(payload, "html", normalized.html);
|
|
78
|
+
addOptional(
|
|
79
|
+
payload,
|
|
80
|
+
"cc",
|
|
81
|
+
normalized.cc ? formatMailAddressList(normalized.cc) : undefined,
|
|
82
|
+
);
|
|
83
|
+
addOptional(
|
|
84
|
+
payload,
|
|
85
|
+
"bcc",
|
|
86
|
+
normalized.bcc ? formatMailAddressList(normalized.bcc) : undefined,
|
|
87
|
+
);
|
|
88
|
+
addOptional(
|
|
89
|
+
payload,
|
|
90
|
+
"replyTo",
|
|
91
|
+
normalized.replyTo ? formatMailAddressList(normalized.replyTo) : undefined,
|
|
92
|
+
);
|
|
93
|
+
addOptional(payload, "headers", normalized.headers);
|
|
94
|
+
|
|
95
|
+
return payload as CreateEmailOptions;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function createResendMailer({
|
|
99
|
+
client,
|
|
100
|
+
from,
|
|
101
|
+
instrumentation: instrumentationTarget,
|
|
102
|
+
}: CreateResendMailerOptions): MailerPort {
|
|
103
|
+
const instrumentation = createProviderInstrumentation(instrumentationTarget, {
|
|
104
|
+
providerName: "mail-resend",
|
|
105
|
+
watcher: "mail",
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
async send(message): Promise<SendMailResult> {
|
|
110
|
+
const payload = createResendPayload(message, from);
|
|
111
|
+
instrumentation.custom({
|
|
112
|
+
name: "mail.send",
|
|
113
|
+
label: "Mail send",
|
|
114
|
+
summary: `Sending "${message.subject}"`,
|
|
115
|
+
details: {
|
|
116
|
+
provider: "resend",
|
|
117
|
+
subject: message.subject,
|
|
118
|
+
to: payload.to,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result = await client.emails.send(payload);
|
|
124
|
+
|
|
125
|
+
if (result.error) {
|
|
126
|
+
instrumentation.custom({
|
|
127
|
+
name: "mail.failed",
|
|
128
|
+
label: "Mail failed",
|
|
129
|
+
summary: `Failed to send "${message.subject}"`,
|
|
130
|
+
details: {
|
|
131
|
+
provider: "resend",
|
|
132
|
+
subject: message.subject,
|
|
133
|
+
to: payload.to,
|
|
134
|
+
error: result.error.message,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
throw new MailDeliveryError({
|
|
138
|
+
provider: "resend",
|
|
139
|
+
message: result.error.message || "Resend failed to send email.",
|
|
140
|
+
cause: result.error,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
instrumentation.custom({
|
|
145
|
+
name: "mail.sent",
|
|
146
|
+
label: "Mail sent",
|
|
147
|
+
summary: `Sent "${message.subject}"`,
|
|
148
|
+
details: {
|
|
149
|
+
provider: "resend",
|
|
150
|
+
subject: message.subject,
|
|
151
|
+
to: payload.to,
|
|
152
|
+
id: result.data?.id,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
id: result.data?.id,
|
|
158
|
+
provider: "resend",
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof MailDeliveryError) {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
instrumentation.custom({
|
|
166
|
+
name: "mail.failed",
|
|
167
|
+
label: "Mail failed",
|
|
168
|
+
summary: `Failed to send "${message.subject}"`,
|
|
169
|
+
details: {
|
|
170
|
+
provider: "resend",
|
|
171
|
+
subject: message.subject,
|
|
172
|
+
to: payload.to,
|
|
173
|
+
error: error instanceof Error ? error.message : String(error),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
throw new MailDeliveryError({
|
|
178
|
+
provider: "resend",
|
|
179
|
+
message:
|
|
180
|
+
error instanceof Error
|
|
181
|
+
? error.message
|
|
182
|
+
: "Resend failed to send email.",
|
|
183
|
+
cause: error,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const mailResendProvider = createProvider({
|
|
191
|
+
name: "mail-resend",
|
|
192
|
+
|
|
193
|
+
config: {
|
|
194
|
+
schema: ResendMailConfigSchema,
|
|
195
|
+
envPrefix: "RESEND_",
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
async setup({ config, ports }) {
|
|
199
|
+
if (!config) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
"[mailResendProvider] Missing Resend config. " +
|
|
202
|
+
"Please set RESEND_API_KEY and RESEND_FROM environment variables.",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const client = new Resend(config.API_KEY);
|
|
207
|
+
const instrumentation = createProviderInstrumentation(ports, {
|
|
208
|
+
providerName: "mail-resend",
|
|
209
|
+
watcher: "mail",
|
|
210
|
+
});
|
|
211
|
+
const mailer = createResendMailer({
|
|
212
|
+
client,
|
|
213
|
+
from: config.FROM,
|
|
214
|
+
instrumentation,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
ports: {
|
|
219
|
+
mailer,
|
|
220
|
+
resend: { client },
|
|
221
|
+
} satisfies ResendMailProviderPorts,
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
});
|