sently 0.4.6 → 0.4.7
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 +35 -0
- package/README.md +94 -41
- package/dist/adapters/bun.js +2 -183
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.js +2 -77
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.js +2 -72
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.js +2 -180
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.js +2 -13
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/chunk-2kcwa9gt.js +4 -0
- package/dist/chunk-2kcwa9gt.js.map +11 -0
- package/dist/chunk-2t6hjer3.js +5 -0
- package/dist/chunk-2t6hjer3.js.map +10 -0
- package/dist/chunk-6yggz45h.js +5 -0
- package/dist/{chunk-794hc3m4.js.map → chunk-6yggz45h.js.map} +2 -2
- package/dist/chunk-dgkh77yp.js +4 -0
- package/dist/{chunk-ym3zzv8b.js.map → chunk-dgkh77yp.js.map} +2 -2
- package/dist/chunk-jfs80vhp.js +3 -0
- package/dist/{chunk-7fqv71z1.js.map → chunk-jfs80vhp.js.map} +2 -2
- package/dist/chunk-sqn04kae.js +4 -0
- package/dist/{chunk-v0bahtg2.js.map → chunk-sqn04kae.js.map} +1 -1
- package/dist/chunk-va2awz12.js +4 -0
- package/dist/{chunk-f4c9ttmr.js.map → chunk-va2awz12.js.map} +2 -2
- package/dist/chunk-wgtbr6ge.js +13 -0
- package/dist/{chunk-tymfm441.js.map → chunk-wgtbr6ge.js.map} +2 -2
- package/dist/core/smtp.js +2 -31
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.js +2 -180
- package/dist/detect.js.map +4 -5
- package/dist/dkim.d.ts +21 -0
- package/dist/dkim.js +9 -0
- package/dist/dkim.js.map +10 -0
- package/dist/index.d.ts +74 -16
- package/dist/index.js +85 -14
- package/dist/mailer.d.ts +16 -0
- package/dist/mailer.js +3 -0
- package/dist/mailer.js.map +9 -0
- package/dist/plugins/template.js +2 -28
- package/dist/plugins/template.js.map +2 -2
- package/dist/pool/pool.js +2 -16
- package/dist/pool/pool.js.map +5 -3
- package/dist/transports/brevo.js +2 -115
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.js +2 -119
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.js +2 -113
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -72
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.js +2 -109
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.js +2 -78
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.js +2 -132
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +5 -251
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -26
- package/dist/transports/smtp.js.map +1 -1
- package/package.json +17 -6
- package/dist/chunk-794hc3m4.js +0 -105
- package/dist/chunk-7fqv71z1.js +0 -251
- package/dist/chunk-f4c9ttmr.js +0 -154
- package/dist/chunk-mp5c9bfd.js +0 -270
- package/dist/chunk-mp5c9bfd.js.map +0 -11
- package/dist/chunk-tymfm441.js +0 -405
- package/dist/chunk-v0bahtg2.js +0 -6
- package/dist/chunk-x3szga4k.js +0 -367
- package/dist/chunk-x3szga4k.js.map +0 -11
- package/dist/chunk-ym3zzv8b.js +0 -74
|
@@ -5,7 +5,7 @@
|
|
|
5
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 /** AWS access key ID. */\n accessKeyId: string;\n /** AWS secret access key. */\n secretAccessKey: string;\n /** AWS region (e.g. `us-east-1`). */\n region: string;\n /** AWS service name (e.g. `ses`, `s3`). */\n service: string;\n /** Optional STS session token for temporary credentials. */\n sessionToken?: string;\n}\n\n/** HTTP request to sign with AWS Signature Version 4. */\nexport interface SigV4Request {\n /** HTTP method (e.g. `POST`). */\n method: string;\n /** Full request URL including path and query. */\n url: string;\n /** Request headers to include in the signature. */\n headers: Record<string, string>;\n /** Request body as a string (empty for GET). */\n body: string;\n /** AWS credentials and signing scope. */\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 /** AWS access key ID for SigV4 signing. */\n private readonly accessKeyId: string;\n /** AWS secret access key for SigV4 signing. */\n private readonly secretAccessKey: string;\n /** AWS region for the SES endpoint. */\n private readonly region: string;\n /** Optional STS session token for temporary credentials. */\n private readonly sessionToken: string | undefined;\n /** Optional DKIM config for raw MIME attachment sends. */\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
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "0FA0C89C1B86049064756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/transports/smtp.js
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SMTPTransport,
|
|
3
|
-
closeSMTPSession,
|
|
4
|
-
deliverSMTPMessage,
|
|
5
|
-
openSMTPSession,
|
|
6
|
-
readSMTPResponse,
|
|
7
|
-
resolveSMTPConfig
|
|
8
|
-
} from "../chunk-7fqv71z1.js";
|
|
9
|
-
import"../chunk-ym3zzv8b.js";
|
|
10
|
-
import"../chunk-x3szga4k.js";
|
|
11
|
-
import {
|
|
12
|
-
encodeLine
|
|
13
|
-
} from "../chunk-tymfm441.js";
|
|
14
|
-
import"../chunk-f4c9ttmr.js";
|
|
15
|
-
import"../chunk-794hc3m4.js";
|
|
16
|
-
import"../chunk-v0bahtg2.js";
|
|
17
|
-
export {
|
|
18
|
-
resolveSMTPConfig,
|
|
19
|
-
readSMTPResponse,
|
|
20
|
-
openSMTPSession,
|
|
21
|
-
encodeLine,
|
|
22
|
-
deliverSMTPMessage,
|
|
23
|
-
closeSMTPSession,
|
|
24
|
-
SMTPTransport
|
|
25
|
-
};
|
|
1
|
+
import{a as b,b as c,c as d,d as e,e as f,f as g}from"../chunk-jfs80vhp.js";import"../chunk-dgkh77yp.js";import"../chunk-2t6hjer3.js";import"../chunk-va2awz12.js";import{D as a}from"../chunk-wgtbr6ge.js";import"../chunk-6yggz45h.js";import"../chunk-sqn04kae.js";export{c as resolveSMTPConfig,g as readSMTPResponse,d as openSMTPSession,a as encodeLine,e as deliverSMTPMessage,f as closeSMTPSession,b as SMTPTransport};
|
|
26
2
|
|
|
27
|
-
//# debugId=
|
|
3
|
+
//# debugId=85730DA37F372D4764756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sently",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Runtime-agnostic email library for Node.js, Bun, Deno, and Cloudflare Workers. ESM-native Nodemailer alternative with zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,13 +33,16 @@
|
|
|
33
33
|
"publish:jsr": "npx jsr publish",
|
|
34
34
|
"publish:all-d": "bun run publish:npm-d && bun run publish:jsr-d",
|
|
35
35
|
"publish:all": "bun run publish:npm && bun run publish:jsr",
|
|
36
|
-
"verify:publish": "bun run pack:npm"
|
|
36
|
+
"verify:publish": "bun run pack:npm",
|
|
37
|
+
"measure:size": "bun tools/measure-bundle-size.ts",
|
|
38
|
+
"measure:size:md": "bun tools/measure-bundle-size.ts --markdown",
|
|
39
|
+
"check:size": "bun tools/measure-bundle-size.ts --check"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
|
-
"@biomejs/biome": "
|
|
40
|
-
"@modelcontextprotocol/sdk": "
|
|
41
|
-
"@types/node": "
|
|
42
|
-
"typescript": "
|
|
42
|
+
"@biomejs/biome": "2.4.16",
|
|
43
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
44
|
+
"@types/node": "25.9.1",
|
|
45
|
+
"typescript": "6.0.3"
|
|
43
46
|
},
|
|
44
47
|
"keywords": [
|
|
45
48
|
"email",
|
|
@@ -146,6 +149,14 @@
|
|
|
146
149
|
"./pool": {
|
|
147
150
|
"import": "./dist/pool/pool.js",
|
|
148
151
|
"types": "./dist/pool/pool.d.ts"
|
|
152
|
+
},
|
|
153
|
+
"./mailer": {
|
|
154
|
+
"import": "./dist/mailer.js",
|
|
155
|
+
"types": "./dist/mailer.d.ts"
|
|
156
|
+
},
|
|
157
|
+
"./dkim": {
|
|
158
|
+
"import": "./dist/dkim.js",
|
|
159
|
+
"types": "./dist/dkim.d.ts"
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
}
|
package/dist/chunk-794hc3m4.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// src/core/base64.ts
|
|
2
|
-
var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
-
var BASE64_LINE_LENGTH = 76;
|
|
4
|
-
var encoder = new TextEncoder;
|
|
5
|
-
var decoder = new TextDecoder;
|
|
6
|
-
function encodeBase64(data) {
|
|
7
|
-
const bytes = typeof data === "string" ? encoder.encode(data) : data;
|
|
8
|
-
let result = "";
|
|
9
|
-
let i = 0;
|
|
10
|
-
while (i < bytes.length) {
|
|
11
|
-
const b0 = bytes[i] ?? 0;
|
|
12
|
-
const b1 = bytes[i + 1];
|
|
13
|
-
const b2 = bytes[i + 2];
|
|
14
|
-
if (b1 === undefined) {
|
|
15
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
16
|
-
result += BASE64_CHARS[(b0 & 3) << 4];
|
|
17
|
-
result += "==";
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
if (b2 === undefined) {
|
|
21
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
22
|
-
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
23
|
-
result += BASE64_CHARS[(b1 & 15) << 2];
|
|
24
|
-
result += "=";
|
|
25
|
-
break;
|
|
26
|
-
}
|
|
27
|
-
result += BASE64_CHARS[b0 >> 2];
|
|
28
|
-
result += BASE64_CHARS[(b0 & 3) << 4 | b1 >> 4];
|
|
29
|
-
result += BASE64_CHARS[(b1 & 15) << 2 | b2 >> 6];
|
|
30
|
-
result += BASE64_CHARS[b2 & 63];
|
|
31
|
-
i += 3;
|
|
32
|
-
}
|
|
33
|
-
return wrapBase64Lines(result);
|
|
34
|
-
}
|
|
35
|
-
function decodeBase64(data) {
|
|
36
|
-
const cleaned = data.replace(/\s/g, "");
|
|
37
|
-
const len = cleaned.length;
|
|
38
|
-
if (len === 0) {
|
|
39
|
-
return new Uint8Array(0);
|
|
40
|
-
}
|
|
41
|
-
if (len % 4 !== 0) {
|
|
42
|
-
throw new Error("Invalid base64 string length");
|
|
43
|
-
}
|
|
44
|
-
const padding = cleaned.endsWith("==") ? 2 : cleaned.endsWith("=") ? 1 : 0;
|
|
45
|
-
const outputLen = len * 3 / 4 - padding;
|
|
46
|
-
const output = new Uint8Array(outputLen);
|
|
47
|
-
let outIndex = 0;
|
|
48
|
-
for (let i = 0;i < len; i += 4) {
|
|
49
|
-
const c0 = base64CharToValue(cleaned[i] ?? "=");
|
|
50
|
-
const c1 = base64CharToValue(cleaned[i + 1] ?? "=");
|
|
51
|
-
const c2 = base64CharToValue(cleaned[i + 2] ?? "=");
|
|
52
|
-
const c3 = base64CharToValue(cleaned[i + 3] ?? "=");
|
|
53
|
-
const triple = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
54
|
-
if (outIndex < outputLen) {
|
|
55
|
-
output[outIndex++] = triple >> 16 & 255;
|
|
56
|
-
}
|
|
57
|
-
if (outIndex < outputLen) {
|
|
58
|
-
output[outIndex++] = triple >> 8 & 255;
|
|
59
|
-
}
|
|
60
|
-
if (outIndex < outputLen) {
|
|
61
|
-
output[outIndex++] = triple & 255;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return output;
|
|
65
|
-
}
|
|
66
|
-
function encodeHeader(value) {
|
|
67
|
-
if (!needsEncoding(value)) {
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
const encoded = encodeBase64(value).replace(/\r\n/g, "");
|
|
71
|
-
return `=?UTF-8?B?${encoded}?=`;
|
|
72
|
-
}
|
|
73
|
-
function needsEncoding(text) {
|
|
74
|
-
for (let i = 0;i < text.length; i++) {
|
|
75
|
-
if (text.charCodeAt(i) > 127) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
function base64CharToValue(char) {
|
|
82
|
-
if (char === "=") {
|
|
83
|
-
return 0;
|
|
84
|
-
}
|
|
85
|
-
const index = BASE64_CHARS.indexOf(char);
|
|
86
|
-
if (index === -1) {
|
|
87
|
-
throw new Error(`Invalid base64 character: ${char}`);
|
|
88
|
-
}
|
|
89
|
-
return index;
|
|
90
|
-
}
|
|
91
|
-
function wrapBase64Lines(base64) {
|
|
92
|
-
const lines = [];
|
|
93
|
-
for (let i = 0;i < base64.length; i += BASE64_LINE_LENGTH) {
|
|
94
|
-
lines.push(base64.slice(i, i + BASE64_LINE_LENGTH));
|
|
95
|
-
}
|
|
96
|
-
return lines.join(`\r
|
|
97
|
-
`);
|
|
98
|
-
}
|
|
99
|
-
function encodeUtf8(text) {
|
|
100
|
-
return encoder.encode(text);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export { encodeBase64, decodeBase64, encodeHeader, encodeUtf8 };
|
|
104
|
-
|
|
105
|
-
//# debugId=93C9FABDDF825C4864756E2164756E21
|
package/dist/chunk-7fqv71z1.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
OAuth2Client
|
|
3
|
-
} from "./chunk-ym3zzv8b.js";
|
|
4
|
-
import {
|
|
5
|
-
buildMIME
|
|
6
|
-
} from "./chunk-x3szga4k.js";
|
|
7
|
-
import {
|
|
8
|
-
SMTPError,
|
|
9
|
-
accumulateResponse,
|
|
10
|
-
assertResponse,
|
|
11
|
-
computeCRAMMD5,
|
|
12
|
-
encodeAuthLoginPass,
|
|
13
|
-
encodeAuthLoginUser,
|
|
14
|
-
encodeCommand,
|
|
15
|
-
encodeLine,
|
|
16
|
-
parseEHLO,
|
|
17
|
-
parseResponse,
|
|
18
|
-
selectAuthMethod
|
|
19
|
-
} from "./chunk-tymfm441.js";
|
|
20
|
-
import {
|
|
21
|
-
resolveAttachments
|
|
22
|
-
} from "./chunk-f4c9ttmr.js";
|
|
23
|
-
import {
|
|
24
|
-
__require
|
|
25
|
-
} from "./chunk-v0bahtg2.js";
|
|
26
|
-
|
|
27
|
-
// src/transports/smtp.ts
|
|
28
|
-
class SMTPTransport {
|
|
29
|
-
config;
|
|
30
|
-
adapter = null;
|
|
31
|
-
constructor(config) {
|
|
32
|
-
this.config = resolveSMTPConfig(config);
|
|
33
|
-
}
|
|
34
|
-
async send(options) {
|
|
35
|
-
const resolvedOptions = {
|
|
36
|
-
...options,
|
|
37
|
-
attachments: await resolveAttachments(options.attachments)
|
|
38
|
-
};
|
|
39
|
-
const mime = await buildMIME(resolvedOptions, this.config.dkim);
|
|
40
|
-
const adapter = await this.getAdapter();
|
|
41
|
-
const host = this.config.direct ? await resolveMX(mime.envelope.from.split("@")[1] ?? this.config.host) : this.config.host;
|
|
42
|
-
await adapter.connect(host, this.config.port);
|
|
43
|
-
this.adapter = adapter;
|
|
44
|
-
try {
|
|
45
|
-
await openSMTPSession(adapter, this.config);
|
|
46
|
-
return await deliverSMTPMessage(adapter, mime);
|
|
47
|
-
} finally {
|
|
48
|
-
await closeSMTPSession(adapter);
|
|
49
|
-
this.adapter = null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async verify() {
|
|
53
|
-
try {
|
|
54
|
-
const adapter = await this.getAdapter();
|
|
55
|
-
await adapter.connect(this.config.host, this.config.port);
|
|
56
|
-
try {
|
|
57
|
-
await openSMTPSession(adapter, this.config);
|
|
58
|
-
return { ok: true, provider: "smtp" };
|
|
59
|
-
} finally {
|
|
60
|
-
await closeSMTPSession(adapter);
|
|
61
|
-
}
|
|
62
|
-
} catch (err) {
|
|
63
|
-
return {
|
|
64
|
-
ok: false,
|
|
65
|
-
provider: "smtp",
|
|
66
|
-
message: err instanceof Error ? err.message : String(err)
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async close() {
|
|
71
|
-
if (this.adapter) {
|
|
72
|
-
await this.adapter.close();
|
|
73
|
-
this.adapter = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async getAdapter() {
|
|
77
|
-
if (!this.config.adapter) {
|
|
78
|
-
throw new SMTPError("No socket adapter configured", 0, "CONNECT", "");
|
|
79
|
-
}
|
|
80
|
-
return this.config.adapter;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function resolveSMTPConfig(config) {
|
|
84
|
-
const secure = config.secure ?? false;
|
|
85
|
-
return {
|
|
86
|
-
host: config.host,
|
|
87
|
-
port: config.port ?? (secure ? 465 : 587),
|
|
88
|
-
secure,
|
|
89
|
-
...config.auth !== undefined ? { auth: config.auth } : {},
|
|
90
|
-
...config.requireTLS !== undefined ? { requireTLS: config.requireTLS } : {},
|
|
91
|
-
...config.dkim !== undefined ? { dkim: config.dkim } : {},
|
|
92
|
-
...config.tls !== undefined ? { tls: config.tls } : {},
|
|
93
|
-
...config.connectionTimeout !== undefined ? { connectionTimeout: config.connectionTimeout } : {},
|
|
94
|
-
...config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {},
|
|
95
|
-
...config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {},
|
|
96
|
-
...config.direct !== undefined ? { direct: config.direct } : {},
|
|
97
|
-
...config.adapter !== undefined ? { adapter: config.adapter } : {}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
async function openSMTPSession(adapter, config) {
|
|
101
|
-
const greeting = await readSMTPResponse(adapter);
|
|
102
|
-
assertResponse(greeting, [220], "greeting");
|
|
103
|
-
let capabilities = await ehlo(adapter, config.host);
|
|
104
|
-
const supportsStartTls = capabilities.some((cap) => cap.toUpperCase() === "STARTTLS");
|
|
105
|
-
if (!config.secure && !adapter.secure && supportsStartTls) {
|
|
106
|
-
await sendRaw(adapter, encodeCommand({ type: "STARTTLS" }));
|
|
107
|
-
const starttlsResp = await readSMTPResponse(adapter);
|
|
108
|
-
assertResponse(starttlsResp, [220], "STARTTLS");
|
|
109
|
-
await adapter.startTLS(config.tls);
|
|
110
|
-
capabilities = await ehlo(adapter, config.host);
|
|
111
|
-
}
|
|
112
|
-
if (config.auth) {
|
|
113
|
-
const tlsRequired = config.requireTLS ?? true;
|
|
114
|
-
const encrypted = adapter.secure || config.secure;
|
|
115
|
-
if (tlsRequired && !encrypted) {
|
|
116
|
-
throw new SMTPError("Refusing to authenticate over unencrypted connection. " + "Set requireTLS: false to disable this check (not recommended).", 0, "AUTH", "");
|
|
117
|
-
}
|
|
118
|
-
await authenticate(adapter, config.auth, capabilities);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
async function deliverSMTPMessage(adapter, mime) {
|
|
122
|
-
await sendCommand(adapter, { type: "MAIL_FROM", address: mime.envelope.from });
|
|
123
|
-
const mailResp = await readSMTPResponse(adapter);
|
|
124
|
-
assertResponse(mailResp, [250], "MAIL FROM");
|
|
125
|
-
const accepted = [];
|
|
126
|
-
const rejected = [];
|
|
127
|
-
for (const recipient of mime.envelope.to) {
|
|
128
|
-
await sendRaw(adapter, encodeCommand({ type: "RCPT_TO", address: recipient }));
|
|
129
|
-
const rcptResp = await readSMTPResponse(adapter);
|
|
130
|
-
if (rcptResp.isSuccess) {
|
|
131
|
-
accepted.push(recipient);
|
|
132
|
-
} else {
|
|
133
|
-
rejected.push(recipient);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
await sendCommand(adapter, { type: "DATA" });
|
|
137
|
-
const dataResp = await readSMTPResponse(adapter);
|
|
138
|
-
assertResponse(dataResp, [354], "DATA");
|
|
139
|
-
let finalResp;
|
|
140
|
-
try {
|
|
141
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
142
|
-
finalResp = await readSMTPResponse(adapter);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
await sendRaw(adapter, encodeCommand({ type: "DATA_BODY", content: mime.raw }));
|
|
145
|
-
finalResp = await readSMTPResponse(adapter);
|
|
146
|
-
if (finalResp.isError) {
|
|
147
|
-
throw err;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
assertResponse(finalResp, [250], "DATA end");
|
|
151
|
-
return {
|
|
152
|
-
messageId: mime.messageId,
|
|
153
|
-
accepted,
|
|
154
|
-
rejected,
|
|
155
|
-
response: finalResp.message,
|
|
156
|
-
envelope: mime.envelope
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
async function closeSMTPSession(adapter) {
|
|
160
|
-
try {
|
|
161
|
-
await sendCommand(adapter, { type: "QUIT" });
|
|
162
|
-
await readSMTPResponse(adapter);
|
|
163
|
-
} catch {} finally {
|
|
164
|
-
await adapter.close();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function ehlo(adapter, host) {
|
|
168
|
-
await sendCommand(adapter, { type: "EHLO", domain: host });
|
|
169
|
-
const response = await readSMTPResponse(adapter);
|
|
170
|
-
assertResponse(response, [250], "EHLO");
|
|
171
|
-
return parseEHLO(response);
|
|
172
|
-
}
|
|
173
|
-
async function authenticate(adapter, auth, capabilities) {
|
|
174
|
-
if (auth.type === "OAUTH2" && auth.oauth2) {
|
|
175
|
-
const client = new OAuth2Client(auth.oauth2);
|
|
176
|
-
const xoauth2 = await client.buildXOAUTH2();
|
|
177
|
-
await sendCommand(adapter, { type: "AUTH_XOAUTH2", xoauth2String: xoauth2 });
|
|
178
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
179
|
-
if (resp2.code === 334) {
|
|
180
|
-
await sendRaw(adapter, encodeLine(""));
|
|
181
|
-
resp2 = await readSMTPResponse(adapter);
|
|
182
|
-
}
|
|
183
|
-
assertResponse(resp2, [235], "AUTH XOAUTH2");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const method = auth.type ?? selectAuthMethod(capabilities);
|
|
187
|
-
if (method === "CRAM-MD5") {
|
|
188
|
-
const pass2 = requirePassword(auth, "CRAM-MD5");
|
|
189
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_INIT" });
|
|
190
|
-
let resp2 = await readSMTPResponse(adapter);
|
|
191
|
-
assertResponse(resp2, [334], "AUTH CRAM-MD5");
|
|
192
|
-
const challenge = resp2.message.trim();
|
|
193
|
-
const response = await computeCRAMMD5(challenge, auth.user, pass2);
|
|
194
|
-
await sendCommand(adapter, { type: "AUTH_CRAM_MD5_RESPONSE", response });
|
|
195
|
-
resp2 = await readSMTPResponse(adapter);
|
|
196
|
-
assertResponse(resp2, [235], "AUTH CRAM-MD5 response");
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (method === "PLAIN") {
|
|
200
|
-
const pass2 = requirePassword(auth, "PLAIN");
|
|
201
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_PLAIN", user: auth.user, pass: pass2 }));
|
|
202
|
-
const resp2 = await readSMTPResponse(adapter);
|
|
203
|
-
assertResponse(resp2, [235], "AUTH PLAIN");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const pass = requirePassword(auth, "LOGIN");
|
|
207
|
-
await sendRaw(adapter, encodeCommand({ type: "AUTH_LOGIN", user: auth.user, pass }));
|
|
208
|
-
let resp = await readSMTPResponse(adapter);
|
|
209
|
-
assertResponse(resp, [334], "AUTH LOGIN");
|
|
210
|
-
await sendRaw(adapter, encodeAuthLoginUser(auth.user));
|
|
211
|
-
resp = await readSMTPResponse(adapter);
|
|
212
|
-
assertResponse(resp, [334], "AUTH LOGIN user");
|
|
213
|
-
await sendRaw(adapter, encodeAuthLoginPass(pass));
|
|
214
|
-
resp = await readSMTPResponse(adapter);
|
|
215
|
-
assertResponse(resp, [235], "AUTH LOGIN pass");
|
|
216
|
-
}
|
|
217
|
-
function requirePassword(auth, method) {
|
|
218
|
-
if (!auth.pass) {
|
|
219
|
-
throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, "");
|
|
220
|
-
}
|
|
221
|
-
return auth.pass;
|
|
222
|
-
}
|
|
223
|
-
async function sendCommand(adapter, command) {
|
|
224
|
-
await sendRaw(adapter, encodeCommand(command));
|
|
225
|
-
}
|
|
226
|
-
async function sendRaw(adapter, data) {
|
|
227
|
-
await adapter.write(data);
|
|
228
|
-
}
|
|
229
|
-
async function readSMTPResponse(adapter) {
|
|
230
|
-
const chunks = [];
|
|
231
|
-
for await (const chunk of adapter.read()) {
|
|
232
|
-
chunks.push(chunk);
|
|
233
|
-
const complete = accumulateResponse(chunks);
|
|
234
|
-
if (complete) {
|
|
235
|
-
return parseResponse(complete);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
throw new SMTPError("Connection closed while reading SMTP response", 0, "READ", "");
|
|
239
|
-
}
|
|
240
|
-
async function resolveMX(domain) {
|
|
241
|
-
const dns = await import("node:dns/promises");
|
|
242
|
-
const records = await dns.resolveMx(domain);
|
|
243
|
-
if (records.length === 0) {
|
|
244
|
-
throw new SMTPError(`No MX records for ${domain}`, 0, "MX", "");
|
|
245
|
-
}
|
|
246
|
-
records.sort((a, b) => a.priority - b.priority);
|
|
247
|
-
return records[0]?.exchange ?? domain;
|
|
248
|
-
}
|
|
249
|
-
export { SMTPTransport, resolveSMTPConfig, openSMTPSession, deliverSMTPMessage, closeSMTPSession, readSMTPResponse };
|
|
250
|
-
|
|
251
|
-
//# debugId=736A9106E3164FC464756E2164756E21
|