@particle-academy/resend-js 0.3.4 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ServiceDescriptor, PreparedRequest, ConnectorMode, ConnectorFaker, RequestedMode, Transport, ConnectorResult } from '@particle-academy/fancy-connector-core';
1
+ import { ServiceDescriptor, PreparedRequest, ConnectorMode, ConnectorFaker, RequestedMode, Transport, ConnectorResult, TriggerDescriptor, HmacScheme, InboundDelivery, WebhookVerification } from '@particle-academy/fancy-connector-core';
2
2
 
3
3
  /**
4
4
  * GENERATED FILE — do not edit.
@@ -77,6 +77,112 @@ declare const RESEND: ServiceDescriptor;
77
77
 
78
78
  declare const resendFaker: ConnectorFaker;
79
79
 
80
+ /**
81
+ * GENERATED FILE — do not edit.
82
+ *
83
+ * Emitted from provider/actions/attachment-get.json by weaver's generator.
84
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
85
+ * being rejected, because it works until it silently does not. Fix
86
+ * provider/actions/attachment-get.json (or weaver's template/) and regenerate:
87
+ *
88
+ * npm run provider -- resend
89
+ */
90
+ /**
91
+ * Turn one attachment of a received email into a signed download URL.
92
+ *
93
+ * GET /emails/receiving/{emailId}/attachments/{attachmentId} —
94
+ * https://resend.com/docs/api-reference/emails/retrieve-received-email-attachment
95
+ *
96
+ * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,
97
+ * no fake/real branch. This describes the request; callConnector resolves the
98
+ * connection, picks the estate, and either calls Resend or calls the faker.
99
+ */
100
+
101
+ declare const ATTACHMENT_GET_OPERATION = "attachment_get";
102
+ type AttachmentGetOptions = {
103
+ /** The node's resolved config. Keys: emailId, attachmentId. */
104
+ config: Record<string, unknown>;
105
+ credentials?: Record<string, string | undefined>;
106
+ mode?: RequestedMode;
107
+ connectionId?: string | null;
108
+ input?: unknown;
109
+ attempts?: number;
110
+ /** Override the transport. The only way to exercise this without a network. */
111
+ transport?: Transport;
112
+ };
113
+ declare function resendAttachmentGet(options: AttachmentGetOptions): Promise<ConnectorResult>;
114
+
115
+ /**
116
+ * GENERATED FILE — do not edit.
117
+ *
118
+ * Emitted from provider/actions/attachment-list.json by weaver's generator.
119
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
120
+ * being rejected, because it works until it silently does not. Fix
121
+ * provider/actions/attachment-list.json (or weaver's template/) and regenerate:
122
+ *
123
+ * npm run provider -- resend
124
+ */
125
+ /**
126
+ * List a received email's attachments, each with a signed download URL.
127
+ *
128
+ * GET /emails/receiving/{emailId}/attachments —
129
+ * https://resend.com/docs/api-reference/emails/list-received-email-attachments
130
+ *
131
+ * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,
132
+ * no fake/real branch. This describes the request; callConnector resolves the
133
+ * connection, picks the estate, and either calls Resend or calls the faker.
134
+ */
135
+
136
+ declare const ATTACHMENT_LIST_OPERATION = "attachment_list";
137
+ type AttachmentListOptions = {
138
+ /** The node's resolved config. Keys: emailId, limit, after, before. */
139
+ config: Record<string, unknown>;
140
+ credentials?: Record<string, string | undefined>;
141
+ mode?: RequestedMode;
142
+ connectionId?: string | null;
143
+ input?: unknown;
144
+ attempts?: number;
145
+ /** Override the transport. The only way to exercise this without a network. */
146
+ transport?: Transport;
147
+ };
148
+ declare function resendAttachmentList(options: AttachmentListOptions): Promise<ConnectorResult>;
149
+
150
+ /**
151
+ * GENERATED FILE — do not edit.
152
+ *
153
+ * Emitted from provider/actions/email-get.json by weaver's generator.
154
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
155
+ * being rejected, because it works until it silently does not. Fix
156
+ * provider/actions/email-get.json (or weaver's template/) and regenerate:
157
+ *
158
+ * npm run provider -- resend
159
+ */
160
+ /**
161
+ * Read a received email in full: its bodies, its headers and the list of its
162
+ * attachments.
163
+ *
164
+ * GET /emails/receiving/{emailId} —
165
+ * https://resend.com/docs/api-reference/emails/retrieve-received-email
166
+ *
167
+ * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,
168
+ * no fake/real branch. This describes the request; callConnector resolves the
169
+ * connection, picks the estate, and either calls Resend or calls the faker.
170
+ */
171
+
172
+ declare const EMAIL_GET_OPERATION = "email_get";
173
+ type EmailGetOptions = {
174
+ /** The node's resolved config. Keys: emailId. */
175
+ config: Record<string, unknown>;
176
+ credentials?: Record<string, string | undefined>;
177
+ mode?: RequestedMode;
178
+ connectionId?: string | null;
179
+ input?: unknown;
180
+ attempts?: number;
181
+ /** Override the transport. The only way to exercise this without a network. */
182
+ transport?: Transport;
183
+ };
184
+ declare function resendEmailGet(options: EmailGetOptions): Promise<ConnectorResult>;
185
+
80
186
  /**
81
187
  * GENERATED FILE — do not edit.
82
188
  *
@@ -113,4 +219,54 @@ type EmailSendOptions = {
113
219
  };
114
220
  declare function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult>;
115
221
 
116
- export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, type EmailSendOptions, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker };
222
+ /**
223
+ * GENERATED FILE — do not edit.
224
+ *
225
+ * Emitted from provider/triggers/email-received.json by weaver's generator.
226
+ * A hand-edit here is destroyed by the next protocol sync, which is worse than
227
+ * being rejected, because it works until it silently does not. Fix
228
+ * provider/triggers/email-received.json (or weaver's template/) and regenerate:
229
+ *
230
+ * npm run provider -- resend
231
+ */
232
+ /**
233
+ * Resend's webhook trigger — the delivery contract.
234
+ *
235
+ * Kept beside the service descriptor rather than inside a node, because a
236
+ * signature scheme is a fact about RESEND. Two Resend triggers must not be
237
+ * able to disagree about how a delivery is verified.
238
+ */
239
+
240
+ declare const RESEND_EMAIL_RECEIVED_SIGNATURE_HEADER = "svix-signature";
241
+ /** The provider's documented replay window, in seconds. */
242
+ declare const RESEND_EMAIL_RECEIVED_TOLERANCE = 300;
243
+ declare const RESEND_EMAIL_RECEIVED_SCHEME: HmacScheme;
244
+ /**
245
+ * Split `v1,…` into its parts.
246
+ *
247
+ * EVERY signature the header carries is collected, and the delivery passes
248
+ * when ANY matches: a provider rolling a secret signs once per active secret,
249
+ * and a first-only rule refused the whole roll as a wrong secret.
250
+ *
251
+ * `v1,<base64> v1,<base64>` -- several during a secret rotation, and Svix says
252
+ * yours must match ONE of them. Every `v1` is offered to the verifier; the
253
+ * first-only rule that Stripe's header once carried refused a whole roll.
254
+ */
255
+ declare function parseResendSignature(raw: string): {
256
+ signatures: string[];
257
+ timestamp?: string;
258
+ };
259
+ declare const RESEND_EMAIL_RECEIVED: TriggerDescriptor;
260
+ /**
261
+ * Verify one inbound Resend delivery.
262
+ *
263
+ * The host calls this BEFORE starting a run, with the body exactly as
264
+ * received. Re-serialised JSON changes key order and whitespace, and produces
265
+ * a mismatch that looks precisely like a wrong secret — hours of debugging the
266
+ * wrong thing.
267
+ *
268
+ * The secret is the connection's `webhookSecret`.
269
+ */
270
+ declare function verifyResendDelivery(delivery: InboundDelivery, webhookSecret: string | undefined, now?: number): Promise<WebhookVerification>;
271
+
272
+ export { ATTACHMENT_GET_OPERATION, ATTACHMENT_LIST_OPERATION, type AttachmentGetOptions, type AttachmentListOptions, CONNECTOR_API_VERSION, EMAIL_GET_OPERATION, EMAIL_SEND_OPERATION, type EmailGetOptions, type EmailSendOptions, RESEND, RESEND_BASE_URLS, RESEND_EMAIL_RECEIVED, RESEND_EMAIL_RECEIVED_SCHEME, RESEND_EMAIL_RECEIVED_SIGNATURE_HEADER, RESEND_EMAIL_RECEIVED_TOLERANCE, RESEND_REQUIRES, parseResendSignature, resendAttachmentGet, resendAttachmentList, resendAuthorize, resendEmailGet, resendEmailSend, resendFaker, verifyResendDelivery };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker } from './chunk-QIYVVKWU.js';
1
+ export { ATTACHMENT_GET_OPERATION, ATTACHMENT_LIST_OPERATION, CONNECTOR_API_VERSION, EMAIL_GET_OPERATION, EMAIL_SEND_OPERATION, RESEND, RESEND_BASE_URLS, RESEND_EMAIL_RECEIVED, RESEND_EMAIL_RECEIVED_SCHEME, RESEND_EMAIL_RECEIVED_SIGNATURE_HEADER, RESEND_EMAIL_RECEIVED_TOLERANCE, RESEND_REQUIRES, parseResendSignature, resendAttachmentGet, resendAttachmentList, resendAuthorize, resendEmailGet, resendEmailSend, resendFaker, verifyResendDelivery } from './chunk-JEKUTTK2.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/resend-js",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Resend for Node — the service descriptor, its faker, its webhook verification, and one function per operation. Plain HTTP on @particle-academy/fancy-connector-core; no vendor SDK.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,6 +38,7 @@
38
38
  },
39
39
  "files": [
40
40
  "dist",
41
+ "art",
41
42
  "README.md",
42
43
  "CHANGELOG.md",
43
44
  "LICENSE"
@@ -49,11 +50,11 @@
49
50
  "prepublishOnly": "tsup"
50
51
  },
51
52
  "dependencies": {
52
- "@particle-academy/fancy-connector-core": ">=0.4.0 <2.0"
53
+ "@particle-academy/fancy-connector-core": ">=0.9.0 <2.0"
53
54
  },
54
55
  "peerDependencies": {
55
56
  "@particle-academy/fancy-flow": ">=0.47.1 <2.0",
56
- "@particle-academy/resend-ui": ">=0.3.0 <0.4.0"
57
+ "@particle-academy/resend-ui": ">=0.4.0 <0.5.0"
57
58
  },
58
59
  "peerDependenciesMeta": {
59
60
  "@particle-academy/fancy-flow": {
@@ -1,99 +0,0 @@
1
- import { callConnector } from '@particle-academy/fancy-connector-core';
2
-
3
- // src/faker.ts
4
- function fakeEmailSend({ config, fake }) {
5
- return {
6
- "id": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`
7
- };
8
- }
9
- var resendFaker = (operation, request) => {
10
- switch (operation) {
11
- case "email_send":
12
- return fakeEmailSend(request);
13
- default:
14
- throw new Error(
15
- `resend: no fake response is defined for "${operation}". Add a fixture under provider/fixtures/ and regenerate \u2014 a connector without a faker cannot be developed against, tested, or demonstrated.`
16
- );
17
- }
18
- };
19
-
20
- // src/service.ts
21
- var CONNECTOR_API_VERSION = 1;
22
- var RESEND_BASE_URLS = {
23
- "live": "https://api.resend.com"
24
- };
25
- var RESEND_REQUIRES = [
26
- "apiKey"
27
- ];
28
- function resendAuthorize(credentials, request, _mode) {
29
- request.headers["User-Agent"] = "fancy-flow-connector";
30
- request.headers.Authorization = `Bearer ${credentials.apiKey ?? ""}`;
31
- }
32
- var RESEND = {
33
- service: "resend",
34
- title: "Resend",
35
- sandbox: "none",
36
- baseUrls: { ...RESEND_BASE_URLS },
37
- requires: [...RESEND_REQUIRES],
38
- authorize: resendAuthorize,
39
- // Resend honours this for 24 hours. It is what makes a retried durable run
40
- // resend nothing instead of mailing the recipient twice -- the difference
41
- // between `idempotent` and `unsafe-to-replay` is that this one is merely
42
- // embarrassing, not financial, but the header costs nothing and removes it
43
- // entirely.
44
- idempotencyHeader: "Idempotency-Key",
45
- faker: resendFaker
46
- };
47
- var EMAIL_SEND_OPERATION = "email_send";
48
- async function resendEmailSend(options) {
49
- const config = options.config ?? {};
50
- if (config.from === void 0 || config.from === null || config.from === "") {
51
- throw new Error(`email_send: "from" is required (From).`);
52
- }
53
- if (config.to === void 0 || config.to === null || config.to === "") {
54
- throw new Error(`email_send: "to" is required (To).`);
55
- }
56
- if (!(config.html !== void 0 && config.html !== null && config.html !== "" || config.text !== void 0 && config.text !== null && config.text !== "")) {
57
- throw new Error(`email_send: needs an "html" or "text" body \u2014 an empty email is never intended.`);
58
- }
59
- return callConnector(RESEND, {
60
- operation: EMAIL_SEND_OPERATION,
61
- config,
62
- input: options.input,
63
- ...options.credentials === void 0 ? {} : { credentials: options.credentials },
64
- ...options.mode === void 0 ? {} : { mode: options.mode },
65
- ...options.connectionId === void 0 ? {} : { connectionId: options.connectionId },
66
- ...options.attempts === void 0 ? {} : { attempts: options.attempts },
67
- ...options.transport === void 0 ? {} : { transport: options.transport },
68
- ...options.idempotencyKey === void 0 ? {} : { idempotencyKey: options.idempotencyKey },
69
- request: {
70
- method: "POST",
71
- path: "/emails",
72
- json: {
73
- "from": String(config.from),
74
- "to": toList(config.to),
75
- "subject": config.subject !== void 0 && config.subject !== null && config.subject !== "" ? String(config.subject) : "",
76
- ...config.html !== void 0 && config.html !== null && config.html !== "" ? { "html": String(config.html) } : {},
77
- ...config.text !== void 0 && config.text !== null && config.text !== "" ? { "text": String(config.text) } : {},
78
- ...config.replyTo !== void 0 && config.replyTo !== null && config.replyTo !== "" ? { "reply_to": String(config.replyTo) } : {},
79
- "headers": headersMap(config.headers)
80
- }
81
- }
82
- });
83
- }
84
- function toList(value) {
85
- const items = Array.isArray(value) ? value.map(String) : typeof value === "string" ? value.split(",") : [];
86
- return items.map((item) => item.trim()).filter(Boolean);
87
- }
88
- function headersMap(value) {
89
- if (!value || typeof value !== "object") return {};
90
- const out = {};
91
- for (const [key, item] of Object.entries(value)) {
92
- if (item !== void 0 && item !== null && item !== "") out[key] = String(item);
93
- }
94
- return out;
95
- }
96
-
97
- export { CONNECTOR_API_VERSION, EMAIL_SEND_OPERATION, RESEND, RESEND_BASE_URLS, RESEND_REQUIRES, resendAuthorize, resendEmailSend, resendFaker };
98
- //# sourceMappingURL=chunk-QIYVVKWU.js.map
99
- //# sourceMappingURL=chunk-QIYVVKWU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/faker.ts","../src/service.ts","../src/actions/email-send.ts"],"names":[],"mappings":"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","file":"chunk-QIYVVKWU.js","sourcesContent":["/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/fixtures/ by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/fixtures/ (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * The Resend faker.\n *\n * Shapes, not behaviour: the goal is that a downstream node sees the field\n * NAMES Resend actually publishes, so an author can wire {{ $json.data.id }}\n * against a fake and have it keep working against the real thing.\n *\n * Deterministic — same inputs, same output. A faker returning a fresh uuid\n * every call cannot be asserted on, so its fixtures degrade to \"it did not\n * throw\", which is the assertion that catches nothing.\n */\n\nimport type { ConnectorFaker, FakeRequest } from \"@particle-academy/fancy-connector-core\";\n\nfunction fakeEmailSend({ config, fake }: FakeRequest): unknown {\n return {\n \"id\": `${fake.hex(8)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(4)}-${fake.hex(12)}`,\n };\n}\n\nexport const resendFaker: ConnectorFaker = (operation, request) => {\n switch (operation) {\n case \"email_send\":\n return fakeEmailSend(request);\n\n default:\n // A faker asked for an operation it has no shape for must SAY so. Making\n // something up would produce a green run whose output silently has none\n // of the fields the author is about to reference.\n throw new Error(\n `resend: no fake response is defined for \"${operation}\". ` +\n \"Add a fixture under provider/fixtures/ and regenerate — a connector without a faker \" +\n \"cannot be developed against, tested, or demonstrated.\",\n );\n }\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/manifest.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/manifest.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Resend, as one service descriptor shared by every Resend operation.\n *\n * @particle-academy/fancy-connector-core carries what is true of ALL\n * connectors. This carries what is true of Resend: its base URL, its auth\n * scheme, its idempotency header, and its faker.\n *\n * ## The sandbox trap, written down where it is used\n *\n * Resend has NO test estate, and somebody checked. Its simulator RECIPIENTS --\n * delivered@resend.dev, bounced@resend.dev, complained@resend.dev,\n * suppressed@resend.dev -- are not one: the send is real, it is billed, and it\n * counts against the quota. Modelling them as a sandbox would put a live send\n * behind a control labelled \"test\", so `mode` does not offer sandbox at all\n * and `fake` is the primary development mode rather than the fallback.\n */\n\nimport type { ConnectorMode, PreparedRequest, ServiceDescriptor } from \"@particle-academy/fancy-connector-core\";\n\nimport { resendFaker } from \"./faker.js\";\n\n/**\n * The connector API version this package was GENERATED against.\n *\n * A literal, never imported. An imported constant lets an upgrade rewrite the\n * very claim it exists to detect, after which the copy agrees with itself\n * forever.\n */\nexport const CONNECTOR_API_VERSION = 1;\n\nexport const RESEND_BASE_URLS = {\n \"live\": \"https://api.resend.com\"\n} as const;\n\n/** Credential keys a remote call cannot proceed without. */\nexport const RESEND_REQUIRES = [\n \"apiKey\"\n] as const;\n\n/**\n * Apply Resend's auth scheme to an outgoing request.\n *\n *\n *\n * The mode is passed in because for some providers auth and estate are the\n * same decision expressed in the URL; here it is unused, and saying so is\n * cheaper than wondering later whether it was forgotten.\n */\nexport function resendAuthorize(\n credentials: Record<string, string | undefined>,\n request: PreparedRequest,\n _mode: ConnectorMode,\n): void {\n request.headers[\"User-Agent\"] = \"fancy-flow-connector\";\n\n request.headers.Authorization = `Bearer ${credentials.apiKey ?? \"\"}`;\n}\n\n/** The Resend service, for the TypeScript runtime. */\nexport const RESEND: ServiceDescriptor = {\n service: \"resend\",\n title: \"Resend\",\n sandbox: \"none\",\n baseUrls: { ...RESEND_BASE_URLS },\n requires: [...RESEND_REQUIRES],\n authorize: resendAuthorize,\n // Resend honours this for 24 hours. It is what makes a retried durable run\n // resend nothing instead of mailing the recipient twice -- the difference\n // between `idempotent` and `unsafe-to-replay` is that this one is merely\n // embarrassing, not financial, but the header costs nothing and removes it\n // entirely.\n idempotencyHeader: \"Idempotency-Key\",\n faker: resendFaker,\n};\n","/**\n * GENERATED FILE — do not edit.\n *\n * Emitted from provider/actions/email-send.json by weaver's generator.\n * A hand-edit here is destroyed by the next protocol sync, which is worse than\n * being rejected, because it works until it silently does not. Fix\n * provider/actions/email-send.json (or weaver's template/) and regenerate:\n *\n * npm run provider -- resend\n */\n\n/**\n * Send an email through Resend.\n *\n * POST /emails — https://resend.com/docs/api-reference/emails/send-email\n *\n * Notice what is NOT here: no key, no base URL, no mode check, no retry loop,\n * no fake/real branch. This describes the request; callConnector resolves the\n * connection, picks the estate, and either calls Resend or calls the faker.\n */\n\nimport {\n callConnector,\n type ConnectorResult,\n type RequestedMode,\n type Transport,\n} from \"@particle-academy/fancy-connector-core\";\nimport { RESEND } from \"../service.js\";\n\nexport const EMAIL_SEND_OPERATION = \"email_send\";\n\nexport type EmailSendOptions = {\n /** The node's resolved config. Keys: from, to, subject, html, text, replyTo, headers. */\n config: Record<string, unknown>;\n credentials?: Record<string, string | undefined>;\n mode?: RequestedMode;\n connectionId?: string | null;\n input?: unknown;\n /** Derived from the run and the step, never fresh. See the note above. */\n idempotencyKey?: string;\n attempts?: number;\n /** Override the transport. The only way to exercise this without a network. */\n transport?: Transport;\n};\n\nexport async function resendEmailSend(options: EmailSendOptions): Promise<ConnectorResult> {\n const config = options.config ?? {};\n\n if (config.from === undefined || config.from === null || config.from === \"\") {\n throw new Error(`email_send: \"from\" is required (From).`);\n }\n\n if (config.to === undefined || config.to === null || config.to === \"\") {\n throw new Error(`email_send: \"to\" is required (To).`);\n }\n\n if (!((config.html !== undefined && config.html !== null && config.html !== \"\") || (config.text !== undefined && config.text !== null && config.text !== \"\"))) {\n throw new Error(`email_send: needs an \"html\" or \"text\" body — an empty email is never intended.`);\n }\n\n return callConnector(RESEND, {\n operation: EMAIL_SEND_OPERATION,\n config,\n input: options.input,\n ...(options.credentials === undefined ? {} : { credentials: options.credentials }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.connectionId === undefined ? {} : { connectionId: options.connectionId }),\n ...(options.attempts === undefined ? {} : { attempts: options.attempts }),\n ...(options.transport === undefined ? {} : { transport: options.transport }),\n ...(options.idempotencyKey === undefined ? {} : { idempotencyKey: options.idempotencyKey }),\n request: {\n method: \"POST\",\n path: \"/emails\",\n json: {\n \"from\": String(config.from),\n \"to\": toList(config.to),\n \"subject\": config.subject !== undefined && config.subject !== null && config.subject !== \"\" ? String(config.subject) : \"\",\n ...(config.html !== undefined && config.html !== null && config.html !== \"\" ? { \"html\": String(config.html) } : {}),\n ...(config.text !== undefined && config.text !== null && config.text !== \"\" ? { \"text\": String(config.text) } : {}),\n ...(config.replyTo !== undefined && config.replyTo !== null && config.replyTo !== \"\" ? { \"reply_to\": String(config.replyTo) } : {}),\n \"headers\": headersMap(config.headers),\n },\n },\n });\n}\n\n/** One value, a \",\"-separated string, or an array — all end up a list. */\nfunction toList(value: unknown): string[] {\n const items = Array.isArray(value)\n ? value.map(String)\n : typeof value === \"string\"\n ? value.split(\",\")\n : [];\n\n return items.map((item) => item.trim()).filter(Boolean);\n}\n\n/** `{ k: \"v\" }` → `{ \"headers\": { \"k\": \"v\" } }` — a JSON body wants the real object. */\nfunction headersMap(value: unknown): Record<string, string> {\n if (!value || typeof value !== \"object\") return {};\n\n const out: Record<string, string> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (item !== undefined && item !== null && item !== \"\") out[key] = String(item);\n }\n\n return out;\n}\n"]}