sently 0.4.5 → 0.4.6
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 +9 -0
- package/dist/adapters/bun.d.ts +7 -0
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.d.ts +6 -0
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.d.ts +4 -0
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.d.ts +7 -0
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.d.ts +4 -0
- package/dist/chunk-7fqv71z1.js.map +2 -2
- package/dist/chunk-mp5c9bfd.js.map +3 -3
- package/dist/chunk-tymfm441.js.map +2 -2
- package/dist/chunk-x3szga4k.js.map +2 -2
- package/dist/chunk-ym3zzv8b.js.map +2 -2
- package/dist/core/mime.d.ts +4 -0
- package/dist/core/sigv4.d.ts +10 -0
- package/dist/core/smtp.d.ts +5 -0
- package/dist/pool/connection.d.ts +4 -0
- package/dist/pool/pool.d.ts +20 -0
- package/dist/transports/brevo.d.ts +1 -0
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.d.ts +3 -0
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.d.ts +1 -0
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.d.ts +3 -0
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.d.ts +2 -0
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.d.ts +12 -1
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.d.ts +1 -0
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.d.ts +5 -0
- package/dist/transports/ses.js.map +3 -3
- package/dist/transports/smtp.d.ts +3 -0
- package/package.json +1 -1
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/core/cram-md5.ts", "../src/core/smtp.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Pure-JS HMAC-MD5 implementation for SMTP CRAM-MD5 authentication.\n * Web Crypto does not support MD5, so this is implemented in pure TypeScript\n * with no external dependencies.\n *\n * @example\n * ```ts\n * import { computeCRAMMD5 } from \"sently/core/cram-md5\";\n * const response = await computeCRAMMD5(\"<challenge>\", \"user\", \"pass\");\n * ```\n */\nimport { decodeBase64, encodeBase64, encodeUtf8 } from \"./base64.js\";\n\n/** MD5 block size in bytes (HMAC block size per RFC 2104). */\nconst BLOCK_SIZE = 64;\n\n/** Coerce to unsigned 32-bit integer. */\nfunction u32(x: number): number {\n return x >>> 0;\n}\n\n/**\n * Compute an MD5 hash of the given data (RFC 1321).\n */\nexport function md5(data: Uint8Array): Uint8Array {\n const padded = padMessage(data);\n\n let a0 = 0x67452301;\n let b0 = 0xefcdab89;\n let c0 = 0x98badcfe;\n let d0 = 0x10325476;\n\n for (let i = 0; i < padded.length; i += 64) {\n const block = padded.subarray(i, i + 64);\n const m = new Uint32Array(16);\n for (let j = 0; j < 16; j++) {\n const o = j * 4;\n m[j] = u32(\n (block[o] ?? 0) |\n ((block[o + 1] ?? 0) << 8) |\n ((block[o + 2] ?? 0) << 16) |\n ((block[o + 3] ?? 0) << 24),\n );\n }\n\n let a = a0;\n let b = b0;\n let c = c0;\n let d = d0;\n\n for (let k = 0; k < 64; k++) {\n let f: number;\n let g: number;\n if (k < 16) {\n f = u32((b & c) | (~b & d));\n g = k;\n } else if (k < 32) {\n f = u32((b & d) | (c & ~d));\n g = u32((5 * k + 1) % 16);\n } else if (k < 48) {\n f = u32(b ^ c ^ d);\n g = u32((3 * k + 5) % 16);\n } else {\n f = u32(c ^ (b | ~d));\n g = u32((7 * k) % 16);\n }\n\n const temp = d;\n d = c;\n c = b;\n b = u32(b + leftRotate(u32(a + f + u32((K[k] ?? 0) + (m[g] ?? 0))), S[k] ?? 0));\n a = temp;\n }\n\n a0 = u32(a0 + a);\n b0 = u32(b0 + b);\n c0 = u32(c0 + c);\n d0 = u32(d0 + d);\n }\n\n const out = new Uint8Array(16);\n const view = new DataView(out.buffer);\n view.setUint32(0, a0, true);\n view.setUint32(4, b0, true);\n view.setUint32(8, c0, true);\n view.setUint32(12, d0, true);\n return out;\n}\n\nconst S = [\n 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14,\n 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6,\n 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,\n];\n\nconst K = new Uint32Array([\n 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,\n 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,\n 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,\n 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,\n 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,\n 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,\n 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,\n 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,\n]);\n\nfunction leftRotate(value: number, shift: number): number {\n return u32((value << shift) | (value >>> (32 - shift)));\n}\n\nfunction padMessage(data: Uint8Array): Uint8Array {\n const bitLen = data.length * 8;\n const padLen = (56 - ((data.length + 1) % 64) + 64) % 64;\n const totalLen = data.length + 1 + padLen + 8;\n const padded = new Uint8Array(totalLen);\n padded.set(data);\n padded[data.length] = 0x80;\n\n const view = new DataView(padded.buffer);\n view.setUint32(totalLen - 8, bitLen >>> 0, true);\n view.setUint32(totalLen - 4, Math.floor(bitLen / 0x100000000), true);\n return padded;\n}\n\n/**\n * Compute HMAC-MD5(key, data) per RFC 2104.\n */\nexport function hmacMD5(key: Uint8Array, data: Uint8Array): Uint8Array {\n let k = key;\n if (k.length > BLOCK_SIZE) {\n k = md5(k);\n }\n const paddedKey = new Uint8Array(BLOCK_SIZE);\n paddedKey.set(k);\n\n const ipad = new Uint8Array(BLOCK_SIZE);\n const opad = new Uint8Array(BLOCK_SIZE);\n for (let i = 0; i < BLOCK_SIZE; i++) {\n ipad[i] = (paddedKey[i] ?? 0) ^ 0x36;\n opad[i] = (paddedKey[i] ?? 0) ^ 0x5c;\n }\n\n const inner = new Uint8Array(ipad.length + data.length);\n inner.set(ipad);\n inner.set(data, ipad.length);\n const innerHash = md5(inner);\n\n const outer = new Uint8Array(opad.length + innerHash.length);\n outer.set(opad);\n outer.set(innerHash, opad.length);\n return md5(outer);\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Compute the CRAM-MD5 response string for SMTP authentication.\n *\n * @param challenge - base64-encoded challenge from server\n * @param user - SMTP username\n * @param pass - SMTP password\n * @returns base64-encoded CRAM-MD5 response\n */\nexport async function computeCRAMMD5(\n challenge: string,\n user: string,\n pass: string,\n): Promise<string> {\n const challengeBytes = decodeBase64(challenge.trim());\n const passBytes = encodeUtf8(pass);\n const digest = hmacMD5(passBytes, challengeBytes);\n const hex = bytesToHex(digest);\n return encodeBase64(`${user} ${hex}`).replace(/\\r\\n/g, \"\");\n}\n",
|
|
6
|
-
"// src/core/smtp.ts\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\n\nexport { computeCRAMMD5 } from \"./cram-md5.js\";\n\n/** SMTP command to send to the server. */\nexport type SMTPCommand =\n | { type: \"EHLO\"; domain: string }\n | { type: \"STARTTLS\" }\n | { type: \"AUTH_LOGIN\"; user: string; pass: string }\n | { type: \"AUTH_PLAIN\"; user: string; pass: string }\n | { type: \"AUTH_CRAM_MD5_INIT\" }\n | { type: \"AUTH_CRAM_MD5_RESPONSE\"; response: string }\n | { type: \"AUTH_XOAUTH2\"; xoauth2String: string }\n | { type: \"MAIL_FROM\"; address: string }\n | { type: \"RCPT_TO\"; address: string }\n | { type: \"DATA\" }\n | { type: \"DATA_BODY\"; content: Uint8Array }\n | { type: \"QUIT\" }\n | { type: \"RSET\" }\n | { type: \"NOOP\" };\n\n/** Parsed SMTP server response. */\nexport interface SMTPResponse {\n code: number;\n message: string;\n isSuccess: boolean;\n isReady: boolean;\n isError: boolean;\n}\n\n/** SMTP protocol error with server response details. */\nexport class SMTPError extends Error {\n /** Creates an SMTP protocol error. */\n constructor(\n message: string,\n public readonly code: number,\n public readonly command: string,\n public readonly response: string,\n ) {\n super(message);\n this.name = \"SMTPError\";\n }\n}\n\n/**\n * Encode an SMTPCommand into a Uint8Array for sending over the socket.\n */\nexport function encodeCommand(cmd: SMTPCommand): Uint8Array {\n let line: string;\n\n switch (cmd.type) {\n case \"EHLO\": {\n const safeDomain = cmd.domain.replace(/[\\r\\n]/g, \"\");\n line = `EHLO ${safeDomain}`;\n break;\n }\n case \"STARTTLS\":\n line = \"STARTTLS\";\n break;\n case \"AUTH_LOGIN\":\n line = \"AUTH LOGIN\";\n break;\n case \"AUTH_PLAIN\":\n line = `AUTH PLAIN ${encodeBase64(`\\0${cmd.user}\\0${cmd.pass}`).replace(/\\r\\n/g, \"\")}`;\n break;\n case \"AUTH_CRAM_MD5_INIT\":\n line = \"AUTH CRAM-MD5\";\n break;\n case \"AUTH_CRAM_MD5_RESPONSE\":\n return encodeUtf8(`${cmd.response}\\r\\n`);\n case \"AUTH_XOAUTH2\":\n line = `AUTH XOAUTH2 ${cmd.xoauth2String}`;\n break;\n case \"MAIL_FROM\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"MAIL FROM\", cmd.address);\n }\n line = `MAIL FROM:<${cmd.address}>`;\n break;\n case \"RCPT_TO\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"RCPT TO\", cmd.address);\n }\n line = `RCPT TO:<${cmd.address}>`;\n break;\n case \"DATA\":\n line = \"DATA\";\n break;\n case \"DATA_BODY\":\n return encodeUtf8(applyDotStuffing(cmd.content));\n case \"QUIT\":\n line = \"QUIT\";\n break;\n case \"RSET\":\n line = \"RSET\";\n break;\n case \"NOOP\":\n line = \"NOOP\";\n break;\n }\n\n return encodeUtf8(`${line}\\r\\n`);\n}\n\n/**\n * Parse raw bytes from the server into an SMTPResponse.\n */\nexport function parseResponse(data: Uint8Array): SMTPResponse {\n const text = new TextDecoder().decode(data).trim();\n const lines = text.split(/\\r?\\n/);\n const lastLine = lines[lines.length - 1] ?? \"\";\n const match = lastLine.match(/^(\\d{3})([\\s-])(.*)$/);\n\n if (!match) {\n throw new SMTPError(\"Invalid SMTP response\", 0, \"PARSE\", text);\n }\n\n const code = Number.parseInt(match[1] ?? \"0\", 10);\n const message = lines.map((l) => l.replace(/^\\d{3}[\\s-]/, \"\")).join(\" \");\n\n return {\n code,\n message,\n isSuccess: code >= 200 && code < 300,\n isReady: code >= 300 && code < 400,\n isError: code >= 400,\n };\n}\n\n/**\n * Accumulate byte chunks until a complete SMTP response is received.\n */\nexport function accumulateResponse(chunks: Uint8Array[]): Uint8Array | null {\n if (chunks.length === 0) {\n return null;\n }\n\n const total = chunks.reduce((sum, c) => sum + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n const text = new TextDecoder().decode(combined);\n const lines = text.split(/\\r?\\n/).filter((l) => l.length > 0);\n\n if (lines.length === 0) {\n return null;\n }\n\n const lastLine = lines[lines.length - 1] ?? \"\";\n if (/^\\d{3} /.test(lastLine)) {\n return combined;\n }\n\n return null;\n}\n\n/**\n * Select the best AUTH method from EHLO capability lines.\n * Priority: XOAUTH2 > CRAM-MD5 > LOGIN > PLAIN.\n */\nexport function selectAuthMethod(\n capabilities: string[],\n): \"LOGIN\" | \"PLAIN\" | \"CRAM-MD5\" | \"OAUTH2\" {\n const upper = capabilities.map((c) => c.toUpperCase());\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"XOAUTH2\"))) {\n return \"OAUTH2\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"CRAM-MD5\"))) {\n return \"CRAM-MD5\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"LOGIN\"))) {\n return \"LOGIN\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"PLAIN\"))) {\n return \"PLAIN\";\n }\n throw new SMTPError(\"No supported AUTH method\", 0, \"EHLO\", capabilities.join(\" \"));\n}\n\n/**\n * Parse an EHLO multi-line response and extract capability keywords.\n */\nexport function parseEHLO(response: SMTPResponse): string[] {\n return response.message\n .split(/\\s+/)\n .flatMap((part) => part.split(/\\r?\\n/))\n .filter(Boolean);\n}\n\n/**\n * Assert that an SMTPResponse code is within the expected set.\n */\nexport function assertResponse(\n response: SMTPResponse,\n expectedCodes: number[],\n command: string,\n): void {\n if (!expectedCodes.includes(response.code)) {\n throw new SMTPError(\n `Unexpected SMTP response for ${command}`,\n response.code,\n command,\n response.message,\n );\n }\n}\n\nfunction applyDotStuffing(content: Uint8Array): string {\n const text = new TextDecoder().decode(content);\n const lines = text.split(/\\r?\\n/);\n const stuffed = lines.map((line) => (line.startsWith(\".\") ? `.${line}` : line));\n return `${stuffed.join(\"\\r\\n\")}\\r\\n.\\r\\n`;\n}\n\n/** Encode AUTH LOGIN password step (second base64 chunk). */\nexport function encodeAuthLoginPass(pass: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(pass).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode AUTH LOGIN user step when sent separately after 334. */\nexport function encodeAuthLoginUser(user: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(user).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode CRAM-MD5 response after challenge. */\nexport function encodeAuthCramResponse(response: string): Uint8Array {\n return encodeUtf8(`${response}\\r\\n`);\n}\n\n/** Encode raw SMTP line with CRLF. */\nexport function encodeLine(line: string): Uint8Array {\n return encodeUtf8(`${line}\\r\\n`);\n}\n"
|
|
6
|
+
"// src/core/smtp.ts\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\n\nexport { computeCRAMMD5 } from \"./cram-md5.js\";\n\n/** SMTP command to send to the server. */\nexport type SMTPCommand =\n | { type: \"EHLO\"; domain: string }\n | { type: \"STARTTLS\" }\n | { type: \"AUTH_LOGIN\"; user: string; pass: string }\n | { type: \"AUTH_PLAIN\"; user: string; pass: string }\n | { type: \"AUTH_CRAM_MD5_INIT\" }\n | { type: \"AUTH_CRAM_MD5_RESPONSE\"; response: string }\n | { type: \"AUTH_XOAUTH2\"; xoauth2String: string }\n | { type: \"MAIL_FROM\"; address: string }\n | { type: \"RCPT_TO\"; address: string }\n | { type: \"DATA\" }\n | { type: \"DATA_BODY\"; content: Uint8Array }\n | { type: \"QUIT\" }\n | { type: \"RSET\" }\n | { type: \"NOOP\" };\n\n/** Parsed SMTP server response. */\nexport interface SMTPResponse {\n /** Three-digit SMTP status code. */\n code: number;\n /** Human-readable response text after the status code. */\n message: string;\n /** True when the code is in the 2xx success range. */\n isSuccess: boolean;\n /** True when the code is 354 (ready for message data). */\n isReady: boolean;\n /** True when the code is 4xx or 5xx. */\n isError: boolean;\n}\n\n/** SMTP protocol error with server response details. */\nexport class SMTPError extends Error {\n /** Creates an SMTP protocol error. */\n constructor(\n message: string,\n public readonly code: number,\n public readonly command: string,\n public readonly response: string,\n ) {\n super(message);\n this.name = \"SMTPError\";\n }\n}\n\n/**\n * Encode an SMTPCommand into a Uint8Array for sending over the socket.\n */\nexport function encodeCommand(cmd: SMTPCommand): Uint8Array {\n let line: string;\n\n switch (cmd.type) {\n case \"EHLO\": {\n const safeDomain = cmd.domain.replace(/[\\r\\n]/g, \"\");\n line = `EHLO ${safeDomain}`;\n break;\n }\n case \"STARTTLS\":\n line = \"STARTTLS\";\n break;\n case \"AUTH_LOGIN\":\n line = \"AUTH LOGIN\";\n break;\n case \"AUTH_PLAIN\":\n line = `AUTH PLAIN ${encodeBase64(`\\0${cmd.user}\\0${cmd.pass}`).replace(/\\r\\n/g, \"\")}`;\n break;\n case \"AUTH_CRAM_MD5_INIT\":\n line = \"AUTH CRAM-MD5\";\n break;\n case \"AUTH_CRAM_MD5_RESPONSE\":\n return encodeUtf8(`${cmd.response}\\r\\n`);\n case \"AUTH_XOAUTH2\":\n line = `AUTH XOAUTH2 ${cmd.xoauth2String}`;\n break;\n case \"MAIL_FROM\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"MAIL FROM\", cmd.address);\n }\n line = `MAIL FROM:<${cmd.address}>`;\n break;\n case \"RCPT_TO\":\n if (/[\\r\\n]/.test(cmd.address)) {\n throw new SMTPError(`Invalid address: contains CRLF`, 0, \"RCPT TO\", cmd.address);\n }\n line = `RCPT TO:<${cmd.address}>`;\n break;\n case \"DATA\":\n line = \"DATA\";\n break;\n case \"DATA_BODY\":\n return encodeUtf8(applyDotStuffing(cmd.content));\n case \"QUIT\":\n line = \"QUIT\";\n break;\n case \"RSET\":\n line = \"RSET\";\n break;\n case \"NOOP\":\n line = \"NOOP\";\n break;\n }\n\n return encodeUtf8(`${line}\\r\\n`);\n}\n\n/**\n * Parse raw bytes from the server into an SMTPResponse.\n */\nexport function parseResponse(data: Uint8Array): SMTPResponse {\n const text = new TextDecoder().decode(data).trim();\n const lines = text.split(/\\r?\\n/);\n const lastLine = lines[lines.length - 1] ?? \"\";\n const match = lastLine.match(/^(\\d{3})([\\s-])(.*)$/);\n\n if (!match) {\n throw new SMTPError(\"Invalid SMTP response\", 0, \"PARSE\", text);\n }\n\n const code = Number.parseInt(match[1] ?? \"0\", 10);\n const message = lines.map((l) => l.replace(/^\\d{3}[\\s-]/, \"\")).join(\" \");\n\n return {\n code,\n message,\n isSuccess: code >= 200 && code < 300,\n isReady: code >= 300 && code < 400,\n isError: code >= 400,\n };\n}\n\n/**\n * Accumulate byte chunks until a complete SMTP response is received.\n */\nexport function accumulateResponse(chunks: Uint8Array[]): Uint8Array | null {\n if (chunks.length === 0) {\n return null;\n }\n\n const total = chunks.reduce((sum, c) => sum + c.length, 0);\n const combined = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n const text = new TextDecoder().decode(combined);\n const lines = text.split(/\\r?\\n/).filter((l) => l.length > 0);\n\n if (lines.length === 0) {\n return null;\n }\n\n const lastLine = lines[lines.length - 1] ?? \"\";\n if (/^\\d{3} /.test(lastLine)) {\n return combined;\n }\n\n return null;\n}\n\n/**\n * Select the best AUTH method from EHLO capability lines.\n * Priority: XOAUTH2 > CRAM-MD5 > LOGIN > PLAIN.\n */\nexport function selectAuthMethod(\n capabilities: string[],\n): \"LOGIN\" | \"PLAIN\" | \"CRAM-MD5\" | \"OAUTH2\" {\n const upper = capabilities.map((c) => c.toUpperCase());\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"XOAUTH2\"))) {\n return \"OAUTH2\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"CRAM-MD5\"))) {\n return \"CRAM-MD5\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"LOGIN\"))) {\n return \"LOGIN\";\n }\n if (upper.some((c) => c.includes(\"AUTH\") && c.includes(\"PLAIN\"))) {\n return \"PLAIN\";\n }\n throw new SMTPError(\"No supported AUTH method\", 0, \"EHLO\", capabilities.join(\" \"));\n}\n\n/**\n * Parse an EHLO multi-line response and extract capability keywords.\n */\nexport function parseEHLO(response: SMTPResponse): string[] {\n return response.message\n .split(/\\s+/)\n .flatMap((part) => part.split(/\\r?\\n/))\n .filter(Boolean);\n}\n\n/**\n * Assert that an SMTPResponse code is within the expected set.\n */\nexport function assertResponse(\n response: SMTPResponse,\n expectedCodes: number[],\n command: string,\n): void {\n if (!expectedCodes.includes(response.code)) {\n throw new SMTPError(\n `Unexpected SMTP response for ${command}`,\n response.code,\n command,\n response.message,\n );\n }\n}\n\nfunction applyDotStuffing(content: Uint8Array): string {\n const text = new TextDecoder().decode(content);\n const lines = text.split(/\\r?\\n/);\n const stuffed = lines.map((line) => (line.startsWith(\".\") ? `.${line}` : line));\n return `${stuffed.join(\"\\r\\n\")}\\r\\n.\\r\\n`;\n}\n\n/** Encode AUTH LOGIN password step (second base64 chunk). */\nexport function encodeAuthLoginPass(pass: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(pass).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode AUTH LOGIN user step when sent separately after 334. */\nexport function encodeAuthLoginUser(user: string): Uint8Array {\n return encodeUtf8(`${encodeBase64(user).replace(/\\r\\n/g, \"\")}\\r\\n`);\n}\n\n/** Encode CRAM-MD5 response after challenge. */\nexport function encodeAuthCramResponse(response: string): Uint8Array {\n return encodeUtf8(`${response}\\r\\n`);\n}\n\n/** Encode raw SMTP line with CRLF. */\nexport function encodeLine(line: string): Uint8Array {\n return encodeUtf8(`${line}\\r\\n`);\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "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
|
|
8
|
+
"mappings": "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",
|
|
9
9
|
"debugId": "5BEE29900FA6AC3664756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"sources": ["../src/core/dkim.ts", "../src/core/mime.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * DKIM (DomainKeys Identified Mail) signing per RFC 6376.\n * Supports RSA-SHA256 and Ed25519-SHA256 using Web Crypto.\n * Ed25519 requires Node.js ≥ 18.4, Bun ≥ 1.0, or Cloudflare Workers.\n *\n * @example\n * ```ts\n * import { signDKIM } from \"sently/core/dkim\";\n * const signed = await signDKIM(rawMessage, {\n * domainName: \"example.com\",\n * keySelector: \"2024\",\n * privateKey: \"-----BEGIN PRIVATE KEY-----\\\\n...\",\n * });\n * ```\n */\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\nimport type { DKIMConfig } from \"./types.js\";\n\nconst CRLF = \"\\r\\n\";\nconst DEFAULT_HEADER_FIELDS = \"from:to:subject:date:message-id:mime-version:content-type\";\n\n/** Result of DKIM signing — the header line to prepend. */\nexport interface DKIMSignResult {\n /** Complete DKIM-Signature header line (without trailing CRLF). */\n header: string;\n}\n\n/**\n * Canonicalize headers using the relaxed algorithm (RFC 6376 §3.4.2).\n */\nexport function canonicalizeHeadersRelaxed(headers: string, fieldNames: string[]): string {\n const parsed = parseHeaders(headers);\n const lines: string[] = [];\n\n for (const name of fieldNames) {\n const key = name.toLowerCase().trim();\n const values = parsed.get(key);\n if (!values) {\n continue;\n }\n for (const value of values) {\n const unfolded = value.replace(/\\r?\\n/g, \"\").replace(/\\s+/g, \" \").trim();\n lines.push(`${key}:${unfolded}`);\n }\n }\n\n return lines.length > 0 ? `${lines.join(CRLF)}${CRLF}` : \"\";\n}\n\n/**\n * Canonicalize body using the relaxed algorithm (RFC 6376 §3.4.4).\n */\nexport function canonicalizeBodyRelaxed(body: string): string {\n const normalized = body.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const lines = normalized\n .split(\"\\n\")\n .map((line) => line.replace(/[ \\t]+$/g, \"\").replace(/[ \\t]+/g, \" \"))\n .filter((line) => line.length > 0);\n if (lines.length === 0) {\n return CRLF;\n }\n return `${lines.join(CRLF)}${CRLF}`;\n}\n\n/**\n * Import a PEM-encoded private key into a Web Crypto CryptoKey.\n */\nexport async function importPrivateKey(\n pem: string,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<CryptoKey> {\n if (algorithm === \"ed25519-sha256\" && /OPENSSH PRIVATE KEY/i.test(pem)) {\n throw new Error(\n \"Ed25519 keys must be in PKCS#8 PEM format (-----BEGIN PRIVATE KEY-----). Convert with: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key_pkcs8.pem\",\n );\n }\n\n const der = pemToDer(pem);\n\n const derBuffer = toArrayBuffer(der);\n\n try {\n if (algorithm === \"ed25519-sha256\") {\n return await crypto.subtle.importKey(\"pkcs8\", derBuffer, { name: \"Ed25519\" }, false, [\n \"sign\",\n ]);\n }\n return await crypto.subtle.importKey(\n \"pkcs8\",\n derBuffer,\n { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n } catch (err) {\n if (algorithm === \"ed25519-sha256\" && err instanceof DOMException) {\n throw new Error(\"Ed25519 DKIM requires Node.js ≥ 18.4, Bun ≥ 1.0, or Cloudflare Workers\", {\n cause: err,\n });\n }\n throw err;\n }\n}\n\n/**\n * Sign a raw MIME message with DKIM.\n */\nexport async function signDKIM(\n rawMessage: Uint8Array,\n config: DKIMConfig,\n): Promise<DKIMSignResult> {\n const algorithm = config.algorithm ?? \"rsa-sha256\";\n const fieldList = (config.headerFieldNames ?? DEFAULT_HEADER_FIELDS)\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean);\n const skip = new Set(\n (config.skipFields ?? \"\")\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean),\n );\n const signFields = fieldList.filter((f) => !skip.has(f));\n\n const text = new TextDecoder().decode(rawMessage);\n const sep = text.indexOf(\"\\r\\n\\r\\n\");\n const headerPart = sep >= 0 ? text.slice(0, sep) : text;\n const bodyPart = sep >= 0 ? text.slice(sep + 4) : \"\";\n\n const bodyCanonical = canonicalizeBodyRelaxed(bodyPart);\n const bodyHash = await sha256Base64(encodeUtf8(bodyCanonical));\n\n const dkimAlgo = algorithm === \"ed25519-sha256\" ? \"ed25519-sha256\" : \"rsa-sha256\";\n const headerList = signFields.join(\":\");\n const timestamp = Math.floor(Date.now() / 1000);\n\n const dkimWithoutSig = [\n `v=1`,\n `a=${dkimAlgo}`,\n `c=relaxed/relaxed`,\n `d=${config.domainName}`,\n `s=${config.keySelector}`,\n `h=${headerList}`,\n `bh=${bodyHash}`,\n `b=`,\n `t=${timestamp}`,\n ].join(\"; \");\n\n const dkimHeaderName = \"dkim-signature\";\n const dkimHeaderValue = dkimWithoutSig;\n const headersWithDkim = `${headerPart}${CRLF}${dkimHeaderName}:${dkimHeaderValue}`;\n const canonical = canonicalizeHeadersRelaxed(headersWithDkim, [...signFields, dkimHeaderName]);\n\n const key = await importPrivateKey(config.privateKey, algorithm);\n const data = encodeUtf8(canonical);\n const signature = await signData(key, data, algorithm);\n const bValue = encodeBase64(signature).replace(/\\r\\n/g, \"\");\n\n const header = `DKIM-Signature: v=1; a=${dkimAlgo}; c=relaxed/relaxed; d=${config.domainName}; s=${config.keySelector}; h=${headerList}; bh=${bodyHash}; b=${bValue}; t=${timestamp}`;\n\n return { header };\n}\n\nfunction parseHeaders(headerBlock: string): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const lines = headerBlock.split(/\\r?\\n/).filter((l) => l.length > 0);\n let currentName = \"\";\n let currentValue = \"\";\n\n const flush = (): void => {\n if (!currentName) {\n return;\n }\n const key = currentName.toLowerCase();\n const list = map.get(key) ?? [];\n list.push(currentValue);\n map.set(key, list);\n currentName = \"\";\n currentValue = \"\";\n };\n\n for (const line of lines) {\n if (/^[ \\t]/.test(line) && currentName) {\n currentValue += ` ${line.trim()}`;\n continue;\n }\n flush();\n const colon = line.indexOf(\":\");\n if (colon > 0) {\n currentName = line.slice(0, colon).trim();\n currentValue = line.slice(colon + 1).trim();\n }\n }\n flush();\n return map;\n}\n\nfunction pemToDer(pem: string): Uint8Array {\n const lines = pem\n .replace(/-----BEGIN[^-]+-----/g, \"\")\n .replace(/-----END[^-]+-----/g, \"\")\n .replace(/\\s/g, \"\");\n const binary = atob(lines);\n const der = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n der[i] = binary.charCodeAt(i);\n }\n return der;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nasync function sha256Base64(data: Uint8Array): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", toArrayBuffer(data));\n return encodeBase64(new Uint8Array(hash)).replace(/\\r\\n/g, \"\");\n}\n\nasync function signData(\n key: CryptoKey,\n data: Uint8Array,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<Uint8Array> {\n const buf = toArrayBuffer(data);\n if (algorithm === \"ed25519-sha256\") {\n const sig = await crypto.subtle.sign(\"Ed25519\", key, buf);\n return new Uint8Array(sig);\n }\n const sig = await crypto.subtle.sign({ name: \"RSASSA-PKCS1-v1_5\" }, key, buf);\n return new Uint8Array(sig);\n}\n",
|
|
6
|
-
"// src/core/mime.ts\nimport {\n assertSafeAddress,\n extractEmails,\n isValidEmail,\n parseAddresses,\n toMIMEHeader,\n} from \"./address.js\";\nimport { encodeBase64, encodeHeader, encodeUtf8 } from \"./base64.js\";\nimport { signDKIM } from \"./dkim.js\";\nimport type { Address, Attachment, DKIMConfig, Envelope, MailOptions } from \"./types.js\";\n\nfunction sanitizeHeaderValue(value: string): string {\n return value.replace(/\\r\\n?|\\n/g, \" \").trim();\n}\n\nfunction sanitizeAddress(addr: Address): Address {\n // Security check runs on the raw value FIRST and fails closed: any control\n // character (CR/LF/NUL/…) is rejected outright rather than stripped and\n // accepted. parseAddresses() already enforces this upstream; re-asserting\n // here keeps the function safe if it is ever called directly.\n assertSafeAddress(addr.address, \"address\");\n if (addr.name !== undefined) {\n assertSafeAddress(addr.name, \"display name\");\n }\n\n // Only ordinary surrounding whitespace is trimmed — the address identity is\n // never altered (no CR/LF-to-space repair).\n const address = addr.address.trim();\n if (!isValidEmail(address)) {\n throw new Error(`Invalid email address: ${JSON.stringify(addr.address)}`);\n }\n\n const sanitized: Address = { address };\n if (addr.name) {\n sanitized.name = sanitizeHeaderValue(addr.name);\n }\n return sanitized;\n}\n\n/** Result of building a complete MIME message. */\nexport interface MIMEBuildResult {\n raw: Uint8Array;\n envelope: Envelope;\n messageId: string;\n size: number;\n}\n\nconst CRLF = \"\\r\\n\";\n\n/**\n * Build a complete MIME message as a Uint8Array ready for SMTP DATA.\n * When `dkim` is provided, signs the message and prepends the DKIM-Signature header.\n */\nexport async function buildMIME(options: MailOptions, dkim?: DKIMConfig): Promise<MIMEBuildResult> {\n const messageId = options.messageId ?? generateMessageId();\n const date = (options.date ?? new Date()).toUTCString();\n const fromAddrs = parseAddresses(options.from);\n const toAddrs = parseAddresses(options.to);\n const ccAddrs = options.cc ? parseAddresses(options.cc) : [];\n\n if (fromAddrs.length === 0) {\n throw new Error(\"Missing from address\");\n }\n if (toAddrs.length === 0) {\n throw new Error(\"Missing to address\");\n }\n\n const envelope: Envelope = {\n from: fromAddrs[0]?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n };\n\n const attachments = options.attachments ?? [];\n const inlineAttachments = attachments.filter((a) => a.inline || a.contentId);\n const regularAttachments = attachments.filter((a) => !a.inline && !a.contentId);\n\n let root = buildSimpleBody(options);\n\n if (inlineAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/related; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(buildSimpleBody(options)),\n ...inlineAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n if (regularAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/mixed; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(root),\n ...regularAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n const headers: string[] = [\n foldHeader(\"From\", fromAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n foldHeader(\"To\", toAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n ];\n\n if (ccAddrs.length > 0) {\n headers.push(foldHeader(\"Cc\", ccAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")));\n }\n\n if (options.replyTo) {\n headers.push(\n foldHeader(\n \"Reply-To\",\n parseAddresses(options.replyTo)\n .map((a) => toMIMEHeader(sanitizeAddress(a)))\n .join(\", \"),\n ),\n );\n }\n\n headers.push(\n foldHeader(\"Subject\", encodeHeader(sanitizeHeaderValue(options.subject))),\n foldHeader(\"Date\", date),\n foldHeader(\"Message-ID\", messageId),\n \"MIME-Version: 1.0\",\n );\n\n if (options.priority === \"high\") {\n headers.push(\"X-Priority: 1\", \"Importance: high\");\n } else if (options.priority === \"low\") {\n headers.push(\"X-Priority: 5\", \"Importance: low\");\n }\n\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.push(foldHeader(sanitizeHeaderValue(key), sanitizeHeaderValue(value)));\n }\n }\n\n headers.push(`Content-Type: ${root.contentType}`);\n if (root.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${root.contentTransferEncoding}`);\n }\n\n const rawText = `${headers.join(CRLF)}${CRLF}${CRLF}${root.content}`;\n let raw = encodeUtf8(rawText);\n\n if (dkim) {\n const { header } = await signDKIM(raw, dkim);\n const signedText = `${header}${CRLF}${rawText}`;\n raw = encodeUtf8(signedText);\n }\n\n return { raw, envelope, messageId, size: raw.length };\n}\n\ninterface SimpleBody {\n contentType: string;\n contentTransferEncoding?: string;\n content: string;\n}\n\nfunction buildSimpleBody(options: MailOptions): SimpleBody {\n const hasText = Boolean(options.text);\n const hasHtml = Boolean(options.html);\n\n if (hasText && hasHtml) {\n const boundary = generateBoundary();\n return {\n contentType: `multipart/alternative; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatSimplePart({\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n }),\n formatSimplePart({\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n }),\n ]),\n };\n }\n\n if (hasHtml) {\n return {\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n };\n }\n\n return {\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n };\n}\n\nfunction formatSimplePart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatNestedPart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatAttachmentPart(attachment: Attachment): string {\n if (!attachment.content || typeof attachment.content === \"string\") {\n throw new Error(`Attachment \"${attachment.filename}\" requires Uint8Array content`);\n }\n\n const safeFilename = sanitizeHeaderValue(attachment.filename ?? \"\");\n const headers = [\n `Content-Type: ${attachment.contentType ?? \"application/octet-stream\"}`,\n \"Content-Transfer-Encoding: base64\",\n `Content-Disposition: ${attachment.inline ? \"inline\" : \"attachment\"}; filename=\"${safeFilename}\"`,\n ];\n\n if (attachment.contentId) {\n headers.push(`Content-ID: <${attachment.contentId}>`);\n }\n\n if (attachment.headers) {\n for (const [key, value] of Object.entries(attachment.headers)) {\n const safeKey = sanitizeHeaderValue(key);\n const safeValue = sanitizeHeaderValue(value);\n headers.push(`${safeKey}: ${safeValue}`);\n }\n }\n\n return `${headers.join(CRLF)}${CRLF}${CRLF}${encodeBase64(attachment.content)}`;\n}\n\nfunction assembleMultipart(boundary: string, parts: string[]): string {\n const segments = parts.map((part) => `--${boundary}${CRLF}${part}`);\n segments.push(`--${boundary}--`);\n return segments.join(CRLF);\n}\n\nfunction generateMessageId(): string {\n const random = crypto.getRandomValues(new Uint8Array(8));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `<${Date.now()}.${hex}@sently>`;\n}\n\nfunction generateBoundary(): string {\n const random = crypto.getRandomValues(new Uint8Array(12));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `----sently_${hex}`;\n}\n\nfunction foldHeader(name: string, value: string): string {\n const line = `${name}: ${value}`;\n if (line.length <= 76) {\n return line;\n }\n\n const chunks: string[] = [];\n let remaining = line;\n\n while (remaining.length > 76) {\n let breakAt = remaining.lastIndexOf(\" \", 76);\n if (breakAt <= name.length + 1) {\n breakAt = 76;\n }\n chunks.push(remaining.slice(0, breakAt));\n remaining = ` ${remaining.slice(breakAt).trimStart()}`;\n }\n chunks.push(remaining);\n\n return chunks.join(`${CRLF} `);\n}\n"
|
|
6
|
+
"// src/core/mime.ts\nimport {\n assertSafeAddress,\n extractEmails,\n isValidEmail,\n parseAddresses,\n toMIMEHeader,\n} from \"./address.js\";\nimport { encodeBase64, encodeHeader, encodeUtf8 } from \"./base64.js\";\nimport { signDKIM } from \"./dkim.js\";\nimport type { Address, Attachment, DKIMConfig, Envelope, MailOptions } from \"./types.js\";\n\nfunction sanitizeHeaderValue(value: string): string {\n return value.replace(/\\r\\n?|\\n/g, \" \").trim();\n}\n\nfunction sanitizeAddress(addr: Address): Address {\n // Security check runs on the raw value FIRST and fails closed: any control\n // character (CR/LF/NUL/…) is rejected outright rather than stripped and\n // accepted. parseAddresses() already enforces this upstream; re-asserting\n // here keeps the function safe if it is ever called directly.\n assertSafeAddress(addr.address, \"address\");\n if (addr.name !== undefined) {\n assertSafeAddress(addr.name, \"display name\");\n }\n\n // Only ordinary surrounding whitespace is trimmed — the address identity is\n // never altered (no CR/LF-to-space repair).\n const address = addr.address.trim();\n if (!isValidEmail(address)) {\n throw new Error(`Invalid email address: ${JSON.stringify(addr.address)}`);\n }\n\n const sanitized: Address = { address };\n if (addr.name) {\n sanitized.name = sanitizeHeaderValue(addr.name);\n }\n return sanitized;\n}\n\n/** Result of building a complete MIME message. */\nexport interface MIMEBuildResult {\n /** Complete raw MIME message bytes ready to send. */\n raw: Uint8Array;\n /** SMTP envelope derived from From/To/Cc/Bcc. */\n envelope: Envelope;\n /** Message-ID assigned or supplied for the message. */\n messageId: string;\n /** Size of the raw MIME message in bytes. */\n size: number;\n}\n\nconst CRLF = \"\\r\\n\";\n\n/**\n * Build a complete MIME message as a Uint8Array ready for SMTP DATA.\n * When `dkim` is provided, signs the message and prepends the DKIM-Signature header.\n */\nexport async function buildMIME(options: MailOptions, dkim?: DKIMConfig): Promise<MIMEBuildResult> {\n const messageId = options.messageId ?? generateMessageId();\n const date = (options.date ?? new Date()).toUTCString();\n const fromAddrs = parseAddresses(options.from);\n const toAddrs = parseAddresses(options.to);\n const ccAddrs = options.cc ? parseAddresses(options.cc) : [];\n\n if (fromAddrs.length === 0) {\n throw new Error(\"Missing from address\");\n }\n if (toAddrs.length === 0) {\n throw new Error(\"Missing to address\");\n }\n\n const envelope: Envelope = {\n from: fromAddrs[0]?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n };\n\n const attachments = options.attachments ?? [];\n const inlineAttachments = attachments.filter((a) => a.inline || a.contentId);\n const regularAttachments = attachments.filter((a) => !a.inline && !a.contentId);\n\n let root = buildSimpleBody(options);\n\n if (inlineAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/related; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(buildSimpleBody(options)),\n ...inlineAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n if (regularAttachments.length > 0) {\n const boundary = generateBoundary();\n root = {\n contentType: `multipart/mixed; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatNestedPart(root),\n ...regularAttachments.map(formatAttachmentPart),\n ]),\n };\n }\n\n const headers: string[] = [\n foldHeader(\"From\", fromAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n foldHeader(\"To\", toAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")),\n ];\n\n if (ccAddrs.length > 0) {\n headers.push(foldHeader(\"Cc\", ccAddrs.map((a) => toMIMEHeader(sanitizeAddress(a))).join(\", \")));\n }\n\n if (options.replyTo) {\n headers.push(\n foldHeader(\n \"Reply-To\",\n parseAddresses(options.replyTo)\n .map((a) => toMIMEHeader(sanitizeAddress(a)))\n .join(\", \"),\n ),\n );\n }\n\n headers.push(\n foldHeader(\"Subject\", encodeHeader(sanitizeHeaderValue(options.subject))),\n foldHeader(\"Date\", date),\n foldHeader(\"Message-ID\", messageId),\n \"MIME-Version: 1.0\",\n );\n\n if (options.priority === \"high\") {\n headers.push(\"X-Priority: 1\", \"Importance: high\");\n } else if (options.priority === \"low\") {\n headers.push(\"X-Priority: 5\", \"Importance: low\");\n }\n\n if (options.headers) {\n for (const [key, value] of Object.entries(options.headers)) {\n headers.push(foldHeader(sanitizeHeaderValue(key), sanitizeHeaderValue(value)));\n }\n }\n\n headers.push(`Content-Type: ${root.contentType}`);\n if (root.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${root.contentTransferEncoding}`);\n }\n\n const rawText = `${headers.join(CRLF)}${CRLF}${CRLF}${root.content}`;\n let raw = encodeUtf8(rawText);\n\n if (dkim) {\n const { header } = await signDKIM(raw, dkim);\n const signedText = `${header}${CRLF}${rawText}`;\n raw = encodeUtf8(signedText);\n }\n\n return { raw, envelope, messageId, size: raw.length };\n}\n\ninterface SimpleBody {\n contentType: string;\n contentTransferEncoding?: string;\n content: string;\n}\n\nfunction buildSimpleBody(options: MailOptions): SimpleBody {\n const hasText = Boolean(options.text);\n const hasHtml = Boolean(options.html);\n\n if (hasText && hasHtml) {\n const boundary = generateBoundary();\n return {\n contentType: `multipart/alternative; boundary=\"${boundary}\"`,\n content: assembleMultipart(boundary, [\n formatSimplePart({\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n }),\n formatSimplePart({\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n }),\n ]),\n };\n }\n\n if (hasHtml) {\n return {\n contentType: \"text/html; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.html ?? \"\",\n };\n }\n\n return {\n contentType: \"text/plain; charset=utf-8\",\n contentTransferEncoding: \"8bit\",\n content: options.text ?? \"\",\n };\n}\n\nfunction formatSimplePart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatNestedPart(part: SimpleBody): string {\n const headers = [`Content-Type: ${part.contentType}`];\n if (part.contentTransferEncoding) {\n headers.push(`Content-Transfer-Encoding: ${part.contentTransferEncoding}`);\n }\n return `${headers.join(CRLF)}${CRLF}${CRLF}${part.content}`;\n}\n\nfunction formatAttachmentPart(attachment: Attachment): string {\n if (!attachment.content || typeof attachment.content === \"string\") {\n throw new Error(`Attachment \"${attachment.filename}\" requires Uint8Array content`);\n }\n\n const safeFilename = sanitizeHeaderValue(attachment.filename ?? \"\");\n const headers = [\n `Content-Type: ${attachment.contentType ?? \"application/octet-stream\"}`,\n \"Content-Transfer-Encoding: base64\",\n `Content-Disposition: ${attachment.inline ? \"inline\" : \"attachment\"}; filename=\"${safeFilename}\"`,\n ];\n\n if (attachment.contentId) {\n headers.push(`Content-ID: <${attachment.contentId}>`);\n }\n\n if (attachment.headers) {\n for (const [key, value] of Object.entries(attachment.headers)) {\n const safeKey = sanitizeHeaderValue(key);\n const safeValue = sanitizeHeaderValue(value);\n headers.push(`${safeKey}: ${safeValue}`);\n }\n }\n\n return `${headers.join(CRLF)}${CRLF}${CRLF}${encodeBase64(attachment.content)}`;\n}\n\nfunction assembleMultipart(boundary: string, parts: string[]): string {\n const segments = parts.map((part) => `--${boundary}${CRLF}${part}`);\n segments.push(`--${boundary}--`);\n return segments.join(CRLF);\n}\n\nfunction generateMessageId(): string {\n const random = crypto.getRandomValues(new Uint8Array(8));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `<${Date.now()}.${hex}@sently>`;\n}\n\nfunction generateBoundary(): string {\n const random = crypto.getRandomValues(new Uint8Array(12));\n const hex = Array.from(random, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `----sently_${hex}`;\n}\n\nfunction foldHeader(name: string, value: string): string {\n const line = `${name}: ${value}`;\n if (line.length <= 76) {\n return line;\n }\n\n const chunks: string[] = [];\n let remaining = line;\n\n while (remaining.length > 76) {\n let breakAt = remaining.lastIndexOf(\" \", 76);\n if (breakAt <= name.length + 1) {\n breakAt = 76;\n }\n chunks.push(remaining.slice(0, breakAt));\n remaining = ` ${remaining.slice(breakAt).trimStart()}`;\n }\n chunks.push(remaining);\n\n return chunks.join(`${CRLF} `);\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "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",
|
|
8
|
+
"mappings": "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",
|
|
9
9
|
"debugId": "AA495EEF0CB2364A64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/auth/oauth2.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * OAuth2 / XOAUTH2 authentication for SMTP.\n * Supports Gmail, Microsoft 365, and custom OAuth2 providers.\n *\n * @example\n * ```ts\n * import { OAuth2Client } from \"sently/auth/oauth2\";\n * const client = new OAuth2Client({\n * user: \"me@gmail.com\",\n * clientId: \"...\",\n * clientSecret: \"...\",\n * refreshToken: \"...\",\n * });\n * const token = await client.getAccessToken();\n * ```\n */\nimport { encodeBase64, encodeUtf8 } from \"../core/base64.js\";\nimport type { OAuth2Config } from \"../core/types.js\";\n\n/** Default Google OAuth2 token endpoint. */\nexport const GOOGLE_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\n/** Microsoft OAuth2 token endpoint (common tenant). */\nexport const MICROSOFT_TOKEN_URL = \"https://login.microsoftonline.com/common/oauth2/v2.0/token\";\n\n/** OAuth2 token endpoint response shape. */\nexport interface TokenResponse {\n /** Bearer access token for API or SMTP XOAUTH2. */\n access_token: string;\n /** Token lifetime in seconds. */\n expires_in: number;\n /** Token type (typically `\"Bearer\"`). */\n token_type: string;\n}\n\nconst EXPIRY_BUFFER_MS = 30_000;\n\n/**\n * OAuth2 client with in-memory token cache and automatic refresh.\n */\nexport class OAuth2Client {\n private readonly config: OAuth2Config;\n private cachedToken: string | null = null;\n private expiresAt = 0;\n private refreshPromise: Promise<string> | null = null;\n\n /** Creates an OAuth2 client from configuration. */\n constructor(config: OAuth2Config) {\n this.config = config;\n if (config.accessToken) {\n this.cachedToken = config.accessToken;\n this.expiresAt = Date.now() + 3_600_000;\n }\n }\n\n /**\n * Get a valid access token (cached when still valid, refreshed otherwise).\n */\n async getAccessToken(): Promise<string> {\n if (this.config.getToken) {\n return this.config.getToken();\n }\n\n if (this.cachedToken && Date.now() < this.expiresAt - EXPIRY_BUFFER_MS) {\n return this.cachedToken;\n }\n\n if (!this.refreshPromise) {\n this.refreshPromise = this.refreshAccessToken().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n\n /**\n * Force-refresh the access token regardless of expiry.\n */\n async refreshAccessToken(): Promise<string> {\n if (this.config.getToken) {\n const token = await this.config.getToken();\n this.cachedToken = token;\n this.expiresAt = Date.now() + 3_600_000;\n return token;\n }\n\n const tokenUrl = this.config.tokenUrl ?? GOOGLE_TOKEN_URL;\n const body = new URLSearchParams({\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n refresh_token: this.config.refreshToken,\n grant_type: \"refresh_token\",\n });\n\n const response = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OAuth2 token refresh failed (${response.status}): ${text}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n this.cachedToken = data.access_token;\n this.expiresAt = Date.now() + data.expires_in * 1000;\n return data.access_token;\n }\n\n /**\n * Build the XOAUTH2 SASL string for SMTP AUTH (base64-encoded).\n */\n async buildXOAUTH2(): Promise<string> {\n const token = await this.getAccessToken();\n const raw = `user=${this.config.user}\\x01auth=Bearer ${token}\\x01\\x01`;\n return encodeBase64(encodeUtf8(raw)).replace(/\\r\\n/g, \"\");\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * OAuth2 / XOAUTH2 authentication for SMTP.\n * Supports Gmail, Microsoft 365, and custom OAuth2 providers.\n *\n * @example\n * ```ts\n * import { OAuth2Client } from \"sently/auth/oauth2\";\n * const client = new OAuth2Client({\n * user: \"me@gmail.com\",\n * clientId: \"...\",\n * clientSecret: \"...\",\n * refreshToken: \"...\",\n * });\n * const token = await client.getAccessToken();\n * ```\n */\nimport { encodeBase64, encodeUtf8 } from \"../core/base64.js\";\nimport type { OAuth2Config } from \"../core/types.js\";\n\n/** Default Google OAuth2 token endpoint. */\nexport const GOOGLE_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\n/** Microsoft OAuth2 token endpoint (common tenant). */\nexport const MICROSOFT_TOKEN_URL = \"https://login.microsoftonline.com/common/oauth2/v2.0/token\";\n\n/** OAuth2 token endpoint response shape. */\nexport interface TokenResponse {\n /** Bearer access token for API or SMTP XOAUTH2. */\n access_token: string;\n /** Token lifetime in seconds. */\n expires_in: number;\n /** Token type (typically `\"Bearer\"`). */\n token_type: string;\n}\n\nconst EXPIRY_BUFFER_MS = 30_000;\n\n/**\n * OAuth2 client with in-memory token cache and automatic refresh.\n */\nexport class OAuth2Client {\n /** OAuth2 configuration supplied at construction. */\n private readonly config: OAuth2Config;\n /** Cached access token, or null before the first fetch. */\n private cachedToken: string | null = null;\n /** Expiry timestamp (ms) for the cached access token. */\n private expiresAt = 0;\n /** In-flight refresh promise used to deduplicate concurrent refreshes. */\n private refreshPromise: Promise<string> | null = null;\n\n /** Creates an OAuth2 client from configuration. */\n constructor(config: OAuth2Config) {\n this.config = config;\n if (config.accessToken) {\n this.cachedToken = config.accessToken;\n this.expiresAt = Date.now() + 3_600_000;\n }\n }\n\n /**\n * Get a valid access token (cached when still valid, refreshed otherwise).\n */\n async getAccessToken(): Promise<string> {\n if (this.config.getToken) {\n return this.config.getToken();\n }\n\n if (this.cachedToken && Date.now() < this.expiresAt - EXPIRY_BUFFER_MS) {\n return this.cachedToken;\n }\n\n if (!this.refreshPromise) {\n this.refreshPromise = this.refreshAccessToken().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n\n /**\n * Force-refresh the access token regardless of expiry.\n */\n async refreshAccessToken(): Promise<string> {\n if (this.config.getToken) {\n const token = await this.config.getToken();\n this.cachedToken = token;\n this.expiresAt = Date.now() + 3_600_000;\n return token;\n }\n\n const tokenUrl = this.config.tokenUrl ?? GOOGLE_TOKEN_URL;\n const body = new URLSearchParams({\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n refresh_token: this.config.refreshToken,\n grant_type: \"refresh_token\",\n });\n\n const response = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OAuth2 token refresh failed (${response.status}): ${text}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n this.cachedToken = data.access_token;\n this.expiresAt = Date.now() + data.expires_in * 1000;\n return data.access_token;\n }\n\n /**\n * Build the XOAUTH2 SASL string for SMTP AUTH (base64-encoded).\n */\n async buildXOAUTH2(): Promise<string> {\n const token = await this.getAccessToken();\n const raw = `user=${this.config.user}\\x01auth=Bearer ${token}\\x01\\x01`;\n return encodeBase64(encodeUtf8(raw)).replace(/\\r\\n/g, \"\");\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;AAqBO,IAAM,mBAAmB;AAGzB,IAAM,sBAAsB;AAYnC,IAAM,mBAAmB;AAAA;AAKlB,MAAM,aAAa;AAAA,
|
|
7
|
+
"mappings": ";;;;;;AAqBO,IAAM,mBAAmB;AAGzB,IAAM,sBAAsB;AAYnC,IAAM,mBAAmB;AAAA;AAKlB,MAAM,aAAa;AAAA,EAEP;AAAA,EAET,cAA6B;AAAA,EAE7B,YAAY;AAAA,EAEZ,iBAAyC;AAAA,EAGjD,WAAW,CAAC,QAAsB;AAAA,IAChC,KAAK,SAAS;AAAA,IACd,IAAI,OAAO,aAAa;AAAA,MACtB,KAAK,cAAc,OAAO;AAAA,MAC1B,KAAK,YAAY,KAAK,IAAI,IAAI;AAAA,IAChC;AAAA;AAAA,OAMI,eAAc,GAAoB;AAAA,IACtC,IAAI,KAAK,OAAO,UAAU;AAAA,MACxB,OAAO,KAAK,OAAO,SAAS;AAAA,IAC9B;AAAA,IAEA,IAAI,KAAK,eAAe,KAAK,IAAI,IAAI,KAAK,YAAY,kBAAkB;AAAA,MACtE,OAAO,KAAK;AAAA,IACd;AAAA,IAEA,IAAI,CAAC,KAAK,gBAAgB;AAAA,MACxB,KAAK,iBAAiB,KAAK,mBAAmB,EAAE,QAAQ,MAAM;AAAA,QAC5D,KAAK,iBAAiB;AAAA,OACvB;AAAA,IACH;AAAA,IACA,OAAO,KAAK;AAAA;AAAA,OAMR,mBAAkB,GAAoB;AAAA,IAC1C,IAAI,KAAK,OAAO,UAAU;AAAA,MACxB,MAAM,QAAQ,MAAM,KAAK,OAAO,SAAS;AAAA,MACzC,KAAK,cAAc;AAAA,MACnB,KAAK,YAAY,KAAK,IAAI,IAAI;AAAA,MAC9B,OAAO;AAAA,IACT;AAAA,IAEA,MAAM,WAAW,KAAK,OAAO,YAAY;AAAA,IACzC,MAAM,OAAO,IAAI,gBAAgB;AAAA,MAC/B,WAAW,KAAK,OAAO;AAAA,MACvB,eAAe,KAAK,OAAO;AAAA,MAC3B,eAAe,KAAK,OAAO;AAAA,MAC3B,YAAY;AAAA,IACd,CAAC;AAAA,IAED,MAAM,WAAW,MAAM,MAAM,UAAU;AAAA,MACrC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,MAC/D,MAAM,KAAK,SAAS;AAAA,IACtB,CAAC;AAAA,IAED,IAAI,CAAC,SAAS,IAAI;AAAA,MAChB,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,MACjC,MAAM,IAAI,MAAM,gCAAgC,SAAS,YAAY,MAAM;AAAA,IAC7E;AAAA,IAEA,MAAM,OAAQ,MAAM,SAAS,KAAK;AAAA,IAClC,KAAK,cAAc,KAAK;AAAA,IACxB,KAAK,YAAY,KAAK,IAAI,IAAI,KAAK,aAAa;AAAA,IAChD,OAAO,KAAK;AAAA;AAAA,OAMR,aAAY,GAAoB;AAAA,IACpC,MAAM,QAAQ,MAAM,KAAK,eAAe;AAAA,IACxC,MAAM,MAAM,QAAQ,KAAK,OAAO,uBAAuB;AAAA,IACvD,OAAO,aAAa,WAAW,GAAG,CAAC,EAAE,QAAQ,SAAS,EAAE;AAAA;AAE5D;",
|
|
8
8
|
"debugId": "3609E75C0C54F13D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/core/mime.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { DKIMConfig, Envelope, MailOptions } from "./types.js";
|
|
2
2
|
/** Result of building a complete MIME message. */
|
|
3
3
|
export interface MIMEBuildResult {
|
|
4
|
+
/** Complete raw MIME message bytes ready to send. */
|
|
4
5
|
raw: Uint8Array;
|
|
6
|
+
/** SMTP envelope derived from From/To/Cc/Bcc. */
|
|
5
7
|
envelope: Envelope;
|
|
8
|
+
/** Message-ID assigned or supplied for the message. */
|
|
6
9
|
messageId: string;
|
|
10
|
+
/** Size of the raw MIME message in bytes. */
|
|
7
11
|
size: number;
|
|
8
12
|
}
|
|
9
13
|
/**
|
package/dist/core/sigv4.d.ts
CHANGED
|
@@ -18,18 +18,28 @@
|
|
|
18
18
|
*/
|
|
19
19
|
/** AWS credentials and signing scope for SigV4. */
|
|
20
20
|
export interface SigV4Credentials {
|
|
21
|
+
/** AWS access key ID. */
|
|
21
22
|
accessKeyId: string;
|
|
23
|
+
/** AWS secret access key. */
|
|
22
24
|
secretAccessKey: string;
|
|
25
|
+
/** AWS region (e.g. `us-east-1`). */
|
|
23
26
|
region: string;
|
|
27
|
+
/** AWS service name (e.g. `ses`, `s3`). */
|
|
24
28
|
service: string;
|
|
29
|
+
/** Optional STS session token for temporary credentials. */
|
|
25
30
|
sessionToken?: string;
|
|
26
31
|
}
|
|
27
32
|
/** HTTP request to sign with AWS Signature Version 4. */
|
|
28
33
|
export interface SigV4Request {
|
|
34
|
+
/** HTTP method (e.g. `POST`). */
|
|
29
35
|
method: string;
|
|
36
|
+
/** Full request URL including path and query. */
|
|
30
37
|
url: string;
|
|
38
|
+
/** Request headers to include in the signature. */
|
|
31
39
|
headers: Record<string, string>;
|
|
40
|
+
/** Request body as a string (empty for GET). */
|
|
32
41
|
body: string;
|
|
42
|
+
/** AWS credentials and signing scope. */
|
|
33
43
|
credentials: SigV4Credentials;
|
|
34
44
|
/** Override datetime for testing. Full 'YYYYMMDDTHHMMSSZ' when provided. */
|
|
35
45
|
_date?: string;
|
package/dist/core/smtp.d.ts
CHANGED
|
@@ -41,10 +41,15 @@ export type SMTPCommand = {
|
|
|
41
41
|
};
|
|
42
42
|
/** Parsed SMTP server response. */
|
|
43
43
|
export interface SMTPResponse {
|
|
44
|
+
/** Three-digit SMTP status code. */
|
|
44
45
|
code: number;
|
|
46
|
+
/** Human-readable response text after the status code. */
|
|
45
47
|
message: string;
|
|
48
|
+
/** True when the code is in the 2xx success range. */
|
|
46
49
|
isSuccess: boolean;
|
|
50
|
+
/** True when the code is 354 (ready for message data). */
|
|
47
51
|
isReady: boolean;
|
|
52
|
+
/** True when the code is 4xx or 5xx. */
|
|
48
53
|
isError: boolean;
|
|
49
54
|
}
|
|
50
55
|
/** SMTP protocol error with server response details. */
|
|
@@ -14,9 +14,13 @@ export interface PooledConnection {
|
|
|
14
14
|
}
|
|
15
15
|
/** Options for creating a pooled connection. */
|
|
16
16
|
export interface PooledConnectionOptions {
|
|
17
|
+
/** SMTP configuration for the pooled session. */
|
|
17
18
|
config: SMTPConfig;
|
|
19
|
+
/** Maximum messages before this connection is recycled. */
|
|
18
20
|
maxMessages: number;
|
|
21
|
+
/** Hostname to connect to (may differ from config.host for direct MX). */
|
|
19
22
|
connectHost: string;
|
|
23
|
+
/** Factory that creates the socket adapter for this connection. */
|
|
20
24
|
createAdapter: () => Promise<import("../core/types.js").SocketAdapter>;
|
|
21
25
|
}
|
|
22
26
|
/**
|
package/dist/pool/pool.d.ts
CHANGED
|
@@ -38,34 +38,50 @@ declare class RateLimiter {
|
|
|
38
38
|
private readonly rateDelta;
|
|
39
39
|
private readonly rateLimit;
|
|
40
40
|
private readonly now;
|
|
41
|
+
/** Remaining tokens in the current rate-limit window. */
|
|
41
42
|
private tokens;
|
|
43
|
+
/** Timestamp (ms) of the last token refill. */
|
|
42
44
|
private lastRefill;
|
|
45
|
+
/** Resolvers waiting for a token when the bucket is empty. */
|
|
43
46
|
private waiters;
|
|
47
|
+
/** Creates a rate limiter with the given burst size and window duration. */
|
|
44
48
|
constructor(rateDelta: number, rateLimit: number, now?: () => number);
|
|
45
49
|
/** Wait until a token is available, then consume one. */
|
|
46
50
|
acquire(): Promise<void>;
|
|
47
51
|
/** Wake waiters after the clock advances (for testing). */
|
|
48
52
|
notify(): void;
|
|
53
|
+
/** Refills tokens based on elapsed time and wakes waiting acquirers. */
|
|
49
54
|
private refill;
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* SMTP connection pool with optional rate limiting.
|
|
53
58
|
*/
|
|
54
59
|
export declare class SMTPPool implements Transport {
|
|
60
|
+
/** Resolved SMTP and pool configuration. */
|
|
55
61
|
private readonly config;
|
|
62
|
+
/** Maximum simultaneous pooled connections. */
|
|
56
63
|
private readonly maxConnections;
|
|
64
|
+
/** Maximum messages per connection before recycle. */
|
|
57
65
|
private readonly maxMessages;
|
|
66
|
+
/** Factory that creates a socket adapter for each new connection. */
|
|
58
67
|
private readonly createAdapterFn;
|
|
68
|
+
/** Optional token-bucket rate limiter, or null when disabled. */
|
|
59
69
|
private readonly rateLimiter;
|
|
70
|
+
/** Active pooled SMTP connections. */
|
|
60
71
|
private readonly connections;
|
|
72
|
+
/** Pending send operations waiting for a connection. */
|
|
61
73
|
private readonly queue;
|
|
74
|
+
/** True while {@link close} is draining; rejects new sends. */
|
|
62
75
|
private draining;
|
|
76
|
+
/** True after the pool has fully closed. */
|
|
63
77
|
private closed;
|
|
78
|
+
/** Serializes queue processing to avoid concurrent drain races. */
|
|
64
79
|
private processChain;
|
|
65
80
|
/** Creates an SMTP connection pool. */
|
|
66
81
|
constructor(config: SMTPConfig & PoolConfig, options?: SMTPPoolOptions);
|
|
67
82
|
/** Sends a message through the pool. */
|
|
68
83
|
send(options: MailOptions): Promise<SendResult>;
|
|
84
|
+
/** Schedules asynchronous processing of the send queue. */
|
|
69
85
|
private scheduleProcess;
|
|
70
86
|
/** Verifies connectivity using a temporary connection. */
|
|
71
87
|
verify(): Promise<VerifyResult>;
|
|
@@ -75,9 +91,13 @@ export declare class SMTPPool implements Transport {
|
|
|
75
91
|
get connectionCount(): number;
|
|
76
92
|
/** Number of messages waiting in the send queue. */
|
|
77
93
|
get queueSize(): number;
|
|
94
|
+
/** Dispatches queued messages to idle or newly spawned connections. */
|
|
78
95
|
private processQueue;
|
|
96
|
+
/** Opens a new authenticated pooled SMTP connection. */
|
|
79
97
|
private spawnConnection;
|
|
98
|
+
/** Removes a connection from the active pool list. */
|
|
80
99
|
private removeConnection;
|
|
100
|
+
/** Waits until the send queue and in-flight work are fully drained. */
|
|
81
101
|
private drainQueue;
|
|
82
102
|
}
|
|
83
103
|
/** @internal Exposed for deterministic rate limiter tests. */
|
|
@@ -10,6 +10,7 @@ export declare class BrevoError extends Error {
|
|
|
10
10
|
* Brevo HTTP API transport.
|
|
11
11
|
*/
|
|
12
12
|
export declare class BrevoTransport implements Transport {
|
|
13
|
+
/** Brevo API key used for Authorization. */
|
|
13
14
|
private readonly apiKey;
|
|
14
15
|
/** Creates a Brevo transport with the given API key. */
|
|
15
16
|
constructor(config: BrevoConfig);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/brevo.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends Error {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly code: string,\n ) {\n super(message);\n this.name = \"BrevoError\";\n }\n}\n\nfunction toAddressObjects(input: MailOptions[\"to\"]): Array<{ email: string; name?: string }> {\n return parseAddresses(input).map((addr) => ({\n email: addr.address,\n ...(addr.name ? { name: addr.name } : {}),\n }));\n}\n\n/**\n * Brevo HTTP API transport.\n */\nexport class BrevoTransport implements Transport {\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo 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: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: 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 })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(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 BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends Error {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly code: string,\n ) {\n super(message);\n this.name = \"BrevoError\";\n }\n}\n\nfunction toAddressObjects(input: MailOptions[\"to\"]): Array<{ email: string; name?: string }> {\n return parseAddresses(input).map((addr) => ({\n email: addr.address,\n ...(addr.name ? { name: addr.name } : {}),\n }));\n}\n\n/**\n * Brevo HTTP API transport.\n */\nexport class BrevoTransport implements Transport {\n /** Brevo API key used for Authorization. */\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo 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: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: 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 })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(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 BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;AA0BO,MAAM,mBAAmB,MAAM;AAAA,EAIlB;AAAA,EACA;AAAA,EAHlB,WAAW,CACT,SACgB,YACA,MAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAHG;AAAA,IACA;AAAA,IAGhB,KAAK,OAAO;AAAA;AAEhB;AAEA,SAAS,gBAAgB,CAAC,OAAmE;AAAA,EAC3F,OAAO,eAAe,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,IAC1C,OAAO,KAAK;AAAA,OACR,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,EACzC,EAAE;AAAA;AAAA;AAMG,MAAM,eAAoC;AAAA,
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "C5824397C8CE442364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -10,8 +10,11 @@ export declare class MailgunError extends Error {
|
|
|
10
10
|
* Mailgun HTTP API transport (multipart/form-data).
|
|
11
11
|
*/
|
|
12
12
|
export declare class MailgunTransport implements Transport {
|
|
13
|
+
/** Mailgun API key for basic authentication. */
|
|
13
14
|
private readonly apiKey;
|
|
15
|
+
/** Mailgun sending domain. */
|
|
14
16
|
private readonly domain;
|
|
17
|
+
/** Mailgun API base URL (US or EU region). */
|
|
15
18
|
private readonly baseUrl;
|
|
16
19
|
/** Creates a Mailgun transport with the given API key and domain. */
|
|
17
20
|
constructor(config: MailgunConfig);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/mailgun.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Mailgun HTTP transport for sently.\n * Uses the Mailgun Messages API v3 with multipart/form-data.\n *\n * @example\n * ```ts\n * import { MailgunTransport } from \"sently/transports/mailgun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new MailgunTransport({\n * apiKey: \"key-...\",\n * domain: \"mg.example.com\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n MailgunConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Mailgun API returns a non-success response. */\nexport class MailgunError extends Error {\n /** Creates a Mailgun 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 = \"MailgunError\";\n }\n}\n\n/**\n * Mailgun HTTP API transport (multipart/form-data).\n */\nexport class MailgunTransport implements Transport {\n private readonly apiKey: string;\n private readonly domain: string;\n private readonly baseUrl: string;\n\n /** Creates a Mailgun transport with the given API key and domain. */\n constructor(config: MailgunConfig) {\n this.apiKey = config.apiKey;\n this.domain = config.domain;\n this.baseUrl =\n config.region === \"eu\" ? \"https://api.eu.mailgun.net\" : \"https://api.mailgun.net\";\n }\n\n /** Sends an email via the Mailgun Messages API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const form = new FormData();\n\n form.append(\"from\", from ? toMIMEHeader(from) : \"\");\n form.append(\"to\", parseAddresses(options.to).map(toMIMEHeader).join(\", \"));\n\n if (options.cc) {\n form.append(\"cc\", parseAddresses(options.cc).map(toMIMEHeader).join(\", \"));\n }\n if (options.bcc) {\n form.append(\"bcc\", parseAddresses(options.bcc).map(toMIMEHeader).join(\", \"));\n }\n\n form.append(\"subject\", options.subject);\n\n if (options.text) {\n form.append(\"text\", options.text);\n }\n if (options.html) {\n form.append(\"html\", options.html);\n }\n if (options.replyTo) {\n const replyTo = parseAddresses(options.replyTo)[0];\n if (replyTo) {\n form.append(\"h:Reply-To\", toMIMEHeader(replyTo));\n }\n }\n\n for (const attachment of attachments) {\n const content =\n attachment.content instanceof Uint8Array\n ? attachment.content\n : new TextEncoder().encode(String(attachment.content ?? \"\"));\n const blob = new Blob([new Uint8Array(content)], {\n type: attachment.contentType ?? \"application/octet-stream\",\n });\n form.append(\"attachment\", blob, attachment.filename);\n }\n\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${auth}`,\n },\n body: form,\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new MailgunError(payload.message ?? \"Mailgun API error\", response.status, payload);\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.id ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.message ?? \"queued\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Mailgun API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/domains`, {\n headers: {\n Authorization: `Basic ${auth}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"mailgun\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"mailgun\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"mailgun\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Mailgun HTTP transport for sently.\n * Uses the Mailgun Messages API v3 with multipart/form-data.\n *\n * @example\n * ```ts\n * import { MailgunTransport } from \"sently/transports/mailgun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new MailgunTransport({\n * apiKey: \"key-...\",\n * domain: \"mg.example.com\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n MailgunConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Mailgun API returns a non-success response. */\nexport class MailgunError extends Error {\n /** Creates a Mailgun 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 = \"MailgunError\";\n }\n}\n\n/**\n * Mailgun HTTP API transport (multipart/form-data).\n */\nexport class MailgunTransport implements Transport {\n /** Mailgun API key for basic authentication. */\n private readonly apiKey: string;\n /** Mailgun sending domain. */\n private readonly domain: string;\n /** Mailgun API base URL (US or EU region). */\n private readonly baseUrl: string;\n\n /** Creates a Mailgun transport with the given API key and domain. */\n constructor(config: MailgunConfig) {\n this.apiKey = config.apiKey;\n this.domain = config.domain;\n this.baseUrl =\n config.region === \"eu\" ? \"https://api.eu.mailgun.net\" : \"https://api.mailgun.net\";\n }\n\n /** Sends an email via the Mailgun Messages API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const form = new FormData();\n\n form.append(\"from\", from ? toMIMEHeader(from) : \"\");\n form.append(\"to\", parseAddresses(options.to).map(toMIMEHeader).join(\", \"));\n\n if (options.cc) {\n form.append(\"cc\", parseAddresses(options.cc).map(toMIMEHeader).join(\", \"));\n }\n if (options.bcc) {\n form.append(\"bcc\", parseAddresses(options.bcc).map(toMIMEHeader).join(\", \"));\n }\n\n form.append(\"subject\", options.subject);\n\n if (options.text) {\n form.append(\"text\", options.text);\n }\n if (options.html) {\n form.append(\"html\", options.html);\n }\n if (options.replyTo) {\n const replyTo = parseAddresses(options.replyTo)[0];\n if (replyTo) {\n form.append(\"h:Reply-To\", toMIMEHeader(replyTo));\n }\n }\n\n for (const attachment of attachments) {\n const content =\n attachment.content instanceof Uint8Array\n ? attachment.content\n : new TextEncoder().encode(String(attachment.content ?? \"\"));\n const blob = new Blob([new Uint8Array(content)], {\n type: attachment.contentType ?? \"application/octet-stream\",\n });\n form.append(\"attachment\", blob, attachment.filename);\n }\n\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${auth}`,\n },\n body: form,\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new MailgunError(payload.message ?? \"Mailgun API error\", response.status, payload);\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.id ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.message ?? \"queued\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Mailgun API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/domains`, {\n headers: {\n Authorization: `Basic ${auth}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"mailgun\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"mailgun\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"mailgun\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;AA8BO,MAAM,qBAAqB,MAAM;AAAA,EAIpB;AAAA,EACA;AAAA,EAHlB,WAAW,CACT,SACgB,YACA,UAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAHG;AAAA,IACA;AAAA,IAGhB,KAAK,OAAO;AAAA;AAEhB;AAAA;AAKO,MAAM,iBAAsC;AAAA,
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "0BB5E6A11ACE98B264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -15,6 +15,7 @@ export declare class PostmarkError extends Error {
|
|
|
15
15
|
* Postmark HTTP API transport.
|
|
16
16
|
*/
|
|
17
17
|
export declare class PostmarkTransport implements Transport {
|
|
18
|
+
/** Postmark server API token for the X-Postmark-Server-Token header. */
|
|
18
19
|
private readonly serverToken;
|
|
19
20
|
/** Creates a Postmark transport with the given server token. */
|
|
20
21
|
constructor(config: PostmarkConfig);
|
|
@@ -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 /** 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"
|
|
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 /** Postmark server API token for the X-Postmark-Server-Token header. */\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": ";;;;;;;;;;;;AAiCO,MAAM,sBAAsB,MAAM;AAAA,EAIrB;AAAA,EACA;AAAA,EAHlB,WAAW,CACT,SACgB,YACA,UAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAHG;AAAA,IACA;AAAA,IAGhB,KAAK,OAAO;AAAA;AAEhB;AAAA;AAKO,MAAM,kBAAuC;AAAA,
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "650BF60942E0759C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -3,8 +3,11 @@ import type { MailOptions, PreviewConfig, SendResult, Transport, VerifyResult }
|
|
|
3
3
|
* Development transport that writes emails to disk instead of sending them.
|
|
4
4
|
*/
|
|
5
5
|
export declare class PreviewTransport implements Transport {
|
|
6
|
+
/** Directory where preview `.eml` or `.html` files are written. */
|
|
6
7
|
private readonly outDir;
|
|
8
|
+
/** Whether to open the written file in the default browser. */
|
|
7
9
|
private readonly open;
|
|
10
|
+
/** Output format: full MIME (`.eml`) or HTML body only. */
|
|
8
11
|
private readonly format;
|
|
9
12
|
/** Creates a preview transport with optional output directory and format. */
|
|
10
13
|
constructor(config?: PreviewConfig);
|