sently 0.4.3 → 0.4.5
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 +54 -0
- package/README.md +44 -0
- package/dist/adapters/bun.d.ts +3 -0
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.d.ts +3 -0
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.d.ts +3 -0
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.d.ts +3 -0
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.d.ts +3 -0
- package/dist/{chunk-8sm0vz0n.js → chunk-7fqv71z1.js} +10 -4
- package/dist/chunk-7fqv71z1.js.map +10 -0
- package/dist/{chunk-bvxkmq94.js → chunk-f4c9ttmr.js} +34 -5
- package/dist/chunk-f4c9ttmr.js.map +11 -0
- package/dist/{chunk-bfrvnqhq.js → chunk-mp5c9bfd.js} +3 -3
- package/dist/{chunk-bfrvnqhq.js.map → chunk-mp5c9bfd.js.map} +2 -2
- package/dist/{chunk-sbydk09g.js → chunk-tymfm441.js} +5 -3
- package/dist/{chunk-sbydk09g.js.map → chunk-tymfm441.js.map} +3 -3
- package/dist/{chunk-j6qw8ms6.js → chunk-x3szga4k.js} +21 -7
- package/dist/chunk-x3szga4k.js.map +11 -0
- package/dist/chunk-ym3zzv8b.js.map +2 -2
- package/dist/core/address.d.ts +30 -0
- package/dist/core/smtp.js +1 -1
- package/dist/core/types.d.ts +71 -0
- package/dist/detect.js +5 -5
- package/dist/pool/pool.d.ts +25 -0
- package/dist/pool/pool.js +5 -5
- package/dist/transports/brevo.js +1 -1
- package/dist/transports/mailgun.js +1 -1
- package/dist/transports/postmark.d.ts +1 -0
- package/dist/transports/postmark.js +1 -1
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -2
- package/dist/transports/resend.d.ts +2 -0
- package/dist/transports/resend.js +1 -1
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/resolve-attachments.d.ts +10 -2
- package/dist/transports/retry.js +1 -1
- package/dist/transports/sendgrid.d.ts +1 -0
- package/dist/transports/sendgrid.js +1 -1
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +4 -4
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.d.ts +13 -0
- package/dist/transports/smtp.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-8sm0vz0n.js.map +0 -10
- package/dist/chunk-bvxkmq94.js.map +0 -11
- package/dist/chunk-j6qw8ms6.js.map +0 -11
package/dist/detect.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SMTPPool
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-mp5c9bfd.js";
|
|
4
4
|
import {
|
|
5
5
|
SMTPTransport
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7fqv71z1.js";
|
|
7
7
|
import"./chunk-ym3zzv8b.js";
|
|
8
|
-
import"./chunk-
|
|
9
|
-
import"./chunk-
|
|
10
|
-
import"./chunk-
|
|
8
|
+
import"./chunk-x3szga4k.js";
|
|
9
|
+
import"./chunk-tymfm441.js";
|
|
10
|
+
import"./chunk-f4c9ttmr.js";
|
|
11
11
|
import"./chunk-794hc3m4.js";
|
|
12
12
|
import {
|
|
13
13
|
__require
|
package/dist/pool/pool.d.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* SMTP connection pool with optional rate limiting.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { SMTPPool } from "sently/pool";
|
|
8
|
+
* import { NodeAdapter } from "sently/adapters/node";
|
|
9
|
+
*
|
|
10
|
+
* const pool = new SMTPPool({
|
|
11
|
+
* host: "smtp.example.com",
|
|
12
|
+
* auth: { user: "you@example.com", pass: "secret" },
|
|
13
|
+
* adapter: new NodeAdapter(),
|
|
14
|
+
* pool: true,
|
|
15
|
+
* maxConnections: 5,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* await pool.send({
|
|
19
|
+
* from: "you@example.com",
|
|
20
|
+
* to: "recipient@example.com",
|
|
21
|
+
* subject: "Hello",
|
|
22
|
+
* text: "Pooled send",
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
1
26
|
import type { MailOptions, PoolConfig, SendResult, SMTPConfig, SocketAdapter, Transport, VerifyResult } from "../core/types.js";
|
|
2
27
|
/** Options for {@link SMTPPool}. */
|
|
3
28
|
export interface SMTPPoolOptions {
|
package/dist/pool/pool.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RateLimiter,
|
|
3
3
|
SMTPPool
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import"../chunk-
|
|
4
|
+
} from "../chunk-mp5c9bfd.js";
|
|
5
|
+
import"../chunk-7fqv71z1.js";
|
|
6
6
|
import"../chunk-ym3zzv8b.js";
|
|
7
|
-
import"../chunk-
|
|
8
|
-
import"../chunk-
|
|
9
|
-
import"../chunk-
|
|
7
|
+
import"../chunk-x3szga4k.js";
|
|
8
|
+
import"../chunk-tymfm441.js";
|
|
9
|
+
import"../chunk-f4c9ttmr.js";
|
|
10
10
|
import"../chunk-794hc3m4.js";
|
|
11
11
|
import"../chunk-v0bahtg2.js";
|
|
12
12
|
export {
|
package/dist/transports/brevo.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
2
|
/** Postmark API configuration. */
|
|
3
3
|
export interface PostmarkConfig {
|
|
4
|
+
/** Postmark server API token. */
|
|
4
5
|
serverToken: string;
|
|
5
6
|
}
|
|
6
7
|
/** Error thrown when the Postmark API returns a non-success response. */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/postmark.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Postmark HTTP API transport for sending email via api.postmarkapp.com.\n *\n * @example\n * ```ts\n * import { PostmarkTransport } from \"sently/transports/postmark\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new PostmarkTransport({ serverToken: process.env.POSTMARK_TOKEN! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Postmark API configuration. */\nexport interface PostmarkConfig {\n serverToken: string;\n}\n\n/** Error thrown when the Postmark API returns a non-success response. */\nexport class PostmarkError extends Error {\n /** Creates a Postmark API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"PostmarkError\";\n }\n}\n\n/**\n * Postmark HTTP API transport.\n */\nexport class PostmarkTransport implements Transport {\n private readonly serverToken: string;\n\n /** Creates a Postmark transport with the given server token. */\n constructor(config: PostmarkConfig) {\n this.serverToken = config.serverToken;\n }\n\n /** Sends an email via the Postmark HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body = {\n From: from ? toMIMEHeader(from) : \"\",\n To: parseAddresses(options.to).map(toMIMEHeader).join(\", \"),\n Subject: options.subject,\n ...(options.cc ? { Cc: parseAddresses(options.cc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.bcc ? { Bcc: parseAddresses(options.bcc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.replyTo\n ? { ReplyTo: parseAddresses(options.replyTo).map(toMIMEHeader).join(\", \") }\n : {}),\n ...(options.text ? { TextBody: options.text } : {}),\n ...(options.html ? { HtmlBody: options.html } : {}),\n ...(options.headers\n ? { Headers: Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value })) }\n : {}),\n ...(attachments.length > 0\n ? {\n Attachments: attachments.map((att) => ({\n Name: att.filename,\n Content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ContentType: att.contentType ?? \"application/octet-stream\",\n ...(att.contentId ? { ContentID: att.contentId } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.postmarkapp.com/email\", {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n MessageID?: string;\n Message?: string;\n ErrorCode?: number;\n };\n\n if (!response.ok) {\n throw new PostmarkError(payload.Message ?? \"Postmark API error\", response.status, payload);\n }\n\n return {\n messageId: payload.MessageID ?? options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: payload.Message ?? \"OK\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Postmark server token by fetching server info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.postmarkapp.com/server\", {\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n Accept: \"application/json\",\n },\n });\n\n const payload = (await response.json()) as { Name?: string; Message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"postmark\",\n message: payload.Message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"postmark\",\n ...(payload.Name ? { message: payload.Name } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"postmark\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Postmark HTTP API transport for sending email via api.postmarkapp.com.\n *\n * @example\n * ```ts\n * import { PostmarkTransport } from \"sently/transports/postmark\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new PostmarkTransport({ serverToken: process.env.POSTMARK_TOKEN! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Postmark API configuration. */\nexport interface PostmarkConfig {\n /** Postmark server API token. */\n serverToken: string;\n}\n\n/** Error thrown when the Postmark API returns a non-success response. */\nexport class PostmarkError extends Error {\n /** Creates a Postmark API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"PostmarkError\";\n }\n}\n\n/**\n * Postmark HTTP API transport.\n */\nexport class PostmarkTransport implements Transport {\n private readonly serverToken: string;\n\n /** Creates a Postmark transport with the given server token. */\n constructor(config: PostmarkConfig) {\n this.serverToken = config.serverToken;\n }\n\n /** Sends an email via the Postmark HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body = {\n From: from ? toMIMEHeader(from) : \"\",\n To: parseAddresses(options.to).map(toMIMEHeader).join(\", \"),\n Subject: options.subject,\n ...(options.cc ? { Cc: parseAddresses(options.cc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.bcc ? { Bcc: parseAddresses(options.bcc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.replyTo\n ? { ReplyTo: parseAddresses(options.replyTo).map(toMIMEHeader).join(\", \") }\n : {}),\n ...(options.text ? { TextBody: options.text } : {}),\n ...(options.html ? { HtmlBody: options.html } : {}),\n ...(options.headers\n ? { Headers: Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value })) }\n : {}),\n ...(attachments.length > 0\n ? {\n Attachments: attachments.map((att) => ({\n Name: att.filename,\n Content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ContentType: att.contentType ?? \"application/octet-stream\",\n ...(att.contentId ? { ContentID: att.contentId } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.postmarkapp.com/email\", {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n MessageID?: string;\n Message?: string;\n ErrorCode?: number;\n };\n\n if (!response.ok) {\n throw new PostmarkError(payload.Message ?? \"Postmark API error\", response.status, payload);\n }\n\n return {\n messageId: payload.MessageID ?? options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: payload.Message ?? \"OK\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Postmark server token by fetching server info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.postmarkapp.com/server\", {\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n Accept: \"application/json\",\n },\n });\n\n const payload = (await response.json()) as { Name?: string; Message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"postmark\",\n message: payload.Message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"postmark\",\n ...(payload.Name ? { message: payload.Name } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"postmark\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "650BF60942E0759C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildMIME
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-x3szga4k.js";
|
|
4
4
|
import {
|
|
5
5
|
extractEmails,
|
|
6
6
|
resolveAttachments
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-f4c9ttmr.js";
|
|
8
8
|
import"../chunk-794hc3m4.js";
|
|
9
9
|
import {
|
|
10
10
|
__require
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
2
|
/** Resend API configuration. */
|
|
3
3
|
export interface ResendConfig {
|
|
4
|
+
/** Resend API key (starts with `re_`). */
|
|
4
5
|
apiKey: string;
|
|
6
|
+
/** API base URL. Default: `https://api.resend.com`. */
|
|
5
7
|
baseUrl?: string;
|
|
6
8
|
}
|
|
7
9
|
/** Error thrown when the Resend API returns a non-success response. */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/resend.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Resend HTTP API transport for sending email via api.resend.com.\n *\n * @example\n * ```ts\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"onboarding@yourdomain.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * html: \"<p>Sent via Resend</p>\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Resend API configuration. */\nexport interface ResendConfig {\n apiKey: string;\n baseUrl?: string;\n}\n\n/** Error thrown when the Resend API returns a non-success response. */\nexport class ResendError extends Error {\n /** Creates a Resend API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"ResendError\";\n }\n}\n\n/**\n * Resend HTTP API transport.\n */\nexport class ResendTransport implements Transport {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /** Creates a Resend transport with the given API key. */\n constructor(config: ResendConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? \"https://api.resend.com\";\n }\n\n /** Sends an email via the Resend HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const body = {\n from: from ? toMIMEHeader(from) : \"\",\n to: extractEmails(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: extractEmails(options.cc) } : {}),\n ...(options.bcc ? { bcc: extractEmails(options.bcc) } : {}),\n ...(options.replyTo ? { reply_to: extractEmails(options.replyTo) } : {}),\n ...(options.text ? { text: options.text } : {}),\n ...(options.html ? { html: options.html } : {}),\n ...(options.headers ? { headers: options.headers } : {}),\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n filename: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ...(att.contentType ? { content_type: att.contentType } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(`${this.baseUrl}/emails`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new ResendError(payload.message ?? \"Resend API error\", response.status, payload);\n }\n\n const accepted = extractEmails(options.to);\n return {\n messageId: payload.id ?? options.messageId ?? \"\",\n accepted,\n rejected: [],\n response: payload.message ?? \"Email sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Resend API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(`${this.baseUrl}/domains`, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"resend\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"resend\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"resend\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Resend HTTP API transport for sending email via api.resend.com.\n *\n * @example\n * ```ts\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"onboarding@yourdomain.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * html: \"<p>Sent via Resend</p>\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Resend API configuration. */\nexport interface ResendConfig {\n /** Resend API key (starts with `re_`). */\n apiKey: string;\n /** API base URL. Default: `https://api.resend.com`. */\n baseUrl?: string;\n}\n\n/** Error thrown when the Resend API returns a non-success response. */\nexport class ResendError extends Error {\n /** Creates a Resend API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"ResendError\";\n }\n}\n\n/**\n * Resend HTTP API transport.\n */\nexport class ResendTransport implements Transport {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n /** Creates a Resend transport with the given API key. */\n constructor(config: ResendConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? \"https://api.resend.com\";\n }\n\n /** Sends an email via the Resend HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const body = {\n from: from ? toMIMEHeader(from) : \"\",\n to: extractEmails(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: extractEmails(options.cc) } : {}),\n ...(options.bcc ? { bcc: extractEmails(options.bcc) } : {}),\n ...(options.replyTo ? { reply_to: extractEmails(options.replyTo) } : {}),\n ...(options.text ? { text: options.text } : {}),\n ...(options.html ? { html: options.html } : {}),\n ...(options.headers ? { headers: options.headers } : {}),\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n filename: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ...(att.contentType ? { content_type: att.contentType } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(`${this.baseUrl}/emails`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new ResendError(payload.message ?? \"Resend API error\", response.status, payload);\n }\n\n const accepted = extractEmails(options.to);\n return {\n messageId: payload.id ?? options.messageId ?? \"\",\n accepted,\n rejected: [],\n response: payload.message ?? \"Email sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Resend API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(`${this.baseUrl}/domains`, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"resend\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"resend\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"resend\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "C97D4F9F26828E0A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { Attachment } from "../core/types.js";
|
|
2
2
|
/** Options for {@link resolveAttachments}. */
|
|
3
3
|
export interface ResolveAttachmentsOptions {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
4
|
+
/**
|
|
5
|
+
* If set, attachment paths must resolve within this directory.
|
|
6
|
+
* Prevents path traversal via `..` segments and sibling-directory
|
|
7
|
+
* prefix matches. Opt-in only.
|
|
8
|
+
*
|
|
9
|
+
* Note: this check uses `node:path` `resolve()`, which does NOT
|
|
10
|
+
* dereference symlinks. A symlink located inside `basePath` that
|
|
11
|
+
* points outside of it will pass this check. If symlink traversal is
|
|
12
|
+
* a concern, resolve paths with `fs.realpath()` before passing them in.
|
|
13
|
+
*/
|
|
6
14
|
basePath?: string;
|
|
7
15
|
}
|
|
8
16
|
/**
|
package/dist/transports/retry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
2
|
/** SendGrid API configuration. */
|
|
3
3
|
export interface SendGridConfig {
|
|
4
|
+
/** SendGrid API key (Bearer token). */
|
|
4
5
|
apiKey: string;
|
|
5
6
|
}
|
|
6
7
|
/** Error thrown when the SendGrid API returns a non-success response. */
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/sendgrid.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * SendGrid v3 HTTP API transport for sending email via api.sendgrid.com.\n *\n * @example\n * ```ts\n * import { SendGridTransport } from \"sently/transports/sendgrid\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SendGridTransport({ apiKey: process.env.SENDGRID_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** SendGrid API configuration. */\nexport interface SendGridConfig {\n apiKey: string;\n}\n\n/** Error thrown when the SendGrid API returns a non-success response. */\nexport class SendGridError extends Error {\n /** Creates a SendGrid API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"SendGridError\";\n }\n}\n\n/**\n * SendGrid v3 HTTP API transport.\n */\nexport class SendGridTransport implements Transport {\n private readonly apiKey: string;\n\n /** Creates a SendGrid transport with the given API key. */\n constructor(config: SendGridConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the SendGrid v3 HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const personalization = {\n to: parseAddresses(options.to).map((addr) => ({ email: addr.address, name: addr.name })),\n ...(options.cc\n ? {\n cc: parseAddresses(options.cc).map((addr) => ({\n email: addr.address,\n name: addr.name,\n })),\n }\n : {}),\n ...(options.bcc\n ? {\n bcc: parseAddresses(options.bcc).map((addr) => ({\n email: addr.address,\n name: addr.name,\n })),\n }\n : {}),\n };\n\n const body = {\n personalizations: [personalization],\n from: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n subject: options.subject,\n ...(options.replyTo\n ? {\n reply_to: parseAddresses(options.replyTo).map((addr) => ({\n email: addr.address,\n name: addr.name,\n }))[0],\n }\n : {}),\n content: [\n ...(options.text ? [{ type: \"text/plain\", value: options.text }] : []),\n ...(options.html ? [{ type: \"text/html\", value: options.html }] : []),\n ],\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n filename: att.filename,\n type: att.contentType ?? \"application/octet-stream\",\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ...(att.contentId ? { content_id: att.contentId } : {}),\n disposition: att.inline ? \"inline\" : \"attachment\",\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.sendgrid.com/v3/mail/send\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const apiError = await response.text();\n throw new SendGridError(\"SendGrid API error\", response.status, apiError);\n }\n\n const messageId = response.headers.get(\"x-message-id\") ?? options.messageId ?? \"\";\n\n return {\n messageId,\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Accepted\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the SendGrid API key by fetching the user profile. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.sendgrid.com/v3/user/profile\", {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const apiError = await response.text().catch(() => \"\");\n return {\n ok: false,\n provider: \"sendgrid\",\n message: apiError || `HTTP ${response.status}`,\n };\n }\n\n const payload = (await response.json()) as { username?: string };\n return {\n ok: true,\n provider: \"sendgrid\",\n ...(payload.username ? { message: payload.username } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"sendgrid\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * SendGrid v3 HTTP API transport for sending email via api.sendgrid.com.\n *\n * @example\n * ```ts\n * import { SendGridTransport } from \"sently/transports/sendgrid\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SendGridTransport({ apiKey: process.env.SENDGRID_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** SendGrid API configuration. */\nexport interface SendGridConfig {\n /** SendGrid API key (Bearer token). */\n apiKey: string;\n}\n\n/** Error thrown when the SendGrid API returns a non-success response. */\nexport class SendGridError extends Error {\n /** Creates a SendGrid API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"SendGridError\";\n }\n}\n\n/**\n * SendGrid v3 HTTP API transport.\n */\nexport class SendGridTransport implements Transport {\n private readonly apiKey: string;\n\n /** Creates a SendGrid transport with the given API key. */\n constructor(config: SendGridConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the SendGrid v3 HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const personalization = {\n to: parseAddresses(options.to).map((addr) => ({ email: addr.address, name: addr.name })),\n ...(options.cc\n ? {\n cc: parseAddresses(options.cc).map((addr) => ({\n email: addr.address,\n name: addr.name,\n })),\n }\n : {}),\n ...(options.bcc\n ? {\n bcc: parseAddresses(options.bcc).map((addr) => ({\n email: addr.address,\n name: addr.name,\n })),\n }\n : {}),\n };\n\n const body = {\n personalizations: [personalization],\n from: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n subject: options.subject,\n ...(options.replyTo\n ? {\n reply_to: parseAddresses(options.replyTo).map((addr) => ({\n email: addr.address,\n name: addr.name,\n }))[0],\n }\n : {}),\n content: [\n ...(options.text ? [{ type: \"text/plain\", value: options.text }] : []),\n ...(options.html ? [{ type: \"text/html\", value: options.html }] : []),\n ],\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n filename: att.filename,\n type: att.contentType ?? \"application/octet-stream\",\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ...(att.contentId ? { content_id: att.contentId } : {}),\n disposition: att.inline ? \"inline\" : \"attachment\",\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.sendgrid.com/v3/mail/send\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const apiError = await response.text();\n throw new SendGridError(\"SendGrid API error\", response.status, apiError);\n }\n\n const messageId = response.headers.get(\"x-message-id\") ?? options.messageId ?? \"\";\n\n return {\n messageId,\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Accepted\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the SendGrid API key by fetching the user profile. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.sendgrid.com/v3/user/profile\", {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const apiError = await response.text().catch(() => \"\");\n return {\n ok: false,\n provider: \"sendgrid\",\n message: apiError || `HTTP ${response.status}`,\n };\n }\n\n const payload = (await response.json()) as { username?: string };\n return {\n ok: true,\n provider: \"sendgrid\",\n ...(payload.username ? { message: payload.username } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"sendgrid\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "53D65F527A73629564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/transports/ses.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildMIME
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-x3szga4k.js";
|
|
4
4
|
import {
|
|
5
5
|
extractEmails,
|
|
6
6
|
parseAddresses,
|
|
7
7
|
resolveAttachments,
|
|
8
8
|
toMIMEHeader
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-f4c9ttmr.js";
|
|
10
10
|
import {
|
|
11
11
|
encodeBase64
|
|
12
12
|
} from "../chunk-794hc3m4.js";
|
|
@@ -27,7 +27,7 @@ async function hmacSHA256(key, data) {
|
|
|
27
27
|
async function signRequest(request) {
|
|
28
28
|
const { method, url, body, credentials } = request;
|
|
29
29
|
const parsed = new URL(url);
|
|
30
|
-
const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, "").slice(0,
|
|
30
|
+
const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, "").slice(0, 15)}Z`;
|
|
31
31
|
const dateStamp = amzDate.slice(0, 8);
|
|
32
32
|
const headers = {
|
|
33
33
|
...Object.fromEntries(Object.entries(request.headers).map(([key, value]) => [key.toLowerCase(), value.trim()])),
|
|
@@ -250,4 +250,4 @@ export {
|
|
|
250
250
|
SESError
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
-
//# debugId=
|
|
253
|
+
//# debugId=E3DA50CEC668691D64756E2164756E21
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/core/sigv4.ts", "../src/transports/ses.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * AWS Signature Version 4 signing using Web Crypto (HMAC-SHA256).\n * Works on Node.js, Bun, Deno, and Cloudflare Workers.\n * No external dependencies.\n *\n * @example\n * ```ts\n * import { signRequest } from \"sently/core/sigv4\";\n * const signed = await signRequest({\n * method: \"POST\",\n * url: \"https://email.us-east-1.amazonaws.com/v2/email/outbound-emails\",\n * headers: { \"content-type\": \"application/json\" },\n * body: '{\"...\"}',\n * credentials: { accessKeyId, secretAccessKey, region: \"us-east-1\", service: \"ses\" },\n * });\n * ```\n */\n\n/** AWS credentials and signing scope for SigV4. */\nexport interface SigV4Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n region: string;\n service: string;\n sessionToken?: string;\n}\n\n/** HTTP request to sign with AWS Signature Version 4. */\nexport interface SigV4Request {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n credentials: SigV4Credentials;\n /** Override datetime for testing. Full 'YYYYMMDDTHHMMSSZ' when provided. */\n _date?: string;\n}\n\n/** Signed request headers including Authorization. */\nexport interface SigV4Result {\n /** All headers including Authorization, x-amz-date, and x-amz-security-token */\n headers: Record<string, string>;\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * Compute SHA-256 hash of a string using Web Crypto.\n * Returns lowercase hex string.\n * @internal\n */\nexport async function sha256Hex(data: string): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", encoder.encode(data));\n return bytesToHex(new Uint8Array(hash));\n}\n\n/**\n * Compute HMAC-SHA256 using Web Crypto.\n * @internal\n */\nexport async function hmacSHA256(key: Uint8Array | string, data: string): Promise<Uint8Array> {\n const keyBytes = typeof key === \"string\" ? encoder.encode(key) : new Uint8Array(key);\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyBytes,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, encoder.encode(data));\n return new Uint8Array(signature);\n}\n\n/**\n * Sign an HTTP request with AWS Signature Version 4.\n * Returns the complete set of headers to include in the request.\n */\nexport async function signRequest(request: SigV4Request): Promise<SigV4Result> {\n const { method, url, body, credentials } = request;\n const parsed = new URL(url);\n const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, \"\").slice(0,
|
|
5
|
+
"/**\n * @module\n * AWS Signature Version 4 signing using Web Crypto (HMAC-SHA256).\n * Works on Node.js, Bun, Deno, and Cloudflare Workers.\n * No external dependencies.\n *\n * @example\n * ```ts\n * import { signRequest } from \"sently/core/sigv4\";\n * const signed = await signRequest({\n * method: \"POST\",\n * url: \"https://email.us-east-1.amazonaws.com/v2/email/outbound-emails\",\n * headers: { \"content-type\": \"application/json\" },\n * body: '{\"...\"}',\n * credentials: { accessKeyId, secretAccessKey, region: \"us-east-1\", service: \"ses\" },\n * });\n * ```\n */\n\n/** AWS credentials and signing scope for SigV4. */\nexport interface SigV4Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n region: string;\n service: string;\n sessionToken?: string;\n}\n\n/** HTTP request to sign with AWS Signature Version 4. */\nexport interface SigV4Request {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n credentials: SigV4Credentials;\n /** Override datetime for testing. Full 'YYYYMMDDTHHMMSSZ' when provided. */\n _date?: string;\n}\n\n/** Signed request headers including Authorization. */\nexport interface SigV4Result {\n /** All headers including Authorization, x-amz-date, and x-amz-security-token */\n headers: Record<string, string>;\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * Compute SHA-256 hash of a string using Web Crypto.\n * Returns lowercase hex string.\n * @internal\n */\nexport async function sha256Hex(data: string): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", encoder.encode(data));\n return bytesToHex(new Uint8Array(hash));\n}\n\n/**\n * Compute HMAC-SHA256 using Web Crypto.\n * @internal\n */\nexport async function hmacSHA256(key: Uint8Array | string, data: string): Promise<Uint8Array> {\n const keyBytes = typeof key === \"string\" ? encoder.encode(key) : new Uint8Array(key);\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyBytes,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, encoder.encode(data));\n return new Uint8Array(signature);\n}\n\n/**\n * Sign an HTTP request with AWS Signature Version 4.\n * Returns the complete set of headers to include in the request.\n */\nexport async function signRequest(request: SigV4Request): Promise<SigV4Result> {\n const { method, url, body, credentials } = request;\n const parsed = new URL(url);\n const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, \"\").slice(0, 15)}Z`;\n const dateStamp = amzDate.slice(0, 8);\n\n const headers: Record<string, string> = {\n ...Object.fromEntries(\n Object.entries(request.headers).map(([key, value]) => [key.toLowerCase(), value.trim()]),\n ),\n host: parsed.host,\n \"x-amz-date\": amzDate,\n };\n\n if (credentials.sessionToken) {\n headers[\"x-amz-security-token\"] = credentials.sessionToken;\n }\n\n const signedHeaderNames = Object.keys(headers)\n .map((name) => name.toLowerCase())\n .sort();\n const signedHeaders = signedHeaderNames.join(\";\");\n const canonicalHeaders = `${signedHeaderNames.map((name) => `${name}:${headers[name]}`).join(\"\\n\")}\\n`;\n const canonicalQuery = normalizeQuery(parsed.searchParams);\n const payloadHash = await sha256Hex(body);\n const canonicalRequest = [\n method.toUpperCase(),\n canonicalUri(parsed.pathname),\n canonicalQuery,\n canonicalHeaders,\n signedHeaders,\n payloadHash,\n ].join(\"\\n\");\n\n const credentialScope = `${dateStamp}/${credentials.region}/${credentials.service}/aws4_request`;\n const stringToSign = [\n \"AWS4-HMAC-SHA256\",\n amzDate,\n credentialScope,\n await sha256Hex(canonicalRequest),\n ].join(\"\\n\");\n\n const signingKey = await deriveSigningKey(\n credentials.secretAccessKey,\n dateStamp,\n credentials.region,\n credentials.service,\n );\n const signature = bytesToHex(await hmacSHA256(signingKey, stringToSign));\n const authorization = [\n `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${credentialScope}`,\n `SignedHeaders=${signedHeaders}`,\n `Signature=${signature}`,\n ].join(\", \");\n\n return {\n headers: {\n ...headers,\n Authorization: authorization,\n },\n };\n}\n\nasync function deriveSigningKey(\n secretAccessKey: string,\n dateStamp: string,\n region: string,\n service: string,\n): Promise<Uint8Array> {\n const kDate = await hmacSHA256(`AWS4${secretAccessKey}`, dateStamp);\n const kRegion = await hmacSHA256(kDate, region);\n const kService = await hmacSHA256(kRegion, service);\n return hmacSHA256(kService, \"aws4_request\");\n}\n\nfunction canonicalUri(pathname: string): string {\n if (!pathname || pathname === \"/\") {\n return \"/\";\n }\n return pathname\n .split(\"/\")\n .map((segment) => encodeURIComponent(decodeURIComponent(segment)))\n .join(\"/\");\n}\n\nfunction normalizeQuery(searchParams: URLSearchParams): string {\n const pairs: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n pairs.push(`${encodeRfc3986(key)}=${encodeRfc3986(value)}`);\n }\n pairs.sort();\n return pairs.join(\"&\");\n}\n\nfunction encodeRfc3986(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n",
|
|
6
6
|
"/**\n * @module\n * AWS SES v2 HTTP transport for sently.\n * Signs requests with AWS Signature Version 4 using Web Crypto.\n * Works on Node.js, Bun, Deno, and Cloudflare Workers.\n *\n * @example\n * ```ts\n * import { SESTransport } from \"sently/transports/ses\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SESTransport({\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * region: \"us-east-1\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { buildMIME } from \"../core/mime.js\";\nimport { signRequest } from \"../core/sigv4.js\";\nimport type { MailOptions, SESConfig, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the AWS SES API returns a non-success response. */\nexport class SESError extends Error {\n /** Creates an AWS SES API error with status code, error code, and request ID. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly code: string,\n public readonly requestId: string,\n ) {\n super(message);\n this.name = \"SESError\";\n }\n}\n\n/**\n * AWS SES v2 HTTP API transport.\n */\nexport class SESTransport implements Transport {\n private readonly accessKeyId: string;\n private readonly secretAccessKey: string;\n private readonly region: string;\n private readonly sessionToken: string | undefined;\n private readonly dkim: SESConfig[\"dkim\"];\n\n /** Creates an SES transport with AWS credentials. */\n constructor(config: SESConfig) {\n this.accessKeyId = config.accessKeyId;\n this.secretAccessKey = config.secretAccessKey;\n this.region = config.region ?? \"us-east-1\";\n this.sessionToken = config.sessionToken;\n this.dkim = config.dkim;\n }\n\n /** Sends an email via the AWS SES v2 HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const resolvedOptions = { ...options, attachments };\n const from = parseAddresses(options.from)[0];\n const fromEmail = from ? toMIMEHeader(from) : \"\";\n const toEmails = extractEmails(options.to);\n const ccEmails = options.cc ? extractEmails(options.cc) : [];\n const bccEmails = options.bcc ? extractEmails(options.bcc) : [];\n\n const destination = {\n ToAddresses: toEmails,\n CcAddresses: ccEmails,\n BccAddresses: bccEmails,\n };\n\n let requestBody: Record<string, unknown>;\n\n if (attachments.length > 0) {\n const mime = await buildMIME(resolvedOptions, this.dkim);\n requestBody = {\n FromEmailAddress: fromEmail,\n Destination: destination,\n Content: {\n Raw: {\n Data: encodeBase64(mime.raw).replace(/\\r\\n/g, \"\"),\n },\n },\n };\n } else {\n requestBody = {\n FromEmailAddress: fromEmail,\n Destination: destination,\n Content: {\n Simple: {\n Subject: { Data: options.subject, Charset: \"UTF-8\" },\n Body: {\n ...(options.text ? { Text: { Data: options.text, Charset: \"UTF-8\" } } : {}),\n ...(options.html ? { Html: { Data: options.html, Charset: \"UTF-8\" } } : {}),\n },\n },\n },\n };\n }\n\n const body = JSON.stringify(requestBody);\n const url = `https://email.${this.region}.amazonaws.com/v2/email/outbound-emails`;\n const signed = await signRequest({\n method: \"POST\",\n url,\n headers: {\n \"content-type\": \"application/json\",\n },\n body,\n credentials: {\n accessKeyId: this.accessKeyId,\n secretAccessKey: this.secretAccessKey,\n region: this.region,\n service: \"ses\",\n ...(this.sessionToken ? { sessionToken: this.sessionToken } : {}),\n },\n });\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: signed.headers,\n body,\n });\n\n const payload = (await response.json()) as {\n MessageId?: string;\n message?: string;\n Code?: string;\n };\n\n if (!response.ok) {\n throw new SESError(\n payload.message ?? \"SES API error\",\n response.status,\n payload.Code ?? \"\",\n response.headers.get(\"x-amzn-requestid\") ?? \"\",\n );\n }\n\n const messageId = payload.MessageId ?? \"\";\n return {\n messageId,\n accepted: [...toEmails, ...ccEmails, ...bccEmails],\n rejected: [],\n response: `MessageId: ${messageId}`,\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies AWS credentials by listing SES configuration sets. */\n async verify(): Promise<VerifyResult> {\n try {\n const url = `https://email.${this.region}.amazonaws.com/v2/email/configuration-sets`;\n const signed = await signRequest({\n method: \"GET\",\n url,\n headers: {},\n body: \"\",\n credentials: {\n accessKeyId: this.accessKeyId,\n secretAccessKey: this.secretAccessKey,\n region: this.region,\n service: \"ses\",\n ...(this.sessionToken ? { sessionToken: this.sessionToken } : {}),\n },\n });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: signed.headers,\n });\n\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"ses\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"ses\", message: \"Credentials are valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"ses\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
7
7
|
],
|
|
8
8
|
"mappings": "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",
|
|
9
|
-
"debugId": "
|
|
9
|
+
"debugId": "E3DA50CEC668691D64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -20,16 +20,29 @@ export declare class SMTPTransport implements Transport {
|
|
|
20
20
|
}
|
|
21
21
|
/** Resolved SMTP transport configuration with defaults applied. */
|
|
22
22
|
export interface ResolvedSMTPConfig {
|
|
23
|
+
/** SMTP server hostname. */
|
|
23
24
|
host: string;
|
|
25
|
+
/** SMTP port with secure/default applied. */
|
|
24
26
|
port: number;
|
|
27
|
+
/** Whether implicit TLS is used. */
|
|
25
28
|
secure: boolean;
|
|
29
|
+
/** Resolved authentication credentials, if any. */
|
|
26
30
|
auth?: SMTPConfig["auth"];
|
|
31
|
+
/** Whether AUTH requires an encrypted connection. */
|
|
32
|
+
requireTLS?: boolean;
|
|
33
|
+
/** TLS options for STARTTLS and direct TLS. */
|
|
27
34
|
tls?: SMTPConfig["tls"];
|
|
35
|
+
/** DKIM signing configuration. */
|
|
28
36
|
dkim?: SMTPConfig["dkim"];
|
|
37
|
+
/** Socket connect timeout in milliseconds. */
|
|
29
38
|
connectionTimeout?: number;
|
|
39
|
+
/** Timeout waiting for the SMTP greeting in milliseconds. */
|
|
30
40
|
greetingTimeout?: number;
|
|
41
|
+
/** Idle socket timeout in milliseconds. */
|
|
31
42
|
socketTimeout?: number;
|
|
43
|
+
/** Direct-to-MX delivery mode. */
|
|
32
44
|
direct?: boolean;
|
|
45
|
+
/** Runtime socket adapter. */
|
|
33
46
|
adapter?: SocketAdapter;
|
|
34
47
|
}
|
|
35
48
|
/** Apply defaults to SMTP configuration. */
|
package/dist/transports/smtp.js
CHANGED
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
openSMTPSession,
|
|
6
6
|
readSMTPResponse,
|
|
7
7
|
resolveSMTPConfig
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-7fqv71z1.js";
|
|
9
9
|
import"../chunk-ym3zzv8b.js";
|
|
10
|
-
import"../chunk-
|
|
10
|
+
import"../chunk-x3szga4k.js";
|
|
11
11
|
import {
|
|
12
12
|
encodeLine
|
|
13
|
-
} from "../chunk-
|
|
14
|
-
import"../chunk-
|
|
13
|
+
} from "../chunk-tymfm441.js";
|
|
14
|
+
import"../chunk-f4c9ttmr.js";
|
|
15
15
|
import"../chunk-794hc3m4.js";
|
|
16
16
|
import"../chunk-v0bahtg2.js";
|
|
17
17
|
export {
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/transports/smtp.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * SMTP transport — orchestrates socket adapter, MIME builder, and protocol logic.\n *\n * @example\n * ```ts\n * import { SMTPTransport } from \"sently/transports/smtp\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SMTPTransport({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * }),\n * });\n * ```\n */\nimport { OAuth2Client } from \"../auth/oauth2.js\";\nimport { buildMIME, type MIMEBuildResult } from \"../core/mime.js\";\nimport type { SMTPResponse } from \"../core/smtp.js\";\nimport {\n accumulateResponse,\n assertResponse,\n computeCRAMMD5,\n encodeAuthLoginPass,\n encodeAuthLoginUser,\n encodeCommand,\n encodeLine,\n parseEHLO,\n parseResponse,\n SMTPError,\n selectAuthMethod,\n} from \"../core/smtp.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/**\n * SMTP transport orchestrating adapter, MIME builder, and protocol logic.\n */\nexport class SMTPTransport implements Transport {\n private readonly config: ResolvedSMTPConfig;\n private adapter: SocketAdapter | null = null;\n\n /** Creates an SMTP transport with the given configuration. */\n constructor(config: SMTPConfig) {\n this.config = resolveSMTPConfig(config);\n }\n\n /** Sends an email via SMTP using the configured adapter. */\n async send(options: MailOptions): Promise<SendResult> {\n const resolvedOptions = {\n ...options,\n attachments: await resolveAttachments(options.attachments),\n };\n const mime = await buildMIME(resolvedOptions, this.config.dkim);\n const adapter = await this.getAdapter();\n\n const host = this.config.direct\n ? await resolveMX(mime.envelope.from.split(\"@\")[1] ?? this.config.host)\n : this.config.host;\n\n await adapter.connect(host, this.config.port);\n this.adapter = adapter;\n\n try {\n await openSMTPSession(adapter, this.config);\n return await deliverSMTPMessage(adapter, mime);\n } finally {\n await closeSMTPSession(adapter);\n this.adapter = null;\n }\n }\n\n /** Verifies SMTP connectivity and authentication without sending mail. */\n async verify(): Promise<VerifyResult> {\n try {\n const adapter = await this.getAdapter();\n await adapter.connect(this.config.host, this.config.port);\n\n try {\n await openSMTPSession(adapter, this.config);\n return { ok: true, provider: \"smtp\" };\n } finally {\n await closeSMTPSession(adapter);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Closes the underlying socket adapter if connected. */\n async close(): Promise<void> {\n if (this.adapter) {\n await this.adapter.close();\n this.adapter = null;\n }\n }\n\n private async getAdapter(): Promise<SocketAdapter> {\n if (!this.config.adapter) {\n throw new SMTPError(\"No socket adapter configured\", 0, \"CONNECT\", \"\");\n }\n return this.config.adapter;\n }\n}\n\n/** Resolved SMTP transport configuration with defaults applied. */\nexport interface ResolvedSMTPConfig {\n host: string;\n port: number;\n secure: boolean;\n auth?: SMTPConfig[\"auth\"];\n tls?: SMTPConfig[\"tls\"];\n dkim?: SMTPConfig[\"dkim\"];\n connectionTimeout?: number;\n greetingTimeout?: number;\n socketTimeout?: number;\n direct?: boolean;\n adapter?: SocketAdapter;\n}\n\n/** Apply defaults to SMTP configuration. */\nexport function resolveSMTPConfig(config: SMTPConfig): ResolvedSMTPConfig {\n const secure = config.secure ?? false;\n return {\n host: config.host,\n port: config.port ?? (secure ? 465 : 587),\n secure,\n ...(config.auth !== undefined ? { auth: config.auth } : {}),\n ...(config.dkim !== undefined ? { dkim: config.dkim } : {}),\n ...(config.tls !== undefined ? { tls: config.tls } : {}),\n ...(config.connectionTimeout !== undefined\n ? { connectionTimeout: config.connectionTimeout }\n : {}),\n ...(config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {}),\n ...(config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {}),\n ...(config.direct !== undefined ? { direct: config.direct } : {}),\n ...(config.adapter !== undefined ? { adapter: config.adapter } : {}),\n };\n}\n\n/**\n * Connect greeting, EHLO, optional STARTTLS, and AUTH on an open adapter.\n */\nexport async function openSMTPSession(\n adapter: SocketAdapter,\n config: ResolvedSMTPConfig,\n): Promise<void> {\n const greeting = await readSMTPResponse(adapter);\n assertResponse(greeting, [220], \"greeting\");\n\n let capabilities = await ehlo(adapter, config.host);\n const supportsStartTls = capabilities.some((cap) => cap.toUpperCase() === \"STARTTLS\");\n if (!config.secure && !adapter.secure && supportsStartTls) {\n await sendRaw(adapter, encodeCommand({ type: \"STARTTLS\" }));\n const starttlsResp = await readSMTPResponse(adapter);\n assertResponse(starttlsResp, [220], \"STARTTLS\");\n await adapter.startTLS(config.tls);\n capabilities = await ehlo(adapter, config.host);\n }\n\n if (config.auth) {\n await authenticate(adapter, config.auth, capabilities);\n }\n}\n\n/**\n * MAIL FROM, RCPT TO, and DATA for a built MIME message on an authenticated session.\n */\nexport async function deliverSMTPMessage(\n adapter: SocketAdapter,\n mime: MIMEBuildResult,\n): Promise<SendResult> {\n await sendCommand(adapter, { type: \"MAIL_FROM\", address: mime.envelope.from });\n const mailResp = await readSMTPResponse(adapter);\n assertResponse(mailResp, [250], \"MAIL FROM\");\n\n const accepted: string[] = [];\n const rejected: string[] = [];\n\n for (const recipient of mime.envelope.to) {\n await sendRaw(adapter, encodeCommand({ type: \"RCPT_TO\", address: recipient }));\n const rcptResp = await readSMTPResponse(adapter);\n if (rcptResp.isSuccess) {\n accepted.push(recipient);\n } else {\n rejected.push(recipient);\n }\n }\n\n await sendCommand(adapter, { type: \"DATA\" });\n const dataResp = await readSMTPResponse(adapter);\n assertResponse(dataResp, [354], \"DATA\");\n\n let finalResp: SMTPResponse;\n try {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n } catch (err) {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n if (finalResp.isError) {\n throw err;\n }\n }\n assertResponse(finalResp, [250], \"DATA end\");\n\n return {\n messageId: mime.messageId,\n accepted,\n rejected,\n response: finalResp.message,\n envelope: mime.envelope,\n };\n}\n\n/**\n * QUIT and close an SMTP session adapter.\n */\nexport async function closeSMTPSession(adapter: SocketAdapter): Promise<void> {\n try {\n await sendCommand(adapter, { type: \"QUIT\" });\n await readSMTPResponse(adapter);\n } catch {\n // ignore errors during shutdown\n } finally {\n await adapter.close();\n }\n}\n\nasync function ehlo(adapter: SocketAdapter, host: string): Promise<string[]> {\n await sendCommand(adapter, { type: \"EHLO\", domain: host });\n const response = await readSMTPResponse(adapter);\n assertResponse(response, [250], \"EHLO\");\n return parseEHLO(response);\n}\n\nasync function authenticate(\n adapter: SocketAdapter,\n auth: NonNullable<SMTPConfig[\"auth\"]>,\n capabilities: string[],\n): Promise<void> {\n if (auth.type === \"OAUTH2\" && auth.oauth2) {\n const client = new OAuth2Client(auth.oauth2);\n const xoauth2 = await client.buildXOAUTH2();\n await sendCommand(adapter, { type: \"AUTH_XOAUTH2\", xoauth2String: xoauth2 });\n let resp = await readSMTPResponse(adapter);\n if (resp.code === 334) {\n await sendRaw(adapter, encodeLine(\"\"));\n resp = await readSMTPResponse(adapter);\n }\n assertResponse(resp, [235], \"AUTH XOAUTH2\");\n return;\n }\n\n const method = auth.type ?? selectAuthMethod(capabilities);\n\n if (method === \"CRAM-MD5\") {\n const pass = requirePassword(auth, \"CRAM-MD5\");\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_INIT\" });\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH CRAM-MD5\");\n const challenge = resp.message.trim();\n const response = await computeCRAMMD5(challenge, auth.user, pass);\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_RESPONSE\", response });\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH CRAM-MD5 response\");\n return;\n }\n\n if (method === \"PLAIN\") {\n const pass = requirePassword(auth, \"PLAIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_PLAIN\", user: auth.user, pass }));\n const resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH PLAIN\");\n return;\n }\n\n const pass = requirePassword(auth, \"LOGIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_LOGIN\", user: auth.user, pass }));\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN\");\n\n await sendRaw(adapter, encodeAuthLoginUser(auth.user));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN user\");\n\n await sendRaw(adapter, encodeAuthLoginPass(pass));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH LOGIN pass\");\n}\n\nfunction requirePassword(auth: NonNullable<SMTPConfig[\"auth\"]>, method: string): string {\n if (!auth.pass) {\n throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, \"\");\n }\n return auth.pass;\n}\n\nasync function sendCommand(\n adapter: SocketAdapter,\n command: Parameters<typeof encodeCommand>[0],\n): Promise<void> {\n await sendRaw(adapter, encodeCommand(command));\n}\n\nasync function sendRaw(adapter: SocketAdapter, data: Uint8Array): Promise<void> {\n await adapter.write(data);\n}\n\n/** Reads and parses a complete SMTP response from the adapter. */\nasync function readSMTPResponse(adapter: SocketAdapter): Promise<SMTPResponse> {\n const chunks: Uint8Array[] = [];\n for await (const chunk of adapter.read()) {\n chunks.push(chunk);\n const complete = accumulateResponse(chunks);\n if (complete) {\n return parseResponse(complete);\n }\n }\n throw new SMTPError(\"Connection closed while reading SMTP response\", 0, \"READ\", \"\");\n}\n\nasync function resolveMX(domain: string): Promise<string> {\n const dns = await import(\"node:dns/promises\");\n const records = await dns.resolveMx(domain);\n if (records.length === 0) {\n throw new SMTPError(`No MX records for ${domain}`, 0, \"MX\", \"\");\n }\n records.sort((a: { priority: number }, b: { priority: number }) => a.priority - b.priority);\n return records[0]?.exchange ?? domain;\n}\n\n/** @internal Test helper for raw line writes. */\nexport { encodeLine, readSMTPResponse };\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "44FB4148CD9604AA64756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/core/address.ts", "../src/transports/resolve-attachments.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"// src/core/address.ts\nimport { encodeHeader } from \"./base64.js\";\nimport type { Address, AddressInput } from \"./types.js\";\n\n/**\n * Normalize any AddressInput form into Address[].\n */\nexport function parseAddresses(input: AddressInput): Address[] {\n if (Array.isArray(input)) {\n return input.flatMap((item) => parseAddresses(item));\n }\n\n if (typeof input === \"object\") {\n return [{ ...input }];\n }\n\n const trimmed = input.trim();\n if (!trimmed) {\n return [];\n }\n\n return splitAddressList(trimmed).map(parseSingleAddress);\n}\n\n/**\n * Format an Address for SMTP envelope commands (MAIL FROM / RCPT TO).\n */\nexport function toEnvelope(address: Address): string {\n return address.address;\n}\n\n/**\n * Format an Address for use in a MIME header (From, To, CC, etc.).\n */\nexport function toMIMEHeader(address: Address): string {\n if (address.name) {\n const name = encodeHeader(address.name);\n return `${name} <${address.address}>`;\n }\n return address.address;\n}\n\n/**\n * Extract plain email strings from any AddressInput.\n */\nexport function extractEmails(input: AddressInput): string[] {\n return parseAddresses(input).map((addr) => addr.address);\n}\n\n/**\n * Basic email format validation (format only, no DNS lookup).\n */\nexport function isValidEmail(email: string): boolean {\n if (/[\\r\\n\\t]/.test(email)) return false;\n return /^[^\\s@<>]+@[^\\s@<>]+\\.[^\\s@<>]+$/.test(email);\n}\n\nfunction splitAddressList(input: string): string[] {\n const parts: string[] = [];\n let current = \"\";\n let inQuotes = false;\n let inAngle = false;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i] ?? \"\";\n if (char === '\"' && input[i - 1] !== \"\\\\\") {\n inQuotes = !inQuotes;\n current += char;\n continue;\n }\n if (char === \"<\" && !inQuotes) {\n inAngle = true;\n current += char;\n continue;\n }\n if (char === \">\" && !inQuotes) {\n inAngle = false;\n current += char;\n continue;\n }\n if (char === \",\" && !inQuotes && !inAngle) {\n if (current.trim()) {\n parts.push(current.trim());\n }\n current = \"\";\n continue;\n }\n current += char;\n }\n\n if (current.trim()) {\n parts.push(current.trim());\n }\n\n return parts;\n}\n\nfunction parseSingleAddress(input: string): Address {\n const trimmed = input.trim();\n\n const angleMatch = trimmed.match(/^(?:\"([^\"]*)\"|([^<]*?))\\s*<([^>]+)>$/);\n if (angleMatch) {\n const name = (angleMatch[1] ?? angleMatch[2] ?? \"\").trim();\n const address = (angleMatch[3] ?? \"\").trim();\n if (name) {\n return { name, address };\n }\n return { address };\n }\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n return { address: trimmed.slice(1, -1) };\n }\n\n return { address: trimmed };\n}\n",
|
|
6
|
-
"import type { Attachment } from \"../core/types.js\";\n\n/** Options for {@link resolveAttachments}. */\nexport interface ResolveAttachmentsOptions {\n /** If set, attachment paths must resolve within this directory.\n * Prevents symlink escape and path traversal. Opt-in only. */\n basePath?: string;\n}\n\n/**\n * Resolve attachment.path to in-memory Uint8Array content.\n * @throws When attachment.path is used on runtimes without node:fs/promises\n */\nexport async function resolveAttachments(\n attachments: Attachment[] | undefined,\n options?: ResolveAttachmentsOptions,\n): Promise<Attachment[]> {\n const list = attachments ?? [];\n const resolved: Attachment[] = [];\n\n for (const attachment of list) {\n if (attachment.content instanceof Uint8Array) {\n resolved.push(attachment);\n continue;\n }\n\n if (attachment.path) {\n let fs: typeof import(\"node:fs/promises\");\n try {\n fs = await import(\"node:fs/promises\");\n } catch {\n throw new Error(\n \"attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead\",\n );\n }\n\n if (options?.basePath) {\n const { resolve } = await import(\"node:path\");\n const resolvedPath = resolve(attachment.path);\n const resolvedBase = resolve(options.basePath);\n if (!resolvedPath.startsWith(resolvedBase)) {\n throw new Error(\n `[sently] Attachment path \"${resolvedPath}\" escapes basePath \"${options.basePath}\". ` +\n \"Use absolute paths within the allowed directory.\",\n );\n }\n }\n\n const data = await fs.readFile(attachment.path);\n const { path: _path, ...rest } = attachment;\n resolved.push({ ...rest, content: new Uint8Array(data) });\n continue;\n }\n\n if (typeof attachment.content === \"string\") {\n resolved.push(attachment);\n continue;\n }\n\n resolved.push(attachment);\n }\n\n return resolved;\n}\n"
|
|
7
|
-
],
|
|
8
|
-
"mappings": "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",
|
|
9
|
-
"debugId": "7C59D40B68EA994E64756E2164756E21",
|
|
10
|
-
"names": []
|
|
11
|
-
}
|