@molecule/api-emails-resend 1.0.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/LICENSE ADDED
@@ -0,0 +1,115 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work.
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship.
43
+
44
+ "Contribution" shall mean any work of authorship, including the
45
+ original version of the Work and any modifications or additions
46
+ to that Work, that is intentionally submitted to the Licensor for
47
+ inclusion in the Work by the copyright owner or by an individual or
48
+ Legal Entity authorized to submit on behalf of the copyright owner.
49
+
50
+ "Contributor" shall mean Licensor and any individual or Legal Entity
51
+ on behalf of whom a Contribution has been received by the Licensor and
52
+ subsequently incorporated within the Work.
53
+
54
+ 2. Grant of Copyright License. Subject to the terms and conditions of
55
+ this License, each Contributor hereby grants to You a perpetual,
56
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
57
+ copyright license to reproduce, prepare Derivative Works of,
58
+ publicly display, publicly perform, sublicense, and distribute the
59
+ Work and such Derivative Works in Source or Object form.
60
+
61
+ 3. Grant of Patent License. Subject to the terms and conditions of
62
+ this License, each Contributor hereby grants to You a perpetual,
63
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
64
+ patent license to make, have made, use, offer to sell, sell, import,
65
+ and otherwise transfer the Work.
66
+
67
+ 4. Redistribution. You may reproduce and distribute copies of the
68
+ Work or Derivative Works thereof in any medium, with or without
69
+ modifications, and in Source or Object form, provided that You
70
+ meet the following conditions:
71
+
72
+ (a) You must give any other recipients of the Work or
73
+ Derivative Works a copy of this License; and
74
+
75
+ (b) You must cause any modified files to carry prominent notices
76
+ stating that You changed the files; and
77
+
78
+ (c) You must retain, in the Source form of any Derivative Works
79
+ that You distribute, all copyright, patent, trademark, and
80
+ attribution notices from the Source form of the Work,
81
+ excluding those notices that do not pertain to any part of
82
+ the Derivative Works; and
83
+
84
+ (d) If the Work includes a "NOTICE" text file as part of its
85
+ distribution, then any Derivative Works that You distribute must
86
+ include a readable copy of the attribution notices contained
87
+ within such NOTICE file.
88
+
89
+ 5. Submission of Contributions.
90
+
91
+ 6. Trademarks. This License does not grant permission to use the trade
92
+ names, trademarks, service marks, or product names of the Licensor.
93
+
94
+ 7. Disclaimer of Warranty. Unless required by applicable law or
95
+ agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
96
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
97
+
98
+ 8. Limitation of Liability. In no event and under no legal theory shall
99
+ any Contributor be liable to You for damages.
100
+
101
+ 9. Accepting Warranty or Additional Liability.
102
+
103
+ Copyright 2026 Molecule Dev, Inc.
104
+
105
+ Licensed under the Apache License, Version 2.0 (the "License");
106
+ you may not use this file except in compliance with the License.
107
+ You may obtain a copy of the License at
108
+
109
+ http://www.apache.org/licenses/LICENSE-2.0
110
+
111
+ Unless required by applicable law or agreed to in writing, software
112
+ distributed under the License is distributed on an "AS IS" BASIS,
113
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
114
+ See the License for the specific language governing permissions and
115
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-09-04T12:07:53.348Z
7
+ -->
8
+
9
+ # @molecule/api-emails-resend
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ Resend email provider for molecule.dev.
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { setTransport } from '@molecule/api-emails'
21
+ import { provider } from '@molecule/api-emails-resend'
22
+
23
+ setTransport(provider)
24
+ ```
25
+
26
+ ## Type
27
+
28
+ `provider`
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @molecule/api-emails-resend @molecule/api-bond @molecule/api-emails @molecule/api-secrets
34
+ ```
35
+
36
+ ## API
37
+
38
+ ### Interfaces
39
+
40
+ #### `EmailMessage`
41
+
42
+ Email message options.
43
+
44
+ ```typescript
45
+ interface EmailMessage {
46
+ /**
47
+ * Sender address.
48
+ */
49
+ from: string | EmailAddress
50
+ /**
51
+ * Recipient(s).
52
+ */
53
+ to: string | EmailAddress | (string | EmailAddress)[]
54
+ /**
55
+ * CC recipient(s).
56
+ */
57
+ cc?: string | EmailAddress | (string | EmailAddress)[]
58
+ /**
59
+ * BCC recipient(s).
60
+ */
61
+ bcc?: string | EmailAddress | (string | EmailAddress)[]
62
+ /**
63
+ * Reply-to address.
64
+ */
65
+ replyTo?: string | EmailAddress
66
+ /**
67
+ * Email subject.
68
+ */
69
+ subject: string
70
+ /**
71
+ * Plain text body.
72
+ */
73
+ text?: string
74
+ /**
75
+ * HTML body.
76
+ */
77
+ html?: string
78
+ /**
79
+ * File attachments.
80
+ */
81
+ attachments?: EmailAttachment[]
82
+ /**
83
+ * i18n key for the subject (for client-side translation).
84
+ */
85
+ subjectKey?: string
86
+ /**
87
+ * i18n key for the plain text body (for client-side translation).
88
+ */
89
+ textKey?: string
90
+ /**
91
+ * i18n key for the HTML body (for client-side translation).
92
+ */
93
+ htmlKey?: string
94
+ }
95
+ ```
96
+
97
+ #### `EmailSendResult`
98
+
99
+ Result of sending an email.
100
+
101
+ ```typescript
102
+ interface EmailSendResult {
103
+ /**
104
+ * Whether the email was accepted for delivery.
105
+ */
106
+ accepted: string[]
107
+ /**
108
+ * Addresses that were rejected.
109
+ */
110
+ rejected: string[]
111
+ /**
112
+ * Message ID from the provider.
113
+ */
114
+ messageId?: string
115
+ /**
116
+ * Raw response from the provider.
117
+ */
118
+ response?: string
119
+ }
120
+ ```
121
+
122
+ #### `EmailTransport`
123
+
124
+ Email transport interface.
125
+
126
+ All email providers must implement this interface.
127
+
128
+ ```typescript
129
+ interface EmailTransport {
130
+ /**
131
+ * Sends an email message.
132
+ * @returns The send result.
133
+ */
134
+ sendMail(message: EmailMessage): Promise<EmailSendResult>
135
+ }
136
+ ```
137
+
138
+ #### `ResendAttachment`
139
+
140
+ One entry of a send request's `attachments[]`, exactly as the Resend REST API
141
+ expects it: base64 `content` plus snake_case metadata.
142
+
143
+ ```typescript
144
+ interface ResendAttachment {
145
+ /** File content, base64-encoded. */
146
+ content: string
147
+ /** File name shown to the recipient; Resend derives the MIME type from it when `content_type` is unset. */
148
+ filename: string
149
+ /** MIME type of the file. */
150
+ content_type?: string
151
+ /** Content-ID for an inline image referenced as `cid:` in the HTML body. */
152
+ content_id?: string
153
+ }
154
+ ```
155
+
156
+ #### `ResendClient`
157
+
158
+ The narrow HTTP client this bond uses to reach Resend.
159
+
160
+ ```typescript
161
+ interface ResendClient {
162
+ /**
163
+ * Sends one email via `POST /emails`.
164
+ * @returns The HTTP status and the message id.
165
+ */
166
+ send(request: ResendSendRequest): Promise<ResendSendResponse>
167
+ }
168
+ ```
169
+
170
+ #### `ResendErrorBody`
171
+
172
+ Resend's JSON error body (`{ statusCode, message, name }`), with every field
173
+ optional because the wire is not trusted.
174
+
175
+ ```typescript
176
+ interface ResendErrorBody {
177
+ /** HTTP status Resend reports in the body (may be `null`). */
178
+ statusCode?: number | null
179
+ /** Human-readable error message. */
180
+ message?: string
181
+ /** Machine-readable error name, e.g. `validation_error`, `daily_quota_exceeded`. */
182
+ name?: string
183
+ }
184
+ ```
185
+
186
+ #### `ResendSendRequest`
187
+
188
+ Request body for `POST /emails`. Field names are the REST API's own
189
+ (snake_case, e.g. `reply_to`) — NOT the camelCase the official SDK accepts.
190
+
191
+ ```typescript
192
+ interface ResendSendRequest {
193
+ /** Sender, as a bare address or `Name <address>`. */
194
+ from: string
195
+ /** Recipients (Resend accepts at most 50). */
196
+ to: string[]
197
+ /** Subject line. */
198
+ subject: string
199
+ /** CC recipients. */
200
+ cc?: string[]
201
+ /** BCC recipients. */
202
+ bcc?: string[]
203
+ /** Reply-To address. */
204
+ reply_to?: string
205
+ /** HTML body. */
206
+ html?: string
207
+ /** Plain-text body (Resend derives one from `html` when omitted). */
208
+ text?: string
209
+ /** File attachments. */
210
+ attachments?: ResendAttachment[]
211
+ }
212
+ ```
213
+
214
+ #### `ResendSendResponse`
215
+
216
+ What {@link ResendClient.send} resolves with: the HTTP status of the accepted
217
+ request and the message `id` Resend returned in the response body.
218
+
219
+ ```typescript
220
+ interface ResendSendResponse {
221
+ /** HTTP status code of the response (2xx). */
222
+ status: number
223
+ /** The `id` from the response body, when present. */
224
+ id?: string
225
+ }
226
+ ```
227
+
228
+ ### Classes
229
+
230
+ #### `ResendApiError`
231
+
232
+ A non-2xx response from the Resend API.
233
+
234
+ `status` is the HTTP status, `code` is Resend's machine-readable error `name`
235
+ (e.g. `validation_error`, `rate_limit_exceeded`, `daily_quota_exceeded`) when
236
+ the body carried one, and `body` is the parsed error body. Deliberately NOT
237
+ tagged with `statusCode` / `errorKey`: Resend's status describes OUR request
238
+ to Resend (a 401 is a bad key, a 403 an unverified domain), not the caller's
239
+ request to the app, so the API middleware must fall through to its generic
240
+ 500 rather than echo it.
241
+
242
+ ### Functions
243
+
244
+ #### `getClient()`
245
+
246
+ Returns the Resend HTTP client. Nothing is configured up front: the API key
247
+ and base URL are read from the environment on every `send()`.
248
+
249
+ ```typescript
250
+ function getClient(): ResendClient
251
+ ```
252
+
253
+ **Returns:** The Resend client.
254
+
255
+ #### `sendMail(message)`
256
+
257
+ Sends an email through the Resend API.
258
+
259
+ ```typescript
260
+ function sendMail(message: EmailMessage): Promise<EmailSendResult>
261
+ ```
262
+
263
+ - `message` — The email message (to, from, subject, text/html, attachments).
264
+
265
+ **Returns:** Send result with accepted addresses, message ID, and status code.
266
+
267
+ ### Constants
268
+
269
+ #### `emailsResendSecretDefinitions`
270
+
271
+ Secret definitions required by the Resend email bond.
272
+
273
+ ```typescript
274
+ const emailsResendSecretDefinitions: SecretDefinition[]
275
+ ```
276
+
277
+ #### `provider`
278
+
279
+ The Resend email provider implementing the standard interface.
280
+
281
+ ```typescript
282
+ const provider: EmailTransport
283
+ ```
284
+
285
+ #### `RESEND_DEFAULT_BASE_URL`
286
+
287
+ Resend's REST API base URL. Override with `RESEND_BASE_URL`.
288
+
289
+ ```typescript
290
+ const RESEND_DEFAULT_BASE_URL: 'https://api.resend.com'
291
+ ```
292
+
293
+ ## Core Interface
294
+
295
+ Implements `@molecule/api-emails` interface.
296
+
297
+ ## Bond Wiring
298
+
299
+ Setup function to register this provider with the core interface:
300
+
301
+ ```typescript
302
+ import { setTransport } from '@molecule/api-emails'
303
+ import { provider } from '@molecule/api-emails-resend'
304
+
305
+ export function setupEmailsResend(): void {
306
+ setTransport(provider)
307
+ }
308
+ ```
309
+
310
+ ## Injection Notes
311
+
312
+ ### Requirements
313
+
314
+ Peer dependencies:
315
+
316
+ - `@molecule/api-bond` ^1.0.1
317
+ - `@molecule/api-emails` ^1.0.1
318
+ - `@molecule/api-secrets` ^1.0.1
319
+
320
+ ### Environment Variables
321
+
322
+ - `RESEND_API_KEY` _(required)_ — Resend API key
323
+ - Setup: Resend → API Keys → Create API Key (Sending access). Verify your sending domain under Domains first.
324
+ - Get it here: [https://resend.com/api-keys](https://resend.com/api-keys)
325
+ - Example: `re_...`
326
+ - `RESEND_FROM` _(optional)_ — Default sender
327
+ - Setup: Optional default From address on a verified domain, e.g. "Acme <hello@acme.com>".
328
+ - Get it here: [https://resend.com/domains](https://resend.com/domains)
329
+ - Example: `Acme <hello@example.com>`
330
+
331
+ ### Runtime Dependencies
332
+
333
+ - `@molecule/api-bond`
334
+ - `@molecule/api-emails`
335
+ - `@molecule/api-secrets`
336
+
337
+ - **Zero dependencies — talks to Resend's REST API with the runtime's global
338
+ `fetch`** (`POST https://api.resend.com/emails`), so every app that installs
339
+ this bond stays dependency-free; the official `resend` SDK can be swapped in
340
+ later without changing the bond's interface.
341
+ - **`from` MUST be on a domain you VERIFIED in Resend** (Resend → Domains, DNS
342
+ records added and checked), or the API rejects the send with a 403
343
+ `validation_error` ("domain is not verified"). Before any domain is verified
344
+ the only usable sender is `onboarding@resend.dev` — testing only, and it can
345
+ deliver ONLY to the account owner's own address. Read the sender from config,
346
+ never hardcode a placeholder: set `RESEND_FROM` (e.g.
347
+ `Acme <no-reply@your-verified-domain.com>`) and it is used whenever a
348
+ message's `from` is empty; if both are missing, `sendMail()` throws a tagged
349
+ config-missing error naming `RESEND_FROM`.
350
+ - **Configuration is lazy and env-driven**: `RESEND_API_KEY` (and the optional
351
+ `RESEND_BASE_URL` override for brokers / compatible endpoints) are read on
352
+ EACH send — never at import time — so a key resolved into `process.env`
353
+ after import (late secrets resolution via a secrets bond) is honored. If the
354
+ key is absent at send time, `sendMail()` throws a tagged config-missing
355
+ error (clean 503 / `config.notConfigured`) naming `RESEND_API_KEY` — never
356
+ an opaque Resend 401.
357
+ - **There is NO sandbox / test-mode flag in the Resend API** (nothing like
358
+ SendGrid's `sandboxMode`). Every accepted request is a real send that counts
359
+ against the account's quota — including sends to the simulation recipients
360
+ `delivered@resend.dev`, `bounced@resend.dev` and `complained@resend.dev`,
361
+ which are the supported way to exercise the delivery / bounce / spam paths
362
+ without emailing a real inbox.
363
+ - **Attachments** are sent base64-encoded inline; Resend caps the whole email
364
+ at **40 MB after encoding** (larger sends are rejected). Buffer / string
365
+ content only — a stream attachment throws. A `cid` becomes Resend's
366
+ `content_id` for inline images (`<img src="cid:...">`).
367
+ - **API errors surface as `ResendApiError`** carrying `status` (HTTP) and
368
+ `code` (Resend's error name — `validation_error`, `rate_limit_exceeded`,
369
+ `daily_quota_exceeded`, …). They are deliberately NOT tagged with
370
+ `statusCode` / `errorKey`, so the API middleware returns its generic 500
371
+ instead of echoing Resend's status to the caller. On success `accepted`
372
+ echoes every `to` recipient (Resend returns no per-recipient verdict) and
373
+ `messageId` is the `id` from the response body.
374
+
375
+ ## E2E Tests
376
+
377
+ Integration checklist — drive the real UI (live preview, no mocks). The
378
+ sandbox CAPTURES outbound email instead of sending — read each message with
379
+ the `read_activity` tool (filter type 'email'); the verification/reset link
380
+ is in its payload. Never mock the send or modify production code to expose
381
+ it. Adapt each item to this app's actual screens/flows, and check every box
382
+ off one by one. A box you can't check is an integration bug to fix — not a
383
+ skip:
384
+
385
+ - [ ] Each email-triggering flow (signup verification, password-reset request,
386
+ invites/notifications the app defines) confirms the send in the UI ("check
387
+ your inbox") and a message actually reaches the transport.
388
+ - [ ] The password-reset round-trip completes: request a reset → open the
389
+ captured message → follow its single-use link → set a new password → log
390
+ in with it (and the old password no longer works).
391
+ - [ ] The message body contains a LINK, never the raw token/secret, and renders
392
+ with the app's real name/content (no `undefined` placeholders).
393
+ - [ ] Requesting a reset for an unknown email shows the same neutral UI response
394
+ as a known one (no account-existence oracle).
395
+ - [ ] Account emails go only to the account's own address — no UI or endpoint
396
+ lets an unauthenticated caller send to an arbitrary address.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Browser guard — `@molecule/api-emails-resend` is SERVER-ONLY.
3
+ *
4
+ * Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
5
+ * Evaluating a server package in a browser bundle is always an import-graph mistake
6
+ * (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
7
+ * ("Buffer is not defined") far from the culprit. Throwing here names the package and
8
+ * the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
9
+ * unaffected: the throw requires browser globals AND the absence of a node runtime.
10
+ */
11
+ const g = globalThis;
12
+ if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
13
+ throw new Error('@molecule/api-emails-resend is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
14
+ 'from server code (a server route/function or your API), or dynamic-import it inside ' +
15
+ 'the server handler — never from components or shared client modules, and never ' +
16
+ 'polyfill Buffer/process to silence this.');
17
+ }
18
+ export {};
19
+ //# sourceMappingURL=browser-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-guard.js","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,GAAG,UAIT,CAAA;AACD,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACrF,MAAM,IAAI,KAAK,CACb,sGAAsG;QACpG,sFAAsF;QACtF,iFAAiF;QACjF,0CAA0C,CAC7C,CAAA;AACH,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resend email provider for molecule.dev.
3
+ *
4
+ * @see https://resend.com/docs/api-reference/emails/send-email
5
+ *
6
+ * @remarks
7
+ * - **Zero dependencies — talks to Resend's REST API with the runtime's global
8
+ * `fetch`** (`POST https://api.resend.com/emails`), so every app that installs
9
+ * this bond stays dependency-free; the official `resend` SDK can be swapped in
10
+ * later without changing the bond's interface.
11
+ * - **`from` MUST be on a domain you VERIFIED in Resend** (Resend → Domains, DNS
12
+ * records added and checked), or the API rejects the send with a 403
13
+ * `validation_error` ("domain is not verified"). Before any domain is verified
14
+ * the only usable sender is `onboarding@resend.dev` — testing only, and it can
15
+ * deliver ONLY to the account owner's own address. Read the sender from config,
16
+ * never hardcode a placeholder: set `RESEND_FROM` (e.g.
17
+ * `Acme <no-reply@your-verified-domain.com>`) and it is used whenever a
18
+ * message's `from` is empty; if both are missing, `sendMail()` throws a tagged
19
+ * config-missing error naming `RESEND_FROM`.
20
+ * - **Configuration is lazy and env-driven**: `RESEND_API_KEY` (and the optional
21
+ * `RESEND_BASE_URL` override for brokers / compatible endpoints) are read on
22
+ * EACH send — never at import time — so a key resolved into `process.env`
23
+ * after import (late secrets resolution via a secrets bond) is honored. If the
24
+ * key is absent at send time, `sendMail()` throws a tagged config-missing
25
+ * error (clean 503 / `config.notConfigured`) naming `RESEND_API_KEY` — never
26
+ * an opaque Resend 401.
27
+ * - **There is NO sandbox / test-mode flag in the Resend API** (nothing like
28
+ * SendGrid's `sandboxMode`). Every accepted request is a real send that counts
29
+ * against the account's quota — including sends to the simulation recipients
30
+ * `delivered@resend.dev`, `bounced@resend.dev` and `complained@resend.dev`,
31
+ * which are the supported way to exercise the delivery / bounce / spam paths
32
+ * without emailing a real inbox.
33
+ * - **Attachments** are sent base64-encoded inline; Resend caps the whole email
34
+ * at **40 MB after encoding** (larger sends are rejected). Buffer / string
35
+ * content only — a stream attachment throws. A `cid` becomes Resend's
36
+ * `content_id` for inline images (`<img src="cid:...">`).
37
+ * - **API errors surface as `ResendApiError`** carrying `status` (HTTP) and
38
+ * `code` (Resend's error name — `validation_error`, `rate_limit_exceeded`,
39
+ * `daily_quota_exceeded`, …). They are deliberately NOT tagged with
40
+ * `statusCode` / `errorKey`, so the API middleware returns its generic 500
41
+ * instead of echoing Resend's status to the caller. On success `accepted`
42
+ * echoes every `to` recipient (Resend returns no per-recipient verdict) and
43
+ * `messageId` is the `id` from the response body.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { setTransport } from '@molecule/api-emails'
48
+ * import { provider } from '@molecule/api-emails-resend'
49
+ *
50
+ * setTransport(provider)
51
+ * ```
52
+ *
53
+ * @module
54
+ */
55
+ export * from './browser-guard.js';
56
+ export * from './provider.js';
57
+ export * from './secrets.js';
58
+ export * from './sendMail.js';
59
+ export * from './transport.js';
60
+ export * from './types.js';
61
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resend email provider for molecule.dev.
3
+ *
4
+ * @see https://resend.com/docs/api-reference/emails/send-email
5
+ *
6
+ * @remarks
7
+ * - **Zero dependencies — talks to Resend's REST API with the runtime's global
8
+ * `fetch`** (`POST https://api.resend.com/emails`), so every app that installs
9
+ * this bond stays dependency-free; the official `resend` SDK can be swapped in
10
+ * later without changing the bond's interface.
11
+ * - **`from` MUST be on a domain you VERIFIED in Resend** (Resend → Domains, DNS
12
+ * records added and checked), or the API rejects the send with a 403
13
+ * `validation_error` ("domain is not verified"). Before any domain is verified
14
+ * the only usable sender is `onboarding@resend.dev` — testing only, and it can
15
+ * deliver ONLY to the account owner's own address. Read the sender from config,
16
+ * never hardcode a placeholder: set `RESEND_FROM` (e.g.
17
+ * `Acme <no-reply@your-verified-domain.com>`) and it is used whenever a
18
+ * message's `from` is empty; if both are missing, `sendMail()` throws a tagged
19
+ * config-missing error naming `RESEND_FROM`.
20
+ * - **Configuration is lazy and env-driven**: `RESEND_API_KEY` (and the optional
21
+ * `RESEND_BASE_URL` override for brokers / compatible endpoints) are read on
22
+ * EACH send — never at import time — so a key resolved into `process.env`
23
+ * after import (late secrets resolution via a secrets bond) is honored. If the
24
+ * key is absent at send time, `sendMail()` throws a tagged config-missing
25
+ * error (clean 503 / `config.notConfigured`) naming `RESEND_API_KEY` — never
26
+ * an opaque Resend 401.
27
+ * - **There is NO sandbox / test-mode flag in the Resend API** (nothing like
28
+ * SendGrid's `sandboxMode`). Every accepted request is a real send that counts
29
+ * against the account's quota — including sends to the simulation recipients
30
+ * `delivered@resend.dev`, `bounced@resend.dev` and `complained@resend.dev`,
31
+ * which are the supported way to exercise the delivery / bounce / spam paths
32
+ * without emailing a real inbox.
33
+ * - **Attachments** are sent base64-encoded inline; Resend caps the whole email
34
+ * at **40 MB after encoding** (larger sends are rejected). Buffer / string
35
+ * content only — a stream attachment throws. A `cid` becomes Resend's
36
+ * `content_id` for inline images (`<img src="cid:...">`).
37
+ * - **API errors surface as `ResendApiError`** carrying `status` (HTTP) and
38
+ * `code` (Resend's error name — `validation_error`, `rate_limit_exceeded`,
39
+ * `daily_quota_exceeded`, …). They are deliberately NOT tagged with
40
+ * `statusCode` / `errorKey`, so the API middleware returns its generic 500
41
+ * instead of echoing Resend's status to the caller. On success `accepted`
42
+ * echoes every `to` recipient (Resend returns no per-recipient verdict) and
43
+ * `messageId` is the `id` from the response body.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { setTransport } from '@molecule/api-emails'
48
+ * import { provider } from '@molecule/api-emails-resend'
49
+ *
50
+ * setTransport(provider)
51
+ * ```
52
+ *
53
+ * @module
54
+ */
55
+ export * from './browser-guard.js';
56
+ export * from './provider.js';
57
+ export * from './secrets.js';
58
+ export * from './sendMail.js';
59
+ export * from './transport.js';
60
+ export * from './types.js';
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resend provider implementation.
3
+ *
4
+ * @module
5
+ */
6
+ import type { EmailTransport } from '@molecule/api-emails';
7
+ /**
8
+ * The Resend email provider implementing the standard interface.
9
+ */
10
+ export declare const provider: EmailTransport;
11
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAI1D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,cAEtB,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Resend provider implementation.
3
+ *
4
+ * @module
5
+ */
6
+ import { sendMail } from './sendMail.js';
7
+ /**
8
+ * The Resend email provider implementing the standard interface.
9
+ */
10
+ export const provider = {
11
+ sendMail,
12
+ };
13
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,QAAQ;CACT,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Resend secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import type { SecretDefinition } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the Resend email bond. */
15
+ export declare const emailsResendSecretDefinitions: SecretDefinition[];
16
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,4DAA4D;AAC5D,eAAO,MAAM,6BAA6B,EAAE,gBAAgB,EAgB3D,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Resend secret definitions — self-registered at import time so the
3
+ * runtime secrets registry (`@molecule/api-secrets`) can drive boot-time
4
+ * configuration reports and actionable "not configured" errors.
5
+ *
6
+ * Content is derived MECHANICALLY from this package's mlcl registry secrets
7
+ * entry (label/instructions/setupUrl/example) via the fleet formula, so
8
+ * packages sharing a key register byte-identical definitions and
9
+ * registration order never matters.
10
+ *
11
+ * @module
12
+ */
13
+ import { registerSecrets } from '@molecule/api-secrets';
14
+ /** Secret definitions required by the Resend email bond. */
15
+ export const emailsResendSecretDefinitions = [
16
+ {
17
+ key: 'RESEND_API_KEY',
18
+ description: 'Resend API key — Resend → API Keys → Create API Key (Sending access is enough).',
19
+ helpUrl: 'https://resend.com/api-keys',
20
+ required: true,
21
+ example: 're_....',
22
+ },
23
+ {
24
+ key: 'RESEND_FROM',
25
+ description: 'Resend default sender — Optional. A from address on a domain verified in Resend → Domains; used when a message has no from address.',
26
+ helpUrl: 'https://resend.com/domains',
27
+ required: false,
28
+ example: 'Acme <no-reply@example.com>',
29
+ },
30
+ ];
31
+ registerSecrets(emailsResendSecretDefinitions);
32
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,4DAA4D;AAC5D,MAAM,CAAC,MAAM,6BAA6B,GAAuB;IAC/D;QACE,GAAG,EAAE,gBAAgB;QACrB,WAAW,EAAE,iFAAiF;QAC9F,OAAO,EAAE,6BAA6B;QACtC,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,SAAS;KACnB;IACD;QACE,GAAG,EAAE,aAAa;QAClB,WAAW,EACT,qIAAqI;QACvI,OAAO,EAAE,4BAA4B;QACrC,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,6BAA6B;KACvC;CACF,CAAA;AAED,eAAe,CAAC,6BAA6B,CAAC,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resend email sending functionality.
3
+ *
4
+ * @module
5
+ */
6
+ import type { EmailMessage, EmailSendResult } from '@molecule/api-emails';
7
+ /**
8
+ * Sends an email through the Resend API.
9
+ *
10
+ * @param message - The email message (to, from, subject, text/html, attachments).
11
+ * @returns Send result with accepted addresses, message ID, and status code.
12
+ */
13
+ export declare const sendMail: (message: EmailMessage) => Promise<EmailSendResult>;
14
+ //# sourceMappingURL=sendMail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendMail.d.ts","sourceRoot":"","sources":["../src/sendMail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAGV,YAAY,EACZ,eAAe,EAChB,MAAM,sBAAsB,CAAA;AAgG7B;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAU,SAAS,YAAY,KAAG,OAAO,CAAC,eAAe,CA0C7E,CAAA"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Resend email sending functionality.
3
+ *
4
+ * @module
5
+ */
6
+ import { getLogger } from '@molecule/api-bond';
7
+ import { configNotConfiguredError } from '@molecule/api-secrets';
8
+ import { getClient } from './transport.js';
9
+ const logger = getLogger();
10
+ /** Display-name characters RFC 5322 allows unquoted (atext plus space). */
11
+ const UNQUOTED_NAME = /^[A-Za-z0-9 !#$%&'*+\-/=?^_`{|}~.]*$/;
12
+ /**
13
+ * Format one address the way Resend expects: a bare address, or
14
+ * `Name <address>`. The display name is quoted when it holds characters that
15
+ * would otherwise parse as address syntax (`,`, `<`, `"`, non-ASCII), and any
16
+ * CR/LF in it is collapsed so a display name can never inject a header.
17
+ * @param addr - A string email address or an `EmailAddress` object with name and address.
18
+ * @returns The address string for the request body.
19
+ */
20
+ function formatAddress(addr) {
21
+ if (typeof addr === 'string')
22
+ return addr;
23
+ const name = addr.name?.replace(/[\r\n]+/g, ' ').trim();
24
+ if (!name)
25
+ return addr.address;
26
+ const display = UNQUOTED_NAME.test(name) ? name : `"${name.replace(/(["\\])/g, '\\$1')}"`;
27
+ return `${display} <${addr.address}>`;
28
+ }
29
+ /**
30
+ * Normalize a recipient field (single or array) to Resend's list of address strings.
31
+ * @param field - A single address or array of addresses (string or `EmailAddress`).
32
+ * @returns The formatted address strings.
33
+ */
34
+ function toList(field) {
35
+ return (Array.isArray(field) ? field : [field]).map(formatAddress);
36
+ }
37
+ /**
38
+ * Extract plain email strings from a recipient field.
39
+ * @param field - A single address or array of addresses (string or `EmailAddress`).
40
+ * @returns An array of plain email strings.
41
+ */
42
+ function extractEmails(field) {
43
+ return (Array.isArray(field) ? field : [field]).map((a) => typeof a === 'string' ? a : a.address);
44
+ }
45
+ /**
46
+ * Resolve the sender: the message's own `from` when it names an address,
47
+ * otherwise the `RESEND_FROM` default. Neither → a tagged config-missing error,
48
+ * because Resend refuses a send whose `from` is not on a verified domain and
49
+ * the fix is configuration, not code.
50
+ * @param from - The message's `from` field.
51
+ * @returns The sender string for the request body.
52
+ * @throws {Error} A tagged config-missing error (503 / `config.notConfigured`) naming `RESEND_FROM`.
53
+ */
54
+ function resolveFrom(from) {
55
+ if (from !== undefined) {
56
+ const address = typeof from === 'string' ? from : from.address;
57
+ if (address.trim())
58
+ return formatAddress(from);
59
+ }
60
+ const fallback = process.env.RESEND_FROM?.trim();
61
+ if (fallback)
62
+ return fallback;
63
+ throw configNotConfiguredError('RESEND_FROM', 'email sending');
64
+ }
65
+ /**
66
+ * Convert molecule attachments to Resend's attachment format (base64 content).
67
+ * @param attachments - The molecule email attachments to convert.
68
+ * @returns The Resend-format attachments.
69
+ * @throws {Error} When an attachment's content is a stream.
70
+ */
71
+ function toResendAttachments(attachments) {
72
+ return attachments.map((att) => {
73
+ let content;
74
+ if (Buffer.isBuffer(att.content)) {
75
+ content = att.content.toString('base64');
76
+ }
77
+ else if (typeof att.content === 'string') {
78
+ const encoding = att.encoding && Buffer.isEncoding(att.encoding) ? att.encoding : 'utf-8';
79
+ content = Buffer.from(att.content, encoding).toString('base64');
80
+ }
81
+ else {
82
+ throw new Error('Stream attachments are not supported by the Resend provider');
83
+ }
84
+ return {
85
+ content,
86
+ filename: att.filename,
87
+ ...(att.contentType ? { content_type: att.contentType } : {}),
88
+ ...(att.cid ? { content_id: att.cid } : {}),
89
+ };
90
+ });
91
+ }
92
+ /**
93
+ * Sends an email through the Resend API.
94
+ *
95
+ * @param message - The email message (to, from, subject, text/html, attachments).
96
+ * @returns Send result with accepted addresses, message ID, and status code.
97
+ */
98
+ export const sendMail = async (message) => {
99
+ if (!process.env.RESEND_API_KEY) {
100
+ // Tagged config-missing error → the API middleware returns a clean 503 +
101
+ // 'config.notConfigured', and the message carries the registered
102
+ // definition's description + setup URL. Without this guard the request
103
+ // would go out unauthenticated and the caller would get an opaque Resend
104
+ // 401 that reads like a bad key rather than a missing one.
105
+ throw configNotConfiguredError('RESEND_API_KEY', 'email sending');
106
+ }
107
+ // Also outside the try: a missing sender is a config condition to surface
108
+ // as-is, not an error to log.
109
+ const from = resolveFrom(message.from);
110
+ try {
111
+ const request = {
112
+ from,
113
+ to: toList(message.to),
114
+ subject: message.subject,
115
+ ...(message.text ? { text: message.text } : {}),
116
+ ...(message.html ? { html: message.html } : {}),
117
+ };
118
+ const cc = message.cc ? toList(message.cc) : [];
119
+ if (cc.length)
120
+ request.cc = cc;
121
+ const bcc = message.bcc ? toList(message.bcc) : [];
122
+ if (bcc.length)
123
+ request.bcc = bcc;
124
+ if (message.replyTo)
125
+ request.reply_to = formatAddress(message.replyTo);
126
+ if (message.attachments?.length)
127
+ request.attachments = toResendAttachments(message.attachments);
128
+ // The client reads the API key / base URL from the environment on each
129
+ // send (honors late-resolved secrets).
130
+ const { status, id } = await getClient().send(request);
131
+ return {
132
+ accepted: extractEmails(message.to),
133
+ rejected: [],
134
+ messageId: id,
135
+ response: String(status),
136
+ };
137
+ }
138
+ catch (error) {
139
+ logger.error('Resend sendMail error:', error);
140
+ throw error;
141
+ }
142
+ };
143
+ //# sourceMappingURL=sendMail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendMail.js","sourceRoot":"","sources":["../src/sendMail.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAO9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAA;AAEhE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG1C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAK1B,2EAA2E;AAC3E,MAAM,aAAa,GAAG,sCAAsC,CAAA;AAE5D;;;;;;;GAOG;AACH,SAAS,aAAa,CAAC,IAA2B;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC,OAAO,CAAA;IAC9B,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAA;IACzF,OAAO,GAAG,OAAO,KAAK,IAAI,CAAC,OAAO,GAAG,CAAA;AACvC,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,KAAmB;IACjC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;AACpE,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAmB;IACxC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACxD,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CACtC,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,IAAuC;IAC1D,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAA;QAC9D,IAAI,OAAO,CAAC,IAAI,EAAE;YAAE,OAAO,aAAa,CAAC,IAAI,CAAC,CAAA;IAChD,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAA;IAChD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,wBAAwB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAA;AAChE,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,WAA8B;IACzD,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,IAAI,OAAe,CAAA;QACnB,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACjC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC1C,CAAC;aAAM,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAA;YACzF,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACjE,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAA;QAChF,CAAC;QAED,OAAO;YACL,OAAO;YACP,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,EAAE,OAAqB,EAA4B,EAAE;IAChF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAChC,yEAAyE;QACzE,iEAAiE;QACjE,uEAAuE;QACvE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,wBAAwB,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAA;IACnE,CAAC;IACD,0EAA0E;IAC1E,8BAA8B;IAC9B,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAsB;YACjC,IAAI;YACJ,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAA;QAED,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAC/C,IAAI,EAAE,CAAC,MAAM;YAAE,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAClD,IAAI,GAAG,CAAC,MAAM;YAAE,OAAO,CAAC,GAAG,GAAG,GAAG,CAAA;QACjC,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO,CAAC,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACtE,IAAI,OAAO,CAAC,WAAW,EAAE,MAAM;YAAE,OAAO,CAAC,WAAW,GAAG,mBAAmB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAE/F,uEAAuE;QACvE,uCAAuC;QACvC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAEtD,OAAO;YACL,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,QAAQ,EAAE,EAAE;YACZ,SAAS,EAAE,EAAE;YACb,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC;SACzB,CAAA;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAA;QAC7C,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC,CAAA"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Resend HTTP client — a thin `fetch` wrapper around `POST /emails`.
3
+ *
4
+ * @module
5
+ */
6
+ import './secrets.js';
7
+ import type { ResendClient, ResendErrorBody } from './types.js';
8
+ /** Resend's REST API base URL. Override with `RESEND_BASE_URL`. */
9
+ export declare const RESEND_DEFAULT_BASE_URL = "https://api.resend.com";
10
+ /**
11
+ * A non-2xx response from the Resend API.
12
+ *
13
+ * `status` is the HTTP status, `code` is Resend's machine-readable error `name`
14
+ * (e.g. `validation_error`, `rate_limit_exceeded`, `daily_quota_exceeded`) when
15
+ * the body carried one, and `body` is the parsed error body. Deliberately NOT
16
+ * tagged with `statusCode` / `errorKey`: Resend's status describes OUR request
17
+ * to Resend (a 401 is a bad key, a 403 an unverified domain), not the caller's
18
+ * request to the app, so the API middleware must fall through to its generic
19
+ * 500 rather than echo it.
20
+ */
21
+ export declare class ResendApiError extends Error {
22
+ /** HTTP status of the Resend response. */
23
+ readonly status: number;
24
+ /** Resend's error name from the body, when present. */
25
+ readonly code: string | undefined;
26
+ /** The parsed error body, when the response was JSON. */
27
+ readonly body: ResendErrorBody | undefined;
28
+ /**
29
+ * Builds the error from a non-2xx Resend response.
30
+ * @param status - HTTP status of the response.
31
+ * @param body - The parsed error body, if the response was JSON.
32
+ * @param rawBody - The raw response text (used in the message when the body was not JSON).
33
+ */
34
+ constructor(status: number, body: ResendErrorBody | undefined, rawBody: string);
35
+ }
36
+ /**
37
+ * Returns the Resend HTTP client. Nothing is configured up front: the API key
38
+ * and base URL are read from the environment on every `send()`.
39
+ *
40
+ * @returns The Resend client.
41
+ */
42
+ export declare const getClient: () => ResendClient;
43
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,cAAc,CAAA;AAIrB,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EAGhB,MAAM,YAAY,CAAA;AAEnB,mEAAmE;AACnE,eAAO,MAAM,uBAAuB,2BAA2B,CAAA;AAE/D;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,CAAA;IAE1C;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM;CAQ/E;AAsFD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,QAAO,YAAsB,CAAA"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Resend HTTP client — a thin `fetch` wrapper around `POST /emails`.
3
+ *
4
+ * @module
5
+ */
6
+ // Side-effect import: registers this bond's secret definitions so the
7
+ // runtime registry is populated even when transport.js is imported directly
8
+ // (not through the package barrel).
9
+ import './secrets.js';
10
+ import { configNotConfiguredError } from '@molecule/api-secrets';
11
+ /** Resend's REST API base URL. Override with `RESEND_BASE_URL`. */
12
+ export const RESEND_DEFAULT_BASE_URL = 'https://api.resend.com';
13
+ /**
14
+ * A non-2xx response from the Resend API.
15
+ *
16
+ * `status` is the HTTP status, `code` is Resend's machine-readable error `name`
17
+ * (e.g. `validation_error`, `rate_limit_exceeded`, `daily_quota_exceeded`) when
18
+ * the body carried one, and `body` is the parsed error body. Deliberately NOT
19
+ * tagged with `statusCode` / `errorKey`: Resend's status describes OUR request
20
+ * to Resend (a 401 is a bad key, a 403 an unverified domain), not the caller's
21
+ * request to the app, so the API middleware must fall through to its generic
22
+ * 500 rather than echo it.
23
+ */
24
+ export class ResendApiError extends Error {
25
+ /** HTTP status of the Resend response. */
26
+ status;
27
+ /** Resend's error name from the body, when present. */
28
+ code;
29
+ /** The parsed error body, when the response was JSON. */
30
+ body;
31
+ /**
32
+ * Builds the error from a non-2xx Resend response.
33
+ * @param status - HTTP status of the response.
34
+ * @param body - The parsed error body, if the response was JSON.
35
+ * @param rawBody - The raw response text (used in the message when the body was not JSON).
36
+ */
37
+ constructor(status, body, rawBody) {
38
+ const detail = body?.message ?? (rawBody.trim() || 'no response body');
39
+ super(`Resend API error ${status}${body?.name ? ` (${body.name})` : ''}: ${detail}`);
40
+ this.name = 'ResendApiError';
41
+ this.status = status;
42
+ this.code = body?.name;
43
+ this.body = body;
44
+ }
45
+ }
46
+ /**
47
+ * Whether a value is a non-null object (a JSON object on the wire).
48
+ * @param value - The parsed value.
49
+ * @returns `true` for a plain object.
50
+ */
51
+ const isRecord = (value) => typeof value === 'object' && value !== null;
52
+ /**
53
+ * Parses a response body as JSON, tolerating an empty or non-JSON body.
54
+ * @param text - The raw response text.
55
+ * @returns The parsed value, or `undefined` when the body is empty or not JSON.
56
+ */
57
+ const parseJson = (text) => {
58
+ if (!text)
59
+ return undefined;
60
+ try {
61
+ return JSON.parse(text);
62
+ }
63
+ catch (_error) {
64
+ // Intentional: a non-JSON body (an HTML gateway page, plain text) is
65
+ // reported by the caller via the raw text, so there is nothing to log here.
66
+ return undefined;
67
+ }
68
+ };
69
+ /**
70
+ * Narrows a parsed error response to Resend's `{ statusCode, message, name }` shape.
71
+ * @param parsed - The parsed response body.
72
+ * @returns The error body, or `undefined` when the body was not a JSON object.
73
+ */
74
+ const toErrorBody = (parsed) => {
75
+ if (!isRecord(parsed))
76
+ return undefined;
77
+ return {
78
+ statusCode: typeof parsed.statusCode === 'number' ? parsed.statusCode : null,
79
+ message: typeof parsed.message === 'string' ? parsed.message : undefined,
80
+ name: typeof parsed.name === 'string' ? parsed.name : undefined,
81
+ };
82
+ };
83
+ /**
84
+ * Resolves the API base URL from the environment on each call (late secrets
85
+ * resolution is honored), trimming any trailing slash.
86
+ * @returns The base URL without a trailing slash.
87
+ */
88
+ const resolveBaseUrl = () => (process.env.RESEND_BASE_URL || RESEND_DEFAULT_BASE_URL).replace(/\/+$/, '');
89
+ /**
90
+ * The shared client. Environment is read INSIDE `send()` — never at import
91
+ * time — so a `RESEND_API_KEY` / `RESEND_BASE_URL` resolved into `process.env`
92
+ * after this module is imported (a secrets bond resolving at startup) is the
93
+ * value used on the next send.
94
+ */
95
+ const client = {
96
+ async send(request) {
97
+ const apiKey = process.env.RESEND_API_KEY;
98
+ if (!apiKey) {
99
+ // Tagged config-missing error → clean 503 + 'config.notConfigured'
100
+ // instead of an opaque Resend 401 from an unauthenticated request.
101
+ throw configNotConfiguredError('RESEND_API_KEY', 'email sending');
102
+ }
103
+ const response = await fetch(`${resolveBaseUrl()}/emails`, {
104
+ method: 'POST',
105
+ headers: {
106
+ Authorization: `Bearer ${apiKey}`,
107
+ 'Content-Type': 'application/json',
108
+ },
109
+ body: JSON.stringify(request),
110
+ });
111
+ const text = await response.text();
112
+ const parsed = parseJson(text);
113
+ if (!response.ok) {
114
+ throw new ResendApiError(response.status, toErrorBody(parsed), text);
115
+ }
116
+ return {
117
+ status: response.status,
118
+ id: isRecord(parsed) && typeof parsed.id === 'string' ? parsed.id : undefined,
119
+ };
120
+ },
121
+ };
122
+ /**
123
+ * Returns the Resend HTTP client. Nothing is configured up front: the API key
124
+ * and base URL are read from the environment on every `send()`.
125
+ *
126
+ * @returns The Resend client.
127
+ */
128
+ export const getClient = () => client;
129
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Type definitions for the Resend email provider.
3
+ *
4
+ * @module
5
+ */
6
+ export type { EmailMessage, EmailSendResult, EmailTransport } from '@molecule/api-emails';
7
+ /**
8
+ * One entry of a send request's `attachments[]`, exactly as the Resend REST API
9
+ * expects it: base64 `content` plus snake_case metadata.
10
+ */
11
+ export interface ResendAttachment {
12
+ /** File content, base64-encoded. */
13
+ content: string;
14
+ /** File name shown to the recipient; Resend derives the MIME type from it when `content_type` is unset. */
15
+ filename: string;
16
+ /** MIME type of the file. */
17
+ content_type?: string;
18
+ /** Content-ID for an inline image referenced as `cid:` in the HTML body. */
19
+ content_id?: string;
20
+ }
21
+ /**
22
+ * Request body for `POST /emails`. Field names are the REST API's own
23
+ * (snake_case, e.g. `reply_to`) — NOT the camelCase the official SDK accepts.
24
+ */
25
+ export interface ResendSendRequest {
26
+ /** Sender, as a bare address or `Name <address>`. */
27
+ from: string;
28
+ /** Recipients (Resend accepts at most 50). */
29
+ to: string[];
30
+ /** Subject line. */
31
+ subject: string;
32
+ /** CC recipients. */
33
+ cc?: string[];
34
+ /** BCC recipients. */
35
+ bcc?: string[];
36
+ /** Reply-To address. */
37
+ reply_to?: string;
38
+ /** HTML body. */
39
+ html?: string;
40
+ /** Plain-text body (Resend derives one from `html` when omitted). */
41
+ text?: string;
42
+ /** File attachments. */
43
+ attachments?: ResendAttachment[];
44
+ }
45
+ /**
46
+ * What {@link ResendClient.send} resolves with: the HTTP status of the accepted
47
+ * request and the message `id` Resend returned in the response body.
48
+ */
49
+ export interface ResendSendResponse {
50
+ /** HTTP status code of the response (2xx). */
51
+ status: number;
52
+ /** The `id` from the response body, when present. */
53
+ id?: string;
54
+ }
55
+ /**
56
+ * Resend's JSON error body (`{ statusCode, message, name }`), with every field
57
+ * optional because the wire is not trusted.
58
+ */
59
+ export interface ResendErrorBody {
60
+ /** HTTP status Resend reports in the body (may be `null`). */
61
+ statusCode?: number | null;
62
+ /** Human-readable error message. */
63
+ message?: string;
64
+ /** Machine-readable error name, e.g. `validation_error`, `daily_quota_exceeded`. */
65
+ name?: string;
66
+ }
67
+ /**
68
+ * The narrow HTTP client this bond uses to reach Resend.
69
+ */
70
+ export interface ResendClient {
71
+ /**
72
+ * Sends one email via `POST /emails`.
73
+ * @returns The HTTP status and the message id.
74
+ */
75
+ send(request: ResendSendRequest): Promise<ResendSendResponse>;
76
+ }
77
+ declare global {
78
+ namespace NodeJS {
79
+ /**
80
+ * Process Env interface.
81
+ */
82
+ interface ProcessEnv {
83
+ /**
84
+ * The key used for Resend's API.
85
+ */
86
+ RESEND_API_KEY?: string;
87
+ /**
88
+ * Optional default sender (`Name <address>` or a bare address on a
89
+ * domain verified in Resend), used when a message's `from` is empty.
90
+ */
91
+ RESEND_FROM?: string;
92
+ /**
93
+ * Optional base URL override for the Resend API (e.g. a credential
94
+ * broker or a Resend-compatible endpoint). When unset, the API's
95
+ * default `https://api.resend.com` is used.
96
+ */
97
+ RESEND_BASE_URL?: string;
98
+ }
99
+ }
100
+ }
101
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAEzF;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,CAAA;IAChB,6BAA6B;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,EAAE,EAAE,MAAM,EAAE,CAAA;IACZ,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,qBAAqB;IACrB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAA;IACb,sBAAsB;IACtB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wBAAwB;IACxB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAA;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,qDAAqD;IACrD,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CAC9D;AAED,OAAO,CAAC,MAAM,CAAC;IAEb,UAAU,MAAM,CAAC;QACf;;WAEG;QACH,UAAiB,UAAU;YACzB;;eAEG;YACH,cAAc,CAAC,EAAE,MAAM,CAAA;YAEvB;;;eAGG;YACH,WAAW,CAAC,EAAE,MAAM,CAAA;YAEpB;;;;eAIG;YACH,eAAe,CAAC,EAAE,MAAM,CAAA;SACzB;KACF;CACF"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type definitions for the Resend email provider.
3
+ *
4
+ * @module
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@molecule/api-emails-resend",
3
+ "version": "1.0.0",
4
+ "description": "Resend email provider for molecule.dev.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "vitest run",
11
+ "test:watch": "vitest"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "keywords": [
24
+ "molecule",
25
+ "email",
26
+ "resend"
27
+ ],
28
+ "license": "Apache-2.0",
29
+ "author": "Molecule Dev, Inc. (https://molecule.dev)",
30
+ "devDependencies": {
31
+ "@molecule/api-bond": "1.0.1",
32
+ "@types/node": "26.1.2",
33
+ "typescript": "6.0.3",
34
+ "vitest": "4.1.10"
35
+ },
36
+ "peerDependencies": {
37
+ "@molecule/api-bond": "^1.0.1",
38
+ "@molecule/api-emails": "^1.0.1",
39
+ "@molecule/api-secrets": "^1.0.1"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/molecule-dev/molecule.git",
44
+ "directory": "packages/api/bonds/emails/resend"
45
+ },
46
+ "homepage": "https://github.com/molecule-dev/molecule/tree/main/packages/api/bonds/emails/resend",
47
+ "bugs": "https://github.com/molecule-dev/molecule/issues",
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }