sently 0.5.2 → 0.6.1

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +61 -30
  3. package/dist/adapters/bun.js +1 -1
  4. package/dist/adapters/cf.js +2 -2
  5. package/dist/adapters/cf.js.map +1 -1
  6. package/dist/adapters/deno.js +1 -1
  7. package/dist/adapters/node.js +1 -1
  8. package/dist/auth/oauth2.js +1 -1
  9. package/dist/{chunk-zkczgj2c.js → chunk-348wndn3.js} +2 -2
  10. package/dist/{chunk-zkczgj2c.js.map → chunk-348wndn3.js.map} +1 -1
  11. package/dist/chunk-3y1tr1vt.js +4 -0
  12. package/dist/chunk-3y1tr1vt.js.map +11 -0
  13. package/dist/chunk-n601jhpd.js +4 -0
  14. package/dist/chunk-n601jhpd.js.map +10 -0
  15. package/dist/{chunk-c8ya9em0.js → chunk-qnz9z0d4.js} +2 -2
  16. package/dist/{chunk-c8ya9em0.js.map → chunk-qnz9z0d4.js.map} +1 -1
  17. package/dist/{chunk-mg8m26nr.js → chunk-rszb1n1d.js} +2 -2
  18. package/dist/{chunk-mg8m26nr.js.map → chunk-rszb1n1d.js.map} +1 -1
  19. package/dist/chunk-t0en64h9.js +4 -0
  20. package/dist/chunk-t0en64h9.js.map +10 -0
  21. package/dist/{chunk-vws105eb.js → chunk-tfxje74k.js} +2 -2
  22. package/dist/{chunk-vws105eb.js.map → chunk-tfxje74k.js.map} +1 -1
  23. package/dist/chunk-w3fnjh04.js +13 -0
  24. package/dist/chunk-w3fnjh04.js.map +11 -0
  25. package/dist/{chunk-gxq2aamv.js → chunk-wzdcz88x.js} +2 -2
  26. package/dist/{chunk-gxq2aamv.js.map → chunk-wzdcz88x.js.map} +1 -1
  27. package/dist/{chunk-b0qkndv9.js → chunk-y4qxdyqn.js} +3 -3
  28. package/dist/{chunk-b0qkndv9.js.map → chunk-y4qxdyqn.js.map} +1 -1
  29. package/dist/{chunk-aeqxkeh3.js → chunk-y72qggqf.js} +2 -2
  30. package/dist/{chunk-aeqxkeh3.js.map → chunk-y72qggqf.js.map} +1 -1
  31. package/dist/{chunk-k0gw3qmb.js → chunk-z7j2n9k8.js} +2 -2
  32. package/dist/{chunk-k0gw3qmb.js.map → chunk-z7j2n9k8.js.map} +1 -1
  33. package/dist/core/errors.d.ts +55 -0
  34. package/dist/core/errors.js +3 -0
  35. package/dist/core/errors.js.map +9 -0
  36. package/dist/core/smtp.d.ts +3 -3
  37. package/dist/core/smtp.js +2 -2
  38. package/dist/core/smtp.js.map +1 -1
  39. package/dist/core/types.d.ts +28 -1
  40. package/dist/detect.js +2 -2
  41. package/dist/detect.js.map +3 -3
  42. package/dist/dkim.js +2 -2
  43. package/dist/dkim.js.map +1 -1
  44. package/dist/errors.d.ts +29 -0
  45. package/dist/errors.js +8 -0
  46. package/dist/errors.js.map +9 -0
  47. package/dist/idempotency.js +2 -2
  48. package/dist/idempotency.js.map +1 -1
  49. package/dist/index.d.ts +9 -1
  50. package/dist/index.js +9 -0
  51. package/dist/mailer.d.ts +4 -3
  52. package/dist/mailer.js +2 -2
  53. package/dist/mailer.js.map +1 -1
  54. package/dist/plugins/react.js +2 -2
  55. package/dist/plugins/react.js.map +1 -1
  56. package/dist/plugins/template.js +1 -1
  57. package/dist/pool/pool.js +2 -2
  58. package/dist/pool/pool.js.map +2 -2
  59. package/dist/transports/brevo.d.ts +3 -3
  60. package/dist/transports/brevo.js +2 -2
  61. package/dist/transports/brevo.js.map +3 -3
  62. package/dist/transports/mailgun.d.ts +2 -1
  63. package/dist/transports/mailgun.js +2 -2
  64. package/dist/transports/mailgun.js.map +3 -3
  65. package/dist/transports/postmark.d.ts +2 -1
  66. package/dist/transports/postmark.js +2 -2
  67. package/dist/transports/postmark.js.map +3 -3
  68. package/dist/transports/preview.js +2 -2
  69. package/dist/transports/preview.js.map +1 -1
  70. package/dist/transports/resend.d.ts +2 -1
  71. package/dist/transports/resend.js +2 -2
  72. package/dist/transports/resend.js.map +3 -3
  73. package/dist/transports/retry.d.ts +9 -0
  74. package/dist/transports/retry.js +2 -2
  75. package/dist/transports/retry.js.map +3 -4
  76. package/dist/transports/sendgrid.d.ts +2 -1
  77. package/dist/transports/sendgrid.js +2 -2
  78. package/dist/transports/sendgrid.js.map +3 -3
  79. package/dist/transports/ses.d.ts +3 -3
  80. package/dist/transports/ses.js +5 -5
  81. package/dist/transports/ses.js.map +3 -3
  82. package/dist/transports/smtp.js +2 -2
  83. package/dist/transports/smtp.js.map +1 -1
  84. package/dist/webhooks/brevo.js +1 -1
  85. package/dist/webhooks/mailgun.js +1 -1
  86. package/dist/webhooks/postmark.js +1 -1
  87. package/dist/webhooks/resend.js +1 -1
  88. package/dist/webhooks/sendgrid.js +1 -1
  89. package/dist/webhooks/ses.js +1 -1
  90. package/dist/webhooks/timing-safe-equal.js +1 -1
  91. package/package.json +5 -1
  92. package/dist/chunk-9ea4hx7m.js +0 -4
  93. package/dist/chunk-9ea4hx7m.js.map +0 -11
  94. package/dist/chunk-9p4vev6j.js +0 -13
  95. package/dist/chunk-9p4vev6j.js.map +0 -11
@@ -0,0 +1,13 @@
1
+ import{O,P as K,R as H}from"./chunk-wzdcz88x.js";import{S as v,U as B}from"./chunk-n601jhpd.js";var j=64;function X(q){return q>>>0}function U(q){let z=g(q),G=1732584193,Q=4023233417,N=2562383102,J=271733878;for(let F=0;F<z.length;F+=64){let $=z.subarray(F,F+64),w=new Uint32Array(16);for(let W=0;W<16;W++){let A=W*4;w[W]=X(($[A]??0)|($[A+1]??0)<<8|($[A+2]??0)<<16|($[A+3]??0)<<24)}let x=G,D=Q,I=N,R=J;for(let W=0;W<64;W++){let A,_;if(W<16)A=X(D&I|~D&R),_=W;else if(W<32)A=X(D&R|I&~R),_=X((5*W+1)%16);else if(W<48)A=X(D^I^R),_=X((3*W+5)%16);else A=X(I^(D|~R)),_=X(7*W%16);let y=R;R=I,I=D,D=X(D+T(X(x+A+X((P[W]??0)+(w[_]??0))),L[W]??0)),x=y}G=X(G+x),Q=X(Q+D),N=X(N+I),J=X(J+R)}let Y=new Uint8Array(16),V=new DataView(Y.buffer);return V.setUint32(0,G,!0),V.setUint32(4,Q,!0),V.setUint32(8,N,!0),V.setUint32(12,J,!0),Y}var L=[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,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,10,15,21,6,10,15,21,6,10,15,21],P=new Uint32Array([3614090360,3905402710,606105819,3250441966,4118548399,1200080426,2821735955,4249261313,1770035416,2336552879,4294925233,2304563134,1804603682,4254626195,2792965006,1236535329,4129170786,3225465664,643717713,3921069994,3593408605,38016083,3634488961,3889429448,568446438,3275163606,4107603335,1163531501,2850285829,4243563512,1735328473,2368359562,4294588738,2272392833,1839030562,4259657740,2763975236,1272893353,4139469664,3200236656,681279174,3936430074,3572445317,76029189,3654602809,3873151461,530742520,3299628645,4096336452,1126891415,2878612391,4237533241,1700485571,2399980690,4293915773,2240044497,1873313359,4264355552,2734768916,1309151649,4149444226,3174756917,718787259,3951481745]);function T(q,z){return X(q<<z|q>>>32-z)}function g(q){let z=q.length*8,G=(56-(q.length+1)%64+64)%64,Q=q.length+1+G+8,N=new Uint8Array(Q);N.set(q),N[q.length]=128;let J=new DataView(N.buffer);return J.setUint32(Q-8,z>>>0,!0),J.setUint32(Q-4,Math.floor(z/4294967296),!0),N}function C(q,z){let G=q;if(G.length>j)G=U(G);let Q=new Uint8Array(j);Q.set(G);let N=new Uint8Array(j),J=new Uint8Array(j);for(let $=0;$<j;$++)N[$]=(Q[$]??0)^54,J[$]=(Q[$]??0)^92;let Y=new Uint8Array(N.length+z.length);Y.set(N),Y.set(z,N.length);let V=U(Y),F=new Uint8Array(J.length+V.length);return F.set(J),F.set(V,J.length),U(F)}function E(q){return Array.from(q,(z)=>z.toString(16).padStart(2,"0")).join("")}async function M(q,z,G){let Q=K(q.trim()),N=H(G),J=C(N,Q),Y=E(J);return O(`${z} ${Y}`).replace(/\r\n/g,"")}class Z extends v{command;response;constructor(q,z,G,Q){super(q,B(z,G),{provider:"smtp"});this.command=G;this.response=Q;this.name="SMTPError",this.code=z}}function u(q){let z;switch(q.type){case"EHLO":{z=`EHLO ${q.domain.replace(/[\r\n]/g,"")}`;break}case"STARTTLS":z="STARTTLS";break;case"AUTH_LOGIN":z="AUTH LOGIN";break;case"AUTH_PLAIN":z=`AUTH PLAIN ${O(`\x00${q.user}\x00${q.pass}`).replace(/\r\n/g,"")}`;break;case"AUTH_CRAM_MD5_INIT":z="AUTH CRAM-MD5";break;case"AUTH_CRAM_MD5_RESPONSE":return H(`${q.response}\r
2
+ `);case"AUTH_XOAUTH2":z=`AUTH XOAUTH2 ${q.xoauth2String}`;break;case"MAIL_FROM":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"MAIL FROM",q.address);z=`MAIL FROM:<${q.address}>`;break;case"RCPT_TO":if(/[\r\n]/.test(q.address))throw new Z("Invalid address: contains CRLF",0,"RCPT TO",q.address);z=`RCPT TO:<${q.address}>`;break;case"DATA":z="DATA";break;case"DATA_BODY":return H(S(q.content));case"QUIT":z="QUIT";break;case"RSET":z="RSET";break;case"NOOP":z="NOOP";break}return H(`${z}\r
3
+ `)}function l(q){let z=new TextDecoder().decode(q).trim(),G=z.split(/\r?\n/),N=(G[G.length-1]??"").match(/^(\d{3})([\s-])(.*)$/);if(!N)throw new Z("Invalid SMTP response",0,"PARSE",z);let J=Number.parseInt(N[1]??"0",10),Y=G.map((V)=>V.replace(/^\d{3}[\s-]/,"")).join(" ");return{code:J,message:Y,isSuccess:J>=200&&J<300,isReady:J>=300&&J<400,isError:J>=400}}function p(q){if(q.length===0)return null;let z=q.reduce((V,F)=>V+F.length,0),G=new Uint8Array(z),Q=0;for(let V of q)G.set(V,Q),Q+=V.length;let J=new TextDecoder().decode(G).split(/\r?\n/).filter((V)=>V.length>0);if(J.length===0)return null;let Y=J[J.length-1]??"";if(/^\d{3} /.test(Y))return G;return null}function m(q){let z=q.map((G)=>G.toUpperCase());if(z.some((G)=>G.includes("AUTH")&&G.includes("XOAUTH2")))return"OAUTH2";if(z.some((G)=>G.includes("AUTH")&&G.includes("CRAM-MD5")))return"CRAM-MD5";if(z.some((G)=>G.includes("AUTH")&&G.includes("LOGIN")))return"LOGIN";if(z.some((G)=>G.includes("AUTH")&&G.includes("PLAIN")))return"PLAIN";throw new Z("No supported AUTH method",0,"EHLO",q.join(" "))}function i(q){return q.message.split(/\s+/).flatMap((z)=>z.split(/\r?\n/)).filter(Boolean)}function r(q,z,G){if(!z.includes(q.code))throw new Z(`Unexpected SMTP response for ${G}`,q.code,G,q.message)}function S(q){return`${new TextDecoder().decode(q).split(/\r?\n/).map((N)=>N.startsWith(".")?`.${N}`:N).join(`\r
4
+ `)}\r
5
+ .\r
6
+ `}function t(q){return H(`${O(q).replace(/\r\n/g,"")}\r
7
+ `)}function o(q){return H(`${O(q).replace(/\r\n/g,"")}\r
8
+ `)}function n(q){return H(`${q}\r
9
+ `)}function s(q){return H(`${q}\r
10
+ `)}
11
+ export{M as C,Z as D,u as E,l as F,p as G,m as H,i as I,r as J,t as K,o as L,n as M,s as N};
12
+
13
+ //# debugId=387F8E628B7D3D7064756E2164756E21
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/cram-md5.ts", "../src/core/smtp.ts"],
4
+ "sourcesContent": [
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\";\nimport { SentlyError, smtpCodeToSentlyCode } from \"./errors.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 SentlyError {\n /** Creates an SMTP protocol error. */\n constructor(\n message: string,\n smtpCode: number,\n public readonly command: string,\n public readonly response: string,\n ) {\n super(message, smtpCodeToSentlyCode(smtpCode, command), { provider: \"smtp\" });\n this.name = \"SMTPError\";\n this.code = smtpCode;\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
+ ],
8
+ "mappings": "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",
9
+ "debugId": "387F8E628B7D3D7064756E2164756E21",
10
+ "names": []
11
+ }
@@ -1,5 +1,5 @@
1
1
  var J=new TextEncoder;function P(k){let f=typeof k==="string"?J.encode(k):k,j="",A=0;while(A<f.length){let m=f[A]??0,q=f[A+1],z=f[A+2];if(q===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4],j+="==";break}if(z===void 0){j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2],j+="=";break}j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[m>>2],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(m&3)<<4|q>>4],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(q&15)<<2|z>>6],j+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[z&63],A+=3}return R(j)}function S(k){let f=k.replace(/\s/g,""),j=f.length;if(j===0)return new Uint8Array(0);if(j%4!==0)throw Error("Invalid base64 string length");let A=f.endsWith("==")?2:f.endsWith("=")?1:0,m=j*3/4-A,q=new Uint8Array(m),z=0;for(let D=0;D<j;D+=4){let K=G(f[D]??"="),M=G(f[D+1]??"="),N=G(f[D+2]??"="),O=G(f[D+3]??"="),H=K<<18|M<<12|N<<6|O;if(z<m)q[z++]=H>>16&255;if(z<m)q[z++]=H>>8&255;if(z<m)q[z++]=H&255}return q}function W(k){if(!Q(k))return k;return`=?UTF-8?B?${P(k).replace(/\r\n/g,"")}?=`}function Q(k){for(let f=0;f<k.length;f++)if(k.charCodeAt(f)>127)return!0;return!1}function G(k){if(k==="=")return 0;let f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(k);if(f===-1)throw Error(`Invalid base64 character: ${k}`);return f}function R(k){let f=[];for(let j=0;j<k.length;j+=76)f.push(k.slice(j,j+76));return f.join(`\r
2
2
  `)}function X(k){return J.encode(k)}
3
- export{P as N,S as O,W as P,X as Q};
3
+ export{P as O,S as P,W as Q,X as R};
4
4
 
5
- //# debugId=678FAACF05287D3164756E2164756E21
5
+ //# debugId=8870A4553A4DBAC364756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "// src/core/base64.ts\n\nconst BASE64_CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\nconst BASE64_LINE_LENGTH = 76;\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Encode a Uint8Array or string to Base64.\n * Uses TextEncoder + manual base64 to support binary data correctly.\n */\nexport function encodeBase64(data: Uint8Array | string): string {\n const bytes = typeof data === \"string\" ? encoder.encode(data) : data;\n let result = \"\";\n let i = 0;\n\n while (i < bytes.length) {\n const b0 = bytes[i] ?? 0;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n\n if (b1 === undefined) {\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[(b0 & 0x03) << 4];\n result += \"==\";\n break;\n }\n\n if (b2 === undefined) {\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[((b0 & 0x03) << 4) | (b1 >> 4)];\n result += BASE64_CHARS[(b1 & 0x0f) << 2];\n result += \"=\";\n break;\n }\n\n result += BASE64_CHARS[b0 >> 2];\n result += BASE64_CHARS[((b0 & 0x03) << 4) | (b1 >> 4)];\n result += BASE64_CHARS[((b1 & 0x0f) << 2) | (b2 >> 6)];\n result += BASE64_CHARS[b2 & 0x3f];\n i += 3;\n }\n\n return wrapBase64Lines(result);\n}\n\n/**\n * Decode a Base64 string to Uint8Array.\n */\nexport function decodeBase64(data: string): Uint8Array {\n const cleaned = data.replace(/\\s/g, \"\");\n const len = cleaned.length;\n\n if (len === 0) {\n return new Uint8Array(0);\n }\n\n if (len % 4 !== 0) {\n throw new Error(\"Invalid base64 string length\");\n }\n\n const padding = cleaned.endsWith(\"==\") ? 2 : cleaned.endsWith(\"=\") ? 1 : 0;\n const outputLen = (len * 3) / 4 - padding;\n const output = new Uint8Array(outputLen);\n\n let outIndex = 0;\n for (let i = 0; i < len; i += 4) {\n const c0 = base64CharToValue(cleaned[i] ?? \"=\");\n const c1 = base64CharToValue(cleaned[i + 1] ?? \"=\");\n const c2 = base64CharToValue(cleaned[i + 2] ?? \"=\");\n const c3 = base64CharToValue(cleaned[i + 3] ?? \"=\");\n const triple = (c0 << 18) | (c1 << 12) | (c2 << 6) | c3;\n\n if (outIndex < outputLen) {\n output[outIndex++] = (triple >> 16) & 0xff;\n }\n if (outIndex < outputLen) {\n output[outIndex++] = (triple >> 8) & 0xff;\n }\n if (outIndex < outputLen) {\n output[outIndex++] = triple & 0xff;\n }\n }\n\n return output;\n}\n\n/**\n * Encode text using Quoted-Printable (RFC 2045).\n */\nexport function encodeQP(text: string): string {\n const bytes = encoder.encode(text);\n const lines: string[] = [];\n let line = \"\";\n\n for (let i = 0; i < bytes.length; i++) {\n const byte = bytes[i] ?? 0;\n\n if (byte === 0x0a) {\n lines.push(line);\n line = \"\";\n continue;\n }\n\n if (byte === 0x0d) {\n continue;\n }\n\n let encoded: string;\n if (\n (byte >= 33 && byte <= 60) ||\n (byte >= 62 && byte <= 126) ||\n byte === 0x09 ||\n byte === 0x20\n ) {\n encoded = String.fromCharCode(byte);\n } else {\n encoded = `=${byte.toString(16).toUpperCase().padStart(2, \"0\")}`;\n }\n\n if (line.length + encoded.length > 75) {\n lines.push(`${line}=`);\n line = encoded;\n } else {\n line += encoded;\n }\n }\n\n if (line.length > 0) {\n lines.push(line);\n }\n\n return lines.join(\"\\r\\n\");\n}\n\n/**\n * Encode an email header value per RFC 2047.\n * Non-ASCII values become: =?UTF-8?B?<base64>?=\n */\nexport function encodeHeader(value: string): string {\n if (!needsEncoding(value)) {\n return value;\n }\n\n const encoded = encodeBase64(value).replace(/\\r\\n/g, \"\");\n return `=?UTF-8?B?${encoded}?=`;\n}\n\n/**\n * Returns true if the string contains non-ASCII characters\n * and therefore requires RFC 2047 encoding in headers.\n */\nexport function needsEncoding(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) > 127) {\n return true;\n }\n }\n return false;\n}\n\nfunction base64CharToValue(char: string): number {\n if (char === \"=\") {\n return 0;\n }\n const index = BASE64_CHARS.indexOf(char);\n if (index === -1) {\n throw new Error(`Invalid base64 character: ${char}`);\n }\n return index;\n}\n\nfunction wrapBase64Lines(base64: string): string {\n const lines: string[] = [];\n for (let i = 0; i < base64.length; i += BASE64_LINE_LENGTH) {\n lines.push(base64.slice(i, i + BASE64_LINE_LENGTH));\n }\n return lines.join(\"\\r\\n\");\n}\n\n/** Decode bytes to UTF-8 string. */\nexport function decodeUtf8(bytes: Uint8Array): string {\n return decoder.decode(bytes);\n}\n\n/** Encode string to UTF-8 bytes. */\nexport function encodeUtf8(text: string): Uint8Array {\n return encoder.encode(text);\n}\n"
6
6
  ],
7
7
  "mappings": "AAKA,IAAM,EAAU,IAAI,YAOb,SAAS,CAAY,CAAC,EAAmC,CAC9D,IAAM,EAAQ,OAAO,IAAS,SAAW,EAAQ,OAAO,CAAI,EAAI,EAC5D,EAAS,GACT,EAAI,EAER,MAAO,EAAI,EAAM,OAAQ,CACvB,IAAM,EAAK,EAAM,IAAM,EACjB,EAAK,EAAM,EAAI,GACf,EAAK,EAAM,EAAI,GAErB,GAAI,IAAO,OAAW,CACpB,GArBe,mEAqBQ,GAAM,GAC7B,GAtBe,mEAsBS,GAAK,IAAS,GACtC,GAAU,KACV,MAGF,GAAI,IAAO,OAAW,CACpB,GA5Be,mEA4BQ,GAAM,GAC7B,GA7Be,mEA6BU,GAAK,IAAS,EAAM,GAAM,GACnD,GA9Be,mEA8BS,GAAK,KAAS,GACtC,GAAU,IACV,MAGF,GAnCiB,mEAmCM,GAAM,GAC7B,GApCiB,mEAoCQ,GAAK,IAAS,EAAM,GAAM,GACnD,GArCiB,mEAqCQ,GAAK,KAAS,EAAM,GAAM,GACnD,GAtCiB,mEAsCM,EAAK,IAC5B,GAAK,EAGP,OAAO,EAAgB,CAAM,EAMxB,SAAS,CAAY,CAAC,EAA0B,CACrD,IAAM,EAAU,EAAK,QAAQ,MAAO,EAAE,EAChC,EAAM,EAAQ,OAEpB,GAAI,IAAQ,EACV,OAAO,IAAI,WAAW,CAAC,EAGzB,GAAI,EAAM,IAAM,EACd,MAAU,MAAM,8BAA8B,EAGhD,IAAM,EAAU,EAAQ,SAAS,IAAI,EAAI,EAAI,EAAQ,SAAS,GAAG,EAAI,EAAI,EACnE,EAAa,EAAM,EAAK,EAAI,EAC5B,EAAS,IAAI,WAAW,CAAS,EAEnC,EAAW,EACf,QAAS,EAAI,EAAG,EAAI,EAAK,GAAK,EAAG,CAC/B,IAAM,EAAK,EAAkB,EAAQ,IAAM,GAAG,EACxC,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAK,EAAkB,EAAQ,EAAI,IAAM,GAAG,EAC5C,EAAU,GAAM,GAAO,GAAM,GAAO,GAAM,EAAK,EAErD,GAAI,EAAW,EACb,EAAO,KAAe,GAAU,GAAM,IAExC,GAAI,EAAW,EACb,EAAO,KAAe,GAAU,EAAK,IAEvC,GAAI,EAAW,EACb,EAAO,KAAc,EAAS,IAIlC,OAAO,EAuDF,SAAS,CAAY,CAAC,EAAuB,CAClD,GAAI,CAAC,EAAc,CAAK,EACtB,OAAO,EAIT,MAAO,aADS,EAAa,CAAK,EAAE,QAAQ,QAAS,EAAE,MAQlD,SAAS,CAAa,CAAC,EAAuB,CACnD,QAAS,EAAI,EAAG,EAAI,EAAK,OAAQ,IAC/B,GAAI,EAAK,WAAW,CAAC,EAAI,IACvB,MAAO,GAGX,MAAO,GAGT,SAAS,CAAiB,CAAC,EAAsB,CAC/C,GAAI,IAAS,IACX,MAAO,GAET,IAAM,EApKa,mEAoKQ,QAAQ,CAAI,EACvC,GAAI,IAAU,GACZ,MAAU,MAAM,6BAA6B,GAAM,EAErD,OAAO,EAGT,SAAS,CAAe,CAAC,EAAwB,CAC/C,IAAM,EAAkB,CAAC,EACzB,QAAS,EAAI,EAAG,EAAI,EAAO,OAAQ,GA5KV,GA6KvB,EAAM,KAAK,EAAO,MAAM,EAAG,EA7KJ,EA6K0B,CAAC,EAEpD,OAAO,EAAM,KAAK;AAAA,CAAM,EASnB,SAAS,CAAU,CAAC,EAA0B,CACnD,OAAO,EAAQ,OAAO,CAAI",
8
- "debugId": "678FAACF05287D3164756E2164756E21",
8
+ "debugId": "8870A4553A4DBAC364756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,4 @@
1
- import{P as V}from"./chunk-gxq2aamv.js";function W(q){for(let E=0;E<q.length;E++){let z=q.charCodeAt(E);if(z<=31||z===127||z===8232||z===8233)return z}return-1}function K(q,E="address"){let z=W(q);if(z!==-1){let G=z.toString(16).padStart(2,"0");throw Error(`Email ${E} contains a forbidden control character (0x${G}). CR, LF, NUL, and other control characters are not allowed.`)}}function X(q){if(Array.isArray(q))return q.flatMap((z)=>X(z));if(typeof q==="object"){if(K(q.address,"address"),q.name!==void 0)K(q.name,"display name");return[{...q}]}K(q,"address");let E=q.trim();if(!E)return[];return Y(E).map(Z)}function $(q){if(K(q.address,"address"),q.name)return K(q.name,"display name"),`${V(q.name)} <${q.address}>`;return q.address}function w(q){return X(q).map((E)=>E.address)}function x(q){if(W(q)!==-1)return!1;return/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(q)}function Y(q){let E=[],z="",G=!1,J=!1;for(let O=0;O<q.length;O++){let I=q[O]??"";if(I==='"'&&q[O-1]!=="\\"){G=!G,z+=I;continue}if(I==="<"&&!G){J=!0,z+=I;continue}if(I===">"&&!G){J=!1,z+=I;continue}if(I===","&&!G&&!J){if(z.trim())E.push(z.trim());z="";continue}z+=I}if(z.trim())E.push(z.trim());return E}function Z(q){let E=q.trim(),z=E.match(/^(?:"([^"]*)"|([^<]*?))\s*<([^>]+)>$/);if(z){let G=(z[1]??z[2]??"").trim(),J=(z[3]??"").trim();if(G)return{name:G,address:J};return{address:J}}if(E.startsWith('"')&&E.endsWith('"'))return{address:E.slice(1,-1)};return{address:E}}
2
- export{K as t,X as u,$ as v,w,x};
1
+ import{Q as V}from"./chunk-wzdcz88x.js";function W(q){for(let E=0;E<q.length;E++){let z=q.charCodeAt(E);if(z<=31||z===127||z===8232||z===8233)return z}return-1}function K(q,E="address"){let z=W(q);if(z!==-1){let G=z.toString(16).padStart(2,"0");throw Error(`Email ${E} contains a forbidden control character (0x${G}). CR, LF, NUL, and other control characters are not allowed.`)}}function X(q){if(Array.isArray(q))return q.flatMap((z)=>X(z));if(typeof q==="object"){if(K(q.address,"address"),q.name!==void 0)K(q.name,"display name");return[{...q}]}K(q,"address");let E=q.trim();if(!E)return[];return Y(E).map(Z)}function $(q){if(K(q.address,"address"),q.name)return K(q.name,"display name"),`${V(q.name)} <${q.address}>`;return q.address}function w(q){return X(q).map((E)=>E.address)}function x(q){if(W(q)!==-1)return!1;return/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(q)}function Y(q){let E=[],z="",G=!1,J=!1;for(let O=0;O<q.length;O++){let I=q[O]??"";if(I==='"'&&q[O-1]!=="\\"){G=!G,z+=I;continue}if(I==="<"&&!G){J=!0,z+=I;continue}if(I===">"&&!G){J=!1,z+=I;continue}if(I===","&&!G&&!J){if(z.trim())E.push(z.trim());z="";continue}z+=I}if(z.trim())E.push(z.trim());return E}function Z(q){let E=q.trim(),z=E.match(/^(?:"([^"]*)"|([^<]*?))\s*<([^>]+)>$/);if(z){let G=(z[1]??z[2]??"").trim(),J=(z[3]??"").trim();if(G)return{name:G,address:J};return{address:J}}if(E.startsWith('"')&&E.endsWith('"'))return{address:E.slice(1,-1)};return{address:E}}
2
+ export{K as x,X as y,$ as z,w as A,x as B};
3
3
 
4
- //# debugId=B28F759D0BA1665264756E2164756E21
4
+ //# debugId=FB3B85F0A99AF52264756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "// src/core/address.ts\nimport { encodeHeader } from \"./base64.js\";\nimport type { Address, AddressInput } from \"./types.js\";\n\n/**\n * Find the first forbidden control character in a string.\n *\n * Forbidden characters must never appear in an email address or display name,\n * because each enables a distinct attack:\n * - CR (0x0D) / LF (0x0A): email header injection and SMTP command injection\n * - NUL (0x00): C-string truncation / parser confusion in downstream agents\n * - other C0 controls (0x01–0x1F), DEL (0x7F): header/parser confusion\n * - U+2028 / U+2029: line separators that some parsers treat as newlines\n *\n * @returns The char code of the first forbidden character, or -1 if none.\n */\nfunction findForbiddenChar(value: string): number {\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n if (code <= 0x1f || code === 0x7f || code === 0x2028 || code === 0x2029) {\n return code;\n }\n }\n return -1;\n}\n\n/**\n * Assert that a raw address or display-name value contains no forbidden\n * control characters. Throws immediately (fail closed) — the library never\n * attempts to strip or rewrite hostile input into an accepted value.\n *\n * The check is intentionally performed on the RAW input, before any trimming\n * or normalization, so hostile values are rejected rather than repaired.\n *\n * @param value - The raw, untransformed string to validate.\n * @param label - Field label used in the error message (e.g. \"address\").\n * @throws {Error} If the value contains any forbidden control character.\n */\nexport function assertSafeAddress(value: string, label = \"address\"): void {\n const code = findForbiddenChar(value);\n if (code !== -1) {\n const hex = code.toString(16).padStart(2, \"0\");\n throw new Error(\n `Email ${label} contains a forbidden control character (0x${hex}). ` +\n \"CR, LF, NUL, and other control characters are not allowed.\",\n );\n }\n}\n\n/**\n * Normalize any AddressInput form into Address[].\n *\n * Every address and display name is validated against control-character\n * injection before any transformation. This is the single chokepoint shared\n * by all transports and address fields (From, To, Cc, Bcc, Reply-To), so the\n * protection is uniform and secure by default.\n *\n * @throws {Error} If any address or name contains a forbidden control character.\n */\nexport function parseAddresses(input: AddressInput): Address[] {\n if (Array.isArray(input)) {\n return input.flatMap((item) => parseAddresses(item));\n }\n\n if (typeof input === \"object\") {\n assertSafeAddress(input.address, \"address\");\n if (input.name !== undefined) {\n assertSafeAddress(input.name, \"display name\");\n }\n return [{ ...input }];\n }\n\n // Validate the raw input string before splitting or trimming so injected\n // newlines cannot hide inside a multi-address list.\n assertSafeAddress(input, \"address\");\n\n const trimmed = input.trim();\n if (!trimmed) {\n return [];\n }\n\n return splitAddressList(trimmed).map(parseSingleAddress);\n}\n\n/**\n * Format an Address for SMTP envelope commands (MAIL FROM / RCPT TO).\n */\nexport function toEnvelope(address: Address): string {\n return address.address;\n}\n\n/**\n * Format an Address for use in a MIME header (From, To, CC, etc.).\n *\n * Re-validates the address and name at render time so a header can never be\n * emitted with an embedded control character, even if the {@link Address} was\n * constructed without going through {@link parseAddresses}.\n *\n * @throws {Error} If the address or name contains a forbidden control character.\n */\nexport function toMIMEHeader(address: Address): string {\n assertSafeAddress(address.address, \"address\");\n if (address.name) {\n assertSafeAddress(address.name, \"display name\");\n const name = encodeHeader(address.name);\n return `${name} <${address.address}>`;\n }\n return address.address;\n}\n\n/**\n * Extract plain email strings from any AddressInput.\n */\nexport function extractEmails(input: AddressInput): string[] {\n return parseAddresses(input).map((addr) => addr.address);\n}\n\n/**\n * Basic email format validation (format only, no DNS lookup).\n *\n * Rejects any control character (including CR, LF, tab, and NUL) before\n * applying the structural check, so a \"valid\" result is always safe to place\n * into a header or SMTP command.\n */\nexport function isValidEmail(email: string): boolean {\n if (findForbiddenChar(email) !== -1) return false;\n return /^[^\\s@<>]+@[^\\s@<>]+\\.[^\\s@<>]+$/.test(email);\n}\n\nfunction splitAddressList(input: string): string[] {\n const parts: string[] = [];\n let current = \"\";\n let inQuotes = false;\n let inAngle = false;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i] ?? \"\";\n if (char === '\"' && input[i - 1] !== \"\\\\\") {\n inQuotes = !inQuotes;\n current += char;\n continue;\n }\n if (char === \"<\" && !inQuotes) {\n inAngle = true;\n current += char;\n continue;\n }\n if (char === \">\" && !inQuotes) {\n inAngle = false;\n current += char;\n continue;\n }\n if (char === \",\" && !inQuotes && !inAngle) {\n if (current.trim()) {\n parts.push(current.trim());\n }\n current = \"\";\n continue;\n }\n current += char;\n }\n\n if (current.trim()) {\n parts.push(current.trim());\n }\n\n return parts;\n}\n\nfunction parseSingleAddress(input: string): Address {\n const trimmed = input.trim();\n\n const angleMatch = trimmed.match(/^(?:\"([^\"]*)\"|([^<]*?))\\s*<([^>]+)>$/);\n if (angleMatch) {\n const name = (angleMatch[1] ?? angleMatch[2] ?? \"\").trim();\n const address = (angleMatch[3] ?? \"\").trim();\n if (name) {\n return { name, address };\n }\n return { address };\n }\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n return { address: trimmed.slice(1, -1) };\n }\n\n return { address: trimmed };\n}\n"
6
6
  ],
7
7
  "mappings": "wCAgBA,SAAS,CAAiB,CAAC,EAAuB,CAChD,QAAS,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,WAAW,CAAC,EAC/B,GAAI,GAAQ,IAAQ,IAAS,KAAQ,IAAS,MAAU,IAAS,KAC/D,OAAO,EAGX,MAAO,GAeF,SAAS,CAAiB,CAAC,EAAe,EAAQ,UAAiB,CACxE,IAAM,EAAO,EAAkB,CAAK,EACpC,GAAI,IAAS,GAAI,CACf,IAAM,EAAM,EAAK,SAAS,EAAE,EAAE,SAAS,EAAG,GAAG,EAC7C,MAAU,MACR,SAAS,+CAAmD,gEAE9D,GAcG,SAAS,CAAc,CAAC,EAAgC,CAC7D,GAAI,MAAM,QAAQ,CAAK,EACrB,OAAO,EAAM,QAAQ,CAAC,IAAS,EAAe,CAAI,CAAC,EAGrD,GAAI,OAAO,IAAU,SAAU,CAE7B,GADA,EAAkB,EAAM,QAAS,SAAS,EACtC,EAAM,OAAS,OACjB,EAAkB,EAAM,KAAM,cAAc,EAE9C,MAAO,CAAC,IAAK,CAAM,CAAC,EAKtB,EAAkB,EAAO,SAAS,EAElC,IAAM,EAAU,EAAM,KAAK,EAC3B,GAAI,CAAC,EACH,MAAO,CAAC,EAGV,OAAO,EAAiB,CAAO,EAAE,IAAI,CAAkB,EAmBlD,SAAS,CAAY,CAAC,EAA0B,CAErD,GADA,EAAkB,EAAQ,QAAS,SAAS,EACxC,EAAQ,KAGV,OAFA,EAAkB,EAAQ,KAAM,cAAc,EAEvC,GADM,EAAa,EAAQ,IAAI,MACnB,EAAQ,WAE7B,OAAO,EAAQ,QAMV,SAAS,CAAa,CAAC,EAA+B,CAC3D,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC,IAAS,EAAK,OAAO,EAUlD,SAAS,CAAY,CAAC,EAAwB,CACnD,GAAI,EAAkB,CAAK,IAAM,GAAI,MAAO,GAC5C,MAAO,mCAAmC,KAAK,CAAK,EAGtD,SAAS,CAAgB,CAAC,EAAyB,CACjD,IAAM,EAAkB,CAAC,EACrB,EAAU,GACV,EAAW,GACX,EAAU,GAEd,QAAS,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,IAAM,GACzB,GAAI,IAAS,KAAO,EAAM,EAAI,KAAO,KAAM,CACzC,EAAW,CAAC,EACZ,GAAW,EACX,SAEF,GAAI,IAAS,KAAO,CAAC,EAAU,CAC7B,EAAU,GACV,GAAW,EACX,SAEF,GAAI,IAAS,KAAO,CAAC,EAAU,CAC7B,EAAU,GACV,GAAW,EACX,SAEF,GAAI,IAAS,KAAO,CAAC,GAAY,CAAC,EAAS,CACzC,GAAI,EAAQ,KAAK,EACf,EAAM,KAAK,EAAQ,KAAK,CAAC,EAE3B,EAAU,GACV,SAEF,GAAW,EAGb,GAAI,EAAQ,KAAK,EACf,EAAM,KAAK,EAAQ,KAAK,CAAC,EAG3B,OAAO,EAGT,SAAS,CAAkB,CAAC,EAAwB,CAClD,IAAM,EAAU,EAAM,KAAK,EAErB,EAAa,EAAQ,MAAM,sCAAsC,EACvE,GAAI,EAAY,CACd,IAAM,GAAQ,EAAW,IAAM,EAAW,IAAM,IAAI,KAAK,EACnD,GAAW,EAAW,IAAM,IAAI,KAAK,EAC3C,GAAI,EACF,MAAO,CAAE,OAAM,SAAQ,EAEzB,MAAO,CAAE,SAAQ,EAGnB,GAAI,EAAQ,WAAW,GAAG,GAAK,EAAQ,SAAS,GAAG,EACjD,MAAO,CAAE,QAAS,EAAQ,MAAM,EAAG,EAAE,CAAE,EAGzC,MAAO,CAAE,QAAS,CAAQ",
8
- "debugId": "B28F759D0BA1665264756E2164756E21",
8
+ "debugId": "FB3B85F0A99AF52264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
- import{t as v,u as j,v as D,w as T,x as y}from"./chunk-b0qkndv9.js";import{N as F,P as M,Q as P}from"./chunk-gxq2aamv.js";import{R as b}from"./chunk-mg8m26nr.js";function N(q){return q.replace(/\r\n?|\n/g," ").trim()}function w(q){if(v(q.address,"address"),q.name!==void 0)v(q.name,"display name");let O=q.address.trim();if(!y(O))throw Error(`Invalid email address: ${JSON.stringify(q.address)}`);let G={address:O};if(q.name)G.name=N(q.name);return G}var Q=`\r
1
+ import{A as T,B as y,x as v,y as j,z as D}from"./chunk-y4qxdyqn.js";import{O as F,Q as M,R as P}from"./chunk-wzdcz88x.js";import{V as b}from"./chunk-rszb1n1d.js";function N(q){return q.replace(/\r\n?|\n/g," ").trim()}function w(q){if(v(q.address,"address"),q.name!==void 0)v(q.name,"display name");let O=q.address.trim();if(!y(O))throw Error(`Invalid email address: ${JSON.stringify(q.address)}`);let G={address:O};if(q.name)G.name=N(q.name);return G}var Q=`\r
2
2
  `;async function c(q,O){let G=q.messageId??f(),X=(q.date??new Date).toUTCString(),Y=j(q.from),Z=j(q.to),K=q.cc?j(q.cc):[];if(Y.length===0)throw Error("Missing from address");if(Z.length===0)throw Error("Missing to address");let H={from:Y[0]?.address??"",to:[...T(q.to),...q.cc?T(q.cc):[],...q.bcc?T(q.bcc):[]]},E=q.attachments??[],V=E.filter((J)=>J.inline||J.contentId),I=E.filter((J)=>!J.inline&&!J.contentId),W=k(q);if(V.length>0){let J=S();W={contentType:`multipart/related; boundary="${J}"`,content:x(J,[L(k(q)),...V.map(R)])}}if(I.length>0){let J=S();W={contentType:`multipart/mixed; boundary="${J}"`,content:x(J,[L(W),...I.map(R)])}}let _=[$("From",Y.map((J)=>D(w(J))).join(", ")),$("To",Z.map((J)=>D(w(J))).join(", "))];if(K.length>0)_.push($("Cc",K.map((J)=>D(w(J))).join(", ")));if(q.replyTo)_.push($("Reply-To",j(q.replyTo).map((J)=>D(w(J))).join(", ")));if(_.push($("Subject",M(N(q.subject))),$("Date",X),$("Message-ID",G),"MIME-Version: 1.0"),q.priority==="high")_.push("X-Priority: 1","Importance: high");else if(q.priority==="low")_.push("X-Priority: 5","Importance: low");if(q.headers)for(let[J,B]of Object.entries(q.headers))_.push($(N(J),N(B)));if(_.push(`Content-Type: ${W.contentType}`),W.contentTransferEncoding)_.push(`Content-Transfer-Encoding: ${W.contentTransferEncoding}`);let z=`${_.join(Q)}${Q}${Q}${W.content}`,U=P(z);if(O){let{signDKIM:J}=await import("./dkim.js"),{header:B}=await J(U,O),g=`${B}${Q}${z}`;U=P(g)}return{raw:U,envelope:H,messageId:G,size:U.length}}function k(q){let O=Boolean(q.text),G=Boolean(q.html);if(O&&G){let X=S();return{contentType:`multipart/alternative; boundary="${X}"`,content:x(X,[C({contentType:"text/plain; charset=utf-8",contentTransferEncoding:"8bit",content:q.text??""}),C({contentType:"text/html; charset=utf-8",contentTransferEncoding:"8bit",content:q.html??""})])}}if(G)return{contentType:"text/html; charset=utf-8",contentTransferEncoding:"8bit",content:q.html??""};return{contentType:"text/plain; charset=utf-8",contentTransferEncoding:"8bit",content:q.text??""}}function C(q){let O=[`Content-Type: ${q.contentType}`];if(q.contentTransferEncoding)O.push(`Content-Transfer-Encoding: ${q.contentTransferEncoding}`);return`${O.join(Q)}${Q}${Q}${q.content}`}function L(q){let O=[`Content-Type: ${q.contentType}`];if(q.contentTransferEncoding)O.push(`Content-Transfer-Encoding: ${q.contentTransferEncoding}`);return`${O.join(Q)}${Q}${Q}${q.content}`}function R(q){if(!q.content||typeof q.content==="string")throw Error(`Attachment "${q.filename}" requires Uint8Array content`);let O=N(q.filename??""),G=[`Content-Type: ${q.contentType??"application/octet-stream"}`,"Content-Transfer-Encoding: base64",`Content-Disposition: ${q.inline?"inline":"attachment"}; filename="${O}"`];if(q.contentId)G.push(`Content-ID: <${q.contentId}>`);if(q.headers)for(let[X,Y]of Object.entries(q.headers)){let Z=N(X),K=N(Y);G.push(`${Z}: ${K}`)}return`${G.join(Q)}${Q}${Q}${F(q.content)}`}function x(q,O){let G=O.map((X)=>`--${q}${Q}${X}`);return G.push(`--${q}--`),G.join(Q)}function f(){let q=crypto.getRandomValues(new Uint8Array(8)),O=Array.from(q,(G)=>G.toString(16).padStart(2,"0")).join("");return`<${Date.now()}.${O}@sently>`}function S(){let q=crypto.getRandomValues(new Uint8Array(12));return`----sently_${Array.from(q,(G)=>G.toString(16).padStart(2,"0")).join("")}`}function $(q,O){let G=`${q}: ${O}`;if(G.length<=76)return G;let X=[],Y=G;while(Y.length>76){let Z=Y.lastIndexOf(" ",76);if(Z<=q.length+1)Z=76;X.push(Y.slice(0,Z)),Y=` ${Y.slice(Z).trimStart()}`}return X.push(Y),X.join(`${Q} `)}
3
3
  export{c as r};
4
4
 
5
- //# debugId=560208019304574864756E2164756E21
5
+ //# debugId=298CBDBA05B7F04464756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "// 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 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 { signDKIM } = await import(\"../dkim.js\");\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
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "560208019304574864756E2164756E21",
8
+ "debugId": "298CBDBA05B7F04464756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,4 +1,4 @@
1
- import{R as C}from"./chunk-mg8m26nr.js";async function K(D,y){let E=D??[],j=[];for(let b of E){if(b.content instanceof Uint8Array){j.push(b);continue}if(b.path){let z;try{z=await import("node:fs/promises")}catch{throw Error("attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead")}if(y?.basePath){let{resolve:A,sep:H}=await import("node:path"),k=A(b.path),w=A(y.basePath);if(!(k===w||k.startsWith(w+H)))throw Error(`[sently] Attachment path "${k}" escapes basePath "${w}". Use absolute paths within the allowed directory.`)}let F=await z.readFile(b.path),{path:I,...G}=b;j.push({...G,content:new Uint8Array(F)});continue}if(typeof b.content==="string"){j.push(b);continue}j.push(b)}return j}
1
+ import{V as C}from"./chunk-rszb1n1d.js";async function K(D,y){let E=D??[],j=[];for(let b of E){if(b.content instanceof Uint8Array){j.push(b);continue}if(b.path){let z;try{z=await import("node:fs/promises")}catch{throw Error("attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead")}if(y?.basePath){let{resolve:A,sep:H}=await import("node:path"),k=A(b.path),w=A(y.basePath);if(!(k===w||k.startsWith(w+H)))throw Error(`[sently] Attachment path "${k}" escapes basePath "${w}". Use absolute paths within the allowed directory.`)}let F=await z.readFile(b.path),{path:I,...G}=b;j.push({...G,content:new Uint8Array(F)});continue}if(typeof b.content==="string"){j.push(b);continue}j.push(b)}return j}
2
2
  export{K as s};
3
3
 
4
- //# debugId=94F87AE0DE874B5764756E2164756E21
4
+ //# debugId=F2AE02E3CB344AE864756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "import type { Attachment } from \"../core/types.js\";\n\n/** Options for {@link resolveAttachments}. */\nexport interface ResolveAttachmentsOptions {\n /**\n * If set, attachment paths must resolve within this directory.\n * Prevents path traversal via `..` segments and sibling-directory\n * prefix matches. Opt-in only.\n *\n * Note: this check uses `node:path` `resolve()`, which does NOT\n * dereference symlinks. A symlink located inside `basePath` that\n * points outside of it will pass this check. If symlink traversal is\n * a concern, resolve paths with `fs.realpath()` before passing them in.\n */\n basePath?: string;\n}\n\n/**\n * Resolve attachment.path to in-memory Uint8Array content.\n * @throws When attachment.path is used on runtimes without node:fs/promises\n */\nexport async function resolveAttachments(\n attachments: Attachment[] | undefined,\n options?: ResolveAttachmentsOptions,\n): Promise<Attachment[]> {\n const list = attachments ?? [];\n const resolved: Attachment[] = [];\n\n for (const attachment of list) {\n if (attachment.content instanceof Uint8Array) {\n resolved.push(attachment);\n continue;\n }\n\n if (attachment.path) {\n let fs: typeof import(\"node:fs/promises\");\n try {\n fs = await import(\"node:fs/promises\");\n } catch {\n throw new Error(\n \"attachment.path is not supported on this runtime — use attachment.content (Uint8Array) instead\",\n );\n }\n\n if (options?.basePath) {\n const { resolve, sep } = await import(\"node:path\");\n const resolvedPath = resolve(attachment.path);\n const resolvedBase = resolve(options.basePath);\n // startsWith alone is vulnerable: \"/var/data-secret\" passes \"/var/data\".\n // Require an exact match or a trailing path separator.\n const isWithin =\n resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + sep);\n if (!isWithin) {\n throw new Error(\n `[sently] Attachment path \"${resolvedPath}\" escapes basePath \"${resolvedBase}\". ` +\n \"Use absolute paths within the allowed directory.\",\n );\n }\n }\n\n const data = await fs.readFile(attachment.path);\n const { path: _path, ...rest } = attachment;\n resolved.push({ ...rest, content: new Uint8Array(data) });\n continue;\n }\n\n if (typeof attachment.content === \"string\") {\n resolved.push(attachment);\n continue;\n }\n\n resolved.push(attachment);\n }\n\n return resolved;\n}\n"
6
6
  ],
7
7
  "mappings": "wCAqBA,eAAsB,CAAkB,CACtC,EACA,EACuB,CACvB,IAAM,EAAO,GAAe,CAAC,EACvB,EAAyB,CAAC,EAEhC,QAAW,KAAc,EAAM,CAC7B,GAAI,EAAW,mBAAmB,WAAY,CAC5C,EAAS,KAAK,CAAU,EACxB,SAGF,GAAI,EAAW,KAAM,CACnB,IAAI,EACJ,GAAI,CACF,EAAK,KAAa,4BAClB,KAAM,CACN,MAAU,MACR,gGACF,EAGF,GAAI,GAAS,SAAU,CACrB,IAAQ,UAAS,OAAQ,KAAa,qBAChC,EAAe,EAAQ,EAAW,IAAI,EACtC,EAAe,EAAQ,EAAQ,QAAQ,EAK7C,GAAI,EADF,IAAiB,GAAgB,EAAa,WAAW,EAAe,CAAG,GAE3E,MAAU,MACR,6BAA6B,wBAAmC,sDAElE,EAIJ,IAAM,EAAO,MAAM,EAAG,SAAS,EAAW,IAAI,GACtC,KAAM,KAAU,GAAS,EACjC,EAAS,KAAK,IAAK,EAAM,QAAS,IAAI,WAAW,CAAI,CAAE,CAAC,EACxD,SAGF,GAAI,OAAO,EAAW,UAAY,SAAU,CAC1C,EAAS,KAAK,CAAU,EACxB,SAGF,EAAS,KAAK,CAAU,EAG1B,OAAO",
8
- "debugId": "94F87AE0DE874B5764756E2164756E21",
8
+ "debugId": "F2AE02E3CB344AE864756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @module
3
+ * Unified error hierarchy for sently transports and SMTP.
4
+ */
5
+ /** Stable machine-readable error codes shared across providers. */
6
+ export type SentlyErrorCode = "SMTP_AUTH_FAILED" | "RATE_LIMITED" | "TIMEOUT" | "CONNECTION_FAILED" | "PROVIDER_ERROR" | "BAD_REQUEST";
7
+ /** Options for constructing a {@link SentlyError}. */
8
+ export interface SentlyErrorOptions {
9
+ /** HTTP status code when the failure originated from an HTTP transport. */
10
+ statusCode?: number;
11
+ /** Original error or response payload. */
12
+ cause?: unknown;
13
+ /** Provider identifier (e.g. `"smtp"`, `"resend"`). */
14
+ provider?: string;
15
+ }
16
+ /**
17
+ * Base error class for all sently transport and protocol failures.
18
+ *
19
+ * Subclasses may shadow {@link code} with provider-specific or numeric values
20
+ * (e.g. SMTP response codes). Use {@link sentlyCode} for the stable machine-readable
21
+ * code in those cases.
22
+ */
23
+ export declare class SentlyError extends Error {
24
+ /**
25
+ * Stable machine-readable error code.
26
+ * Preserved even when a subclass shadows {@link code}.
27
+ */
28
+ readonly sentlyCode: SentlyErrorCode;
29
+ /** HTTP status code when applicable. */
30
+ readonly statusCode?: number;
31
+ /** Provider identifier when applicable. */
32
+ readonly provider?: string;
33
+ /**
34
+ * Error code — machine-readable string on {@link SentlyError}, or a legacy
35
+ * provider/SMTP-specific value on subclasses (numeric SMTP codes, Brevo/SES API codes).
36
+ */
37
+ code: string | number;
38
+ /** Creates a sently error with a machine-readable code and optional metadata. */
39
+ constructor(message: string, sentlyCode: SentlyErrorCode, options?: SentlyErrorOptions);
40
+ }
41
+ /**
42
+ * Map an HTTP status code to a stable sently error code.
43
+ *
44
+ * @param status - HTTP response status code
45
+ * @returns Machine-readable error code
46
+ */
47
+ export declare function httpStatusToSentlyCode(status: number): SentlyErrorCode;
48
+ /**
49
+ * Map an SMTP response code and command to a stable sently error code.
50
+ *
51
+ * @param smtpCode - Three-digit SMTP response code (0 for local/parse errors)
52
+ * @param command - SMTP command that failed
53
+ * @returns Machine-readable error code
54
+ */
55
+ export declare function smtpCodeToSentlyCode(smtpCode: number, command: string): SentlyErrorCode;
@@ -0,0 +1,3 @@
1
+ import{S as a,T as b,U as c}from"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";export{c as smtpCodeToSentlyCode,b as httpStatusToSentlyCode,a as SentlyError};
2
+
3
+ //# debugId=4F713DE42335730C64756E2164756E21
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "4F713DE42335730C64756E2164756E21",
8
+ "names": []
9
+ }
@@ -1,3 +1,4 @@
1
+ import { SentlyError } from "./errors.js";
1
2
  export { computeCRAMMD5 } from "./cram-md5.js";
2
3
  /** SMTP command to send to the server. */
3
4
  export type SMTPCommand = {
@@ -53,12 +54,11 @@ export interface SMTPResponse {
53
54
  isError: boolean;
54
55
  }
55
56
  /** SMTP protocol error with server response details. */
56
- export declare class SMTPError extends Error {
57
- readonly code: number;
57
+ export declare class SMTPError extends SentlyError {
58
58
  readonly command: string;
59
59
  readonly response: string;
60
60
  /** Creates an SMTP protocol error. */
61
- constructor(message: string, code: number, command: string, response: string);
61
+ constructor(message: string, smtpCode: number, command: string, response: string);
62
62
  }
63
63
  /**
64
64
  * Encode an SMTPCommand into a Uint8Array for sending over the socket.
package/dist/core/smtp.js CHANGED
@@ -1,3 +1,3 @@
1
- import{B as a,C as b,D as c,E as d,F as e,G as f,H as g,I as h,J as i,K as j,L as k,M as l}from"../chunk-9p4vev6j.js";import"../chunk-gxq2aamv.js";import"../chunk-mg8m26nr.js";export{f as selectAuthMethod,d as parseResponse,g as parseEHLO,l as encodeLine,c as encodeCommand,j as encodeAuthLoginUser,i as encodeAuthLoginPass,k as encodeAuthCramResponse,a as computeCRAMMD5,h as assertResponse,e as accumulateResponse,b as SMTPError};
1
+ import{C as a,D as b,E as c,F as d,G as e,H as f,I as g,J as h,K as i,L as j,M as k,N as l}from"../chunk-w3fnjh04.js";import"../chunk-wzdcz88x.js";import"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";export{f as selectAuthMethod,d as parseResponse,g as parseEHLO,l as encodeLine,c as encodeCommand,j as encodeAuthLoginUser,i as encodeAuthLoginPass,k as encodeAuthCramResponse,a as computeCRAMMD5,h as assertResponse,e as accumulateResponse,b as SMTPError};
2
2
 
3
- //# debugId=FCECF4885B1C1DCF64756E2164756E21
3
+ //# debugId=C99031F5474716F164756E2164756E21
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "FCECF4885B1C1DCF64756E2164756E21",
7
+ "debugId": "C99031F5474716F164756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -347,18 +347,45 @@ export interface PreviewConfig {
347
347
  */
348
348
  format?: "eml" | "html";
349
349
  }
350
+ /** Context passed to mailer lifecycle hooks (no message body — avoids PII in logs). */
351
+ export interface MailerHookContext {
352
+ /** Message-ID when known (from options or send result). */
353
+ messageId?: string;
354
+ /** Envelope recipient email addresses. */
355
+ to: string[];
356
+ /** Message subject line. */
357
+ subject: string;
358
+ /** Transport or provider identifier (e.g. `"smtp"`, `"resend"`). */
359
+ provider: string;
360
+ }
361
+ /** Optional lifecycle hooks for metrics, tracing, and observability on every send. */
362
+ export interface MailerHooks {
363
+ /** Fired before the transport sends the message. */
364
+ onSend?: (ctx: MailerHookContext) => void | Promise<void>;
365
+ /** Fired after a successful send. */
366
+ onSuccess?: (ctx: MailerHookContext, result: SendResult) => void | Promise<void>;
367
+ /** Fired when a send throws (error is re-thrown after the hook runs). */
368
+ onError?: (ctx: MailerHookContext, error: unknown) => void | Promise<void>;
369
+ /** Fired before each retry attempt (requires {@link RetryTransport}). */
370
+ onRetry?: (ctx: MailerHookContext, attempt: number, error: unknown) => void | Promise<void>;
371
+ }
350
372
  /** Options for {@link createMailer} from `sently/mailer` — transport-only, smallest bundle. */
351
373
  export interface TransportMailerOptions {
352
374
  /** Transport that sends the message (HTTP API, SMTP wrapper, preview, etc.). */
353
375
  transport: Transport;
354
376
  /** Optional plugins run before each send. */
355
377
  plugins?: MailPlugin[];
378
+ /** Optional lifecycle hooks for metrics and tracing on every send. */
379
+ hooks?: MailerHooks;
356
380
  }
357
381
  /** Options for {@link createMailer} — custom transport or SMTP config. */
358
382
  export type CreateMailerOptions = ({
359
383
  transport: Transport;
360
384
  plugins?: MailPlugin[];
361
- } & Partial<SMTPConfig>) | SMTPConfig;
385
+ hooks?: MailerHooks;
386
+ } & Partial<SMTPConfig>) | (SMTPConfig & {
387
+ hooks?: MailerHooks;
388
+ });
362
389
  /**
363
390
  * A mail plugin transforms MailOptions before the message is built.
364
391
  * Plugins run sequentially. Each receives the output of the previous.
package/dist/detect.js CHANGED
@@ -1,3 +1,3 @@
1
- import{y as z,z as w}from"./chunk-9ea4hx7m.js";import"./chunk-vws105eb.js";import{R as v}from"./chunk-mg8m26nr.js";function K(){if(typeof Bun<"u")return"bun";if(typeof Deno<"u")return"deno";if(typeof caches<"u"&&globalThis.navigator?.userAgent==="Cloudflare-Workers")return"cf-workers";if(typeof window<"u")return"browser";if(typeof process<"u"&&process.versions?.node)return"node";return"unknown"}async function x(k){let h=K();switch(h){case"node":{let{NodeAdapter:q}=await import("./adapters/node.js");return new q(k)}case"bun":{let{BunAdapter:q}=await import("./adapters/bun.js");return new q(k)}case"deno":{let{DenoAdapter:q}=await import("./adapters/deno.js");return new q(k)}case"cf-workers":{let{CloudflareAdapter:q}=await import("./adapters/cf.js");return new q(k)}default:throw Error(`No socket adapter available for runtime: ${h}`)}}async function Q(k){if("transport"in k)return z({transport:k.transport,...k.plugins!==void 0?{plugins:k.plugins}:{}});let h=k,q={...h.secure!==void 0?{secure:h.secure}:{},...h.connectionTimeout!==void 0?{connectionTimeout:h.connectionTimeout}:{},...h.tls!==void 0?{tls:h.tls}:{}};if(h.pool){let{SMTPPool:J}=await import("./pool/pool.js");return new w(new J(h,{createAdapter:async()=>h.adapter??await x(q)}),h.plugins)}let E=h.adapter??await x(q),{SMTPTransport:G}=await import("./transports/smtp.js");return new w(new G({...h,adapter:E}),h.plugins)}export{K as detectRuntime,Q as createMailer,x as createDefaultAdapter};
1
+ import{t as z,u as w}from"./chunk-3y1tr1vt.js";import"./chunk-t0en64h9.js";import"./chunk-tfxje74k.js";import"./chunk-y4qxdyqn.js";import"./chunk-w3fnjh04.js";import"./chunk-wzdcz88x.js";import"./chunk-n601jhpd.js";import{V as v}from"./chunk-rszb1n1d.js";function K(){if(typeof Bun<"u")return"bun";if(typeof Deno<"u")return"deno";if(typeof caches<"u"&&globalThis.navigator?.userAgent==="Cloudflare-Workers")return"cf-workers";if(typeof window<"u")return"browser";if(typeof process<"u"&&process.versions?.node)return"node";return"unknown"}async function x(k){let h=K();switch(h){case"node":{let{NodeAdapter:q}=await import("./adapters/node.js");return new q(k)}case"bun":{let{BunAdapter:q}=await import("./adapters/bun.js");return new q(k)}case"deno":{let{DenoAdapter:q}=await import("./adapters/deno.js");return new q(k)}case"cf-workers":{let{CloudflareAdapter:q}=await import("./adapters/cf.js");return new q(k)}default:throw Error(`No socket adapter available for runtime: ${h}`)}}async function Q(k){if("transport"in k)return z({transport:k.transport,...k.plugins!==void 0?{plugins:k.plugins}:{},...k.hooks!==void 0?{hooks:k.hooks}:{}});let h=k,q={...h.secure!==void 0?{secure:h.secure}:{},...h.connectionTimeout!==void 0?{connectionTimeout:h.connectionTimeout}:{},...h.tls!==void 0?{tls:h.tls}:{}};if(h.pool){let{SMTPPool:J}=await import("./pool/pool.js");return new w(new J(h,{createAdapter:async()=>h.adapter??await x(q)}),h.plugins,h.hooks)}let E=h.adapter??await x(q),{SMTPTransport:G}=await import("./transports/smtp.js");return new w(new G({...h,adapter:E}),h.plugins,h.hooks)}export{K as detectRuntime,Q as createMailer,x as createDefaultAdapter};
2
2
 
3
- //# debugId=E95737E930123F1064756E2164756E21
3
+ //# debugId=29F94DB13623212064756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/detect.ts"],
4
4
  "sourcesContent": [
5
- "// src/detect.ts\nimport type {\n CreateMailerOptions,\n Mailer,\n Runtime,\n SMTPConfig,\n SocketAdapter,\n TLSOptions,\n} from \"./core/types.js\";\nimport { createMailer as createTransportMailer, MailerImpl } from \"./mailer.js\";\n\n/** Detect the current JavaScript runtime. */\nexport function detectRuntime(): Runtime {\n if (typeof Bun !== \"undefined\") {\n return \"bun\";\n }\n if (typeof Deno !== \"undefined\") {\n return \"deno\";\n }\n if (typeof caches !== \"undefined\" && globalThis.navigator?.userAgent === \"Cloudflare-Workers\") {\n return \"cf-workers\";\n }\n if (typeof window !== \"undefined\") {\n return \"browser\";\n }\n if (typeof process !== \"undefined\" && process.versions?.node) {\n return \"node\";\n }\n return \"unknown\";\n}\n\n/**\n * Dynamically import and instantiate the correct adapter for the current runtime.\n */\nexport async function createDefaultAdapter(options?: {\n secure?: boolean;\n connectionTimeout?: number;\n tls?: TLSOptions;\n}): Promise<SocketAdapter> {\n const runtime = detectRuntime();\n\n switch (runtime) {\n case \"node\": {\n const { NodeAdapter } = await import(\"./adapters/node.js\");\n return new NodeAdapter(options);\n }\n case \"bun\": {\n const { BunAdapter } = await import(\"./adapters/bun.js\");\n return new BunAdapter(options);\n }\n case \"deno\": {\n const { DenoAdapter } = await import(\"./adapters/deno.js\");\n return new DenoAdapter(options);\n }\n case \"cf-workers\": {\n const { CloudflareAdapter } = await import(\"./adapters/cf.js\");\n return new CloudflareAdapter(options);\n }\n default:\n throw new Error(`No socket adapter available for runtime: ${runtime}`);\n }\n}\n\n/**\n * Create a ready-to-use Mailer instance.\n *\n * For HTTP transports and smallest bundles, prefer `import { createMailer } from \"sently/mailer\"`.\n */\nexport async function createMailer(options: CreateMailerOptions): Promise<Mailer> {\n if (\"transport\" in options) {\n return createTransportMailer({\n transport: options.transport,\n ...(options.plugins !== undefined ? { plugins: options.plugins } : {}),\n });\n }\n\n const smtpConfig = options as SMTPConfig;\n const adapterOptions = {\n ...(smtpConfig.secure !== undefined ? { secure: smtpConfig.secure } : {}),\n ...(smtpConfig.connectionTimeout !== undefined\n ? { connectionTimeout: smtpConfig.connectionTimeout }\n : {}),\n ...(smtpConfig.tls !== undefined ? { tls: smtpConfig.tls } : {}),\n };\n\n if (smtpConfig.pool) {\n const { SMTPPool } = await import(\"./pool/pool.js\");\n return new MailerImpl(\n new SMTPPool(smtpConfig, {\n createAdapter: async () =>\n smtpConfig.adapter ?? (await createDefaultAdapter(adapterOptions)),\n }),\n smtpConfig.plugins,\n );\n }\n\n const adapter = smtpConfig.adapter ?? (await createDefaultAdapter(adapterOptions));\n const { SMTPTransport } = await import(\"./transports/smtp.js\");\n\n return new MailerImpl(new SMTPTransport({ ...smtpConfig, adapter }), smtpConfig.plugins);\n}\n\ndeclare const Bun: unknown;\ndeclare const Deno: unknown;\n"
5
+ "// src/detect.ts\nimport type {\n CreateMailerOptions,\n Mailer,\n MailerHooks,\n Runtime,\n SMTPConfig,\n SocketAdapter,\n TLSOptions,\n} from \"./core/types.js\";\nimport { createMailer as createTransportMailer, MailerImpl } from \"./mailer.js\";\n\n/** Detect the current JavaScript runtime. */\nexport function detectRuntime(): Runtime {\n if (typeof Bun !== \"undefined\") {\n return \"bun\";\n }\n if (typeof Deno !== \"undefined\") {\n return \"deno\";\n }\n if (typeof caches !== \"undefined\" && globalThis.navigator?.userAgent === \"Cloudflare-Workers\") {\n return \"cf-workers\";\n }\n if (typeof window !== \"undefined\") {\n return \"browser\";\n }\n if (typeof process !== \"undefined\" && process.versions?.node) {\n return \"node\";\n }\n return \"unknown\";\n}\n\n/**\n * Dynamically import and instantiate the correct adapter for the current runtime.\n */\nexport async function createDefaultAdapter(options?: {\n secure?: boolean;\n connectionTimeout?: number;\n tls?: TLSOptions;\n}): Promise<SocketAdapter> {\n const runtime = detectRuntime();\n\n switch (runtime) {\n case \"node\": {\n const { NodeAdapter } = await import(\"./adapters/node.js\");\n return new NodeAdapter(options);\n }\n case \"bun\": {\n const { BunAdapter } = await import(\"./adapters/bun.js\");\n return new BunAdapter(options);\n }\n case \"deno\": {\n const { DenoAdapter } = await import(\"./adapters/deno.js\");\n return new DenoAdapter(options);\n }\n case \"cf-workers\": {\n const { CloudflareAdapter } = await import(\"./adapters/cf.js\");\n return new CloudflareAdapter(options);\n }\n default:\n throw new Error(`No socket adapter available for runtime: ${runtime}`);\n }\n}\n\n/**\n * Create a ready-to-use Mailer instance.\n *\n * For HTTP transports and smallest bundles, prefer `import { createMailer } from \"sently/mailer\"`.\n */\nexport async function createMailer(options: CreateMailerOptions): Promise<Mailer> {\n if (\"transport\" in options) {\n return createTransportMailer({\n transport: options.transport,\n ...(options.plugins !== undefined ? { plugins: options.plugins } : {}),\n ...(options.hooks !== undefined ? { hooks: options.hooks } : {}),\n });\n }\n\n const smtpConfig = options as SMTPConfig & { hooks?: MailerHooks };\n const adapterOptions = {\n ...(smtpConfig.secure !== undefined ? { secure: smtpConfig.secure } : {}),\n ...(smtpConfig.connectionTimeout !== undefined\n ? { connectionTimeout: smtpConfig.connectionTimeout }\n : {}),\n ...(smtpConfig.tls !== undefined ? { tls: smtpConfig.tls } : {}),\n };\n\n if (smtpConfig.pool) {\n const { SMTPPool } = await import(\"./pool/pool.js\");\n return new MailerImpl(\n new SMTPPool(smtpConfig, {\n createAdapter: async () =>\n smtpConfig.adapter ?? (await createDefaultAdapter(adapterOptions)),\n }),\n smtpConfig.plugins,\n smtpConfig.hooks,\n );\n }\n\n const adapter = smtpConfig.adapter ?? (await createDefaultAdapter(adapterOptions));\n const { SMTPTransport } = await import(\"./transports/smtp.js\");\n\n return new MailerImpl(\n new SMTPTransport({ ...smtpConfig, adapter }),\n smtpConfig.plugins,\n smtpConfig.hooks,\n );\n}\n\ndeclare const Bun: unknown;\ndeclare const Deno: unknown;\n"
6
6
  ],
7
- "mappings": "+HAYO,GAAS,LAAa,EAAY,CACvC,GAAI,OAAO,IAAQ,IACjB,MAAO,MAET,GAAI,OAAO,KAAS,IAClB,MAAO,OAET,GAAI,OAAO,OAAW,KAAe,WAAW,WAAW,YAAc,qBACvE,MAAO,aAET,GAAI,OAAO,OAAW,IACpB,MAAO,UAET,GAAI,OAAO,QAAY,KAAe,QAAQ,UAAU,KACtD,MAAO,OAET,MAAO,UAMT,eAAsB,CAAoB,CAAC,EAIhB,CACzB,IAAM,EAAU,EAAc,EAE9B,OAAQ,OACD,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,MAAO,CACV,IAAQ,cAAe,KAAa,6BACpC,OAAO,IAAI,EAAW,CAAO,CAC/B,KACK,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,aAAc,CACjB,IAAQ,qBAAsB,KAAa,4BAC3C,OAAO,IAAI,EAAkB,CAAO,CACtC,SAEE,MAAU,MAAM,4CAA4C,GAAS,GAS3E,eAAsB,CAAY,CAAC,EAA+C,CAChF,GAAI,cAAe,EACjB,OAAO,EAAsB,CAC3B,UAAW,EAAQ,aACf,EAAQ,UAAY,OAAY,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CACtE,CAAC,EAGH,IAAM,EAAa,EACb,EAAiB,IACjB,EAAW,SAAW,OAAY,CAAE,OAAQ,EAAW,MAAO,EAAI,CAAC,KACnE,EAAW,oBAAsB,OACjC,CAAE,kBAAmB,EAAW,iBAAkB,EAClD,CAAC,KACD,EAAW,MAAQ,OAAY,CAAE,IAAK,EAAW,GAAI,EAAI,CAAC,CAChE,EAEA,GAAI,EAAW,KAAM,CACnB,IAAQ,YAAa,KAAa,0BAClC,OAAO,IAAI,EACT,IAAI,EAAS,EAAY,CACvB,cAAe,SACb,EAAW,SAAY,MAAM,EAAqB,CAAc,CACpE,CAAC,EACD,EAAW,OACb,EAGF,IAAM,EAAU,EAAW,SAAY,MAAM,EAAqB,CAAc,GACxE,iBAAkB,KAAa,gCAEvC,OAAO,IAAI,EAAW,IAAI,EAAc,IAAK,EAAY,SAAQ,CAAC,EAAG,EAAW,OAAO",
8
- "debugId": "E95737E930123F1064756E2164756E21",
7
+ "mappings": "ySAaO,GAAS,LAAa,JAAY,LACvC,HAAI,CAAO,FAAQ,IACjB,MAAO,MAET,GAAI,OAAO,KAAS,IAClB,MAAO,OAET,GAAI,OAAO,OAAW,KAAe,WAAW,WAAW,YAAc,qBACvE,MAAO,aAET,GAAI,OAAO,OAAW,IACpB,MAAO,UAET,GAAI,OAAO,QAAY,KAAe,QAAQ,UAAU,KACtD,MAAO,OAET,MAAO,UAMT,eAAsB,CAAoB,CAAC,EAIhB,CACzB,IAAM,EAAU,EAAc,EAE9B,OAAQ,OACD,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,MAAO,CACV,IAAQ,cAAe,KAAa,6BACpC,OAAO,IAAI,EAAW,CAAO,CAC/B,KACK,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,aAAc,CACjB,IAAQ,qBAAsB,KAAa,4BAC3C,OAAO,IAAI,EAAkB,CAAO,CACtC,SAEE,MAAU,MAAM,4CAA4C,GAAS,GAS3E,eAAsB,CAAY,CAAC,EAA+C,CAChF,GAAI,cAAe,EACjB,OAAO,EAAsB,CAC3B,UAAW,EAAQ,aACf,EAAQ,UAAY,OAAY,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,KAChE,EAAQ,QAAU,OAAY,CAAE,MAAO,EAAQ,KAAM,EAAI,CAAC,CAChE,CAAC,EAGH,IAAM,EAAa,EACb,EAAiB,IACjB,EAAW,SAAW,OAAY,CAAE,OAAQ,EAAW,MAAO,EAAI,CAAC,KACnE,EAAW,oBAAsB,OACjC,CAAE,kBAAmB,EAAW,iBAAkB,EAClD,CAAC,KACD,EAAW,MAAQ,OAAY,CAAE,IAAK,EAAW,GAAI,EAAI,CAAC,CAChE,EAEA,GAAI,EAAW,KAAM,CACnB,IAAQ,YAAa,KAAa,0BAClC,OAAO,IAAI,EACT,IAAI,EAAS,EAAY,CACvB,cAAe,SACb,EAAW,SAAY,MAAM,EAAqB,CAAc,CACpE,CAAC,EACD,EAAW,QACX,EAAW,KACb,EAGF,IAAM,EAAU,EAAW,SAAY,MAAM,EAAqB,CAAc,GACxE,iBAAkB,KAAa,gCAEvC,OAAO,IAAI,EACT,IAAI,EAAc,IAAK,EAAY,SAAQ,CAAC,EAC5C,EAAW,QACX,EAAW,KACb",
8
+ "debugId": "29F94DB13623212064756E2164756E21",
9
9
  "names": []
10
10
  }
package/dist/dkim.js CHANGED
@@ -1,4 +1,4 @@
1
- import{N as G,Q as E}from"./chunk-gxq2aamv.js";import"./chunk-mg8m26nr.js";var j=`\r
1
+ import{O as G,R as E}from"./chunk-wzdcz88x.js";import"./chunk-rszb1n1d.js";var j=`\r
2
2
  `,C="from:to:subject:date:message-id:mime-version:content-type";function _(M,I){let x=F(M),K=[];for(let S of I){let Y=S.toLowerCase().trim(),z=x.get(Y);if(!z)continue;for(let D of z){let w=D.replace(/\r?\n/g,"").replace(/\s+/g," ").trim();K.push(`${Y}:${w}`)}}return K.length>0?`${K.join(j)}${j}`:""}function $(M){let x=M.replace(/\r\n/g,`
3
3
  `).replace(/\r/g,`
4
4
  `).split(`
@@ -6,4 +6,4 @@ import{N as G,Q as E}from"./chunk-gxq2aamv.js";import"./chunk-mg8m26nr.js";var j
6
6
  \r
7
7
  `),w=D>=0?z.slice(0,D):z,P=D>=0?z.slice(D+4):"",T=$(P),O=await k(E(T)),Q=x==="ed25519-sha256"?"ed25519-sha256":"rsa-sha256",X=Y.join(":"),Z=Math.floor(Date.now()/1000),W=["v=1",`a=${Q}`,"c=relaxed/relaxed",`d=${I.domainName}`,`s=${I.keySelector}`,`h=${X}`,`bh=${O}`,"b=",`t=${Z}`].join("; "),b="dkim-signature",H=`${w}${j}dkim-signature:${W}`,p=_(H,[...Y,"dkim-signature"]),A=await v(I.privateKey,x),R=E(p),U=await L(A,R,x),V=G(U).replace(/\r\n/g,"");return{header:`DKIM-Signature: v=1; a=${Q}; c=relaxed/relaxed; d=${I.domainName}; s=${I.keySelector}; h=${X}; bh=${O}; b=${V}; t=${Z}`}}function F(M){let I=new Map,x=M.split(/\r?\n/).filter((z)=>z.length>0),K="",S="",Y=()=>{if(!K)return;let z=K.toLowerCase(),D=I.get(z)??[];D.push(S),I.set(z,D),K="",S=""};for(let z of x){if(/^[ \t]/.test(z)&&K){S+=` ${z.trim()}`;continue}Y();let D=z.indexOf(":");if(D>0)K=z.slice(0,D).trim(),S=z.slice(D+1).trim()}return Y(),I}function B(M){let I=M.replace(/-----BEGIN[^-]+-----/g,"").replace(/-----END[^-]+-----/g,"").replace(/\s/g,""),x=atob(I),K=new Uint8Array(x.length);for(let S=0;S<x.length;S++)K[S]=x.charCodeAt(S);return K}function J(M){return M.buffer.slice(M.byteOffset,M.byteOffset+M.byteLength)}async function k(M){let I=await crypto.subtle.digest("SHA-256",J(M));return G(new Uint8Array(I)).replace(/\r\n/g,"")}async function L(M,I,x){let K=J(I);if(x==="ed25519-sha256"){let Y=await crypto.subtle.sign("Ed25519",M,K);return new Uint8Array(Y)}let S=await crypto.subtle.sign({name:"RSASSA-PKCS1-v1_5"},M,K);return new Uint8Array(S)}export{N as signDKIM,v as importPrivateKey,_ as canonicalizeHeadersRelaxed,$ as canonicalizeBodyRelaxed};
8
8
 
9
- //# debugId=C154E3241728AA9864756E2164756E21
9
+ //# debugId=A424387239B7E1A864756E2164756E21
package/dist/dkim.js.map CHANGED
@@ -5,6 +5,6 @@
5
5
  "/**\n * DKIM signing implementation (RFC 6376). Public entry: `sently/dkim`.\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
6
  ],
7
7
  "mappings": "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",
8
- "debugId": "C154E3241728AA9864756E2164756E21",
8
+ "debugId": "A424387239B7E1A864756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module
3
+ * Unified sently error types — import from `sently/errors`.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { SentlyError } from "sently/errors";
8
+ * import { ResendError } from "sently/transports/resend";
9
+ *
10
+ * try {
11
+ * await mailer.send({ ... });
12
+ * } catch (err) {
13
+ * if (err instanceof SentlyError) {
14
+ * console.error(err.sentlyCode, err.statusCode);
15
+ * }
16
+ * if (err instanceof ResendError) {
17
+ * console.error(err.statusCode, err.apiError);
18
+ * }
19
+ * }
20
+ * ```
21
+ */
22
+ export type { SentlyErrorCode, SentlyErrorOptions } from "./core/errors.js";
23
+ export {
24
+ /** Map an HTTP status code to a stable sently error code. */
25
+ httpStatusToSentlyCode,
26
+ /** Base error class for all sently transport and protocol failures. */
27
+ SentlyError,
28
+ /** Map an SMTP response code to a stable sently error code. */
29
+ smtpCodeToSentlyCode, } from "./core/errors.js";
package/dist/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ export {
2
+
3
+ httpStatusToSentlyCode,
4
+
5
+ SentlyError,
6
+
7
+ smtpCodeToSentlyCode,
8
+ } from "./core/errors.js";
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "2BC101DB496C40DA64756E2164756E21",
8
+ "names": []
9
+ }
@@ -1,3 +1,3 @@
1
- import{q as F}from"./chunk-qp4y0kp2.js";import{u as C,w as B}from"./chunk-b0qkndv9.js";import"./chunk-gxq2aamv.js";import"./chunk-mg8m26nr.js";class G{entries=new Map;async has(q){let w=this.entries.get(q);if(w===void 0)return!1;if(w<=Date.now())return this.entries.delete(q),!1;return!0}async set(q,w){let z=w!==void 0?Date.now()+w:Number.POSITIVE_INFINITY;this.entries.set(q,z)}}function H(q){let w=C(q.from)[0],z=[...B(q.to),...q.cc?B(q.cc):[],...q.bcc?B(q.bcc):[]];return{messageId:q.messageId??"",accepted:B(q.to),rejected:[],response:"Deduped — idempotency key already recorded",envelope:{from:w?.address??"",to:z},deduped:!0}}class J{inner;store;ttlMs;constructor(q,w){this.inner=q;this.store=w?.store??new G,this.ttlMs=w?.ttlMs??86400000}async send(q){let w=F(q);if(w===void 0)return this.inner.send(q);if(await this.store.has(w))return H(q);let z=await this.inner.send(q);return await this.store.set(w,this.ttlMs),z}async sendBatch(q){if(this.inner.sendBatch)return this.inner.sendBatch(q);return Promise.all(q.map((w)=>this.send(w)))}async verify(){if(this.inner.verify)return this.inner.verify();return{ok:!0,provider:"idempotency"}}async close(){await this.inner.close?.()}}export{G as MemoryIdempotencyStore,J as IdempotencyTransport};
1
+ import{q as F}from"./chunk-qp4y0kp2.js";import{A as B,y as C}from"./chunk-y4qxdyqn.js";import"./chunk-wzdcz88x.js";import"./chunk-rszb1n1d.js";class G{entries=new Map;async has(q){let w=this.entries.get(q);if(w===void 0)return!1;if(w<=Date.now())return this.entries.delete(q),!1;return!0}async set(q,w){let z=w!==void 0?Date.now()+w:Number.POSITIVE_INFINITY;this.entries.set(q,z)}}function H(q){let w=C(q.from)[0],z=[...B(q.to),...q.cc?B(q.cc):[],...q.bcc?B(q.bcc):[]];return{messageId:q.messageId??"",accepted:B(q.to),rejected:[],response:"Deduped — idempotency key already recorded",envelope:{from:w?.address??"",to:z},deduped:!0}}class J{inner;store;ttlMs;constructor(q,w){this.inner=q;this.store=w?.store??new G,this.ttlMs=w?.ttlMs??86400000}async send(q){let w=F(q);if(w===void 0)return this.inner.send(q);if(await this.store.has(w))return H(q);let z=await this.inner.send(q);return await this.store.set(w,this.ttlMs),z}async sendBatch(q){if(this.inner.sendBatch)return this.inner.sendBatch(q);return Promise.all(q.map((w)=>this.send(w)))}async verify(){if(this.inner.verify)return this.inner.verify();return{ok:!0,provider:"idempotency"}}async close(){await this.inner.close?.()}}export{G as MemoryIdempotencyStore,J as IdempotencyTransport};
2
2
 
3
- //# debugId=8CC54845D79ED62264756E2164756E21
3
+ //# debugId=D8BBD409B64A4DE564756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * IdempotencyTransport — decorator that deduplicates sends on retry or replay.\n *\n * Wrap **outside** RetryTransport so all retry attempts share one key:\n *\n * ```ts\n * new IdempotencyTransport(new RetryTransport(inner))\n * ```\n *\n * For production, supply a shared store (Redis, Dragonfly, etc.).\n *\n * @example\n * ```ts\n * import { IdempotencyTransport, MemoryIdempotencyStore } from \"sently/idempotency\";\n * import { RetryTransport } from \"sently/transports/retry\";\n * import { ResendTransport } from \"sently/transports/resend\";\n *\n * const transport = new IdempotencyTransport(\n * new RetryTransport(new ResendTransport({ apiKey })),\n * { store: new MemoryIdempotencyStore(), ttlMs: 86_400_000 },\n * );\n * ```\n */\nimport { extractEmails, parseAddresses } from \"./core/address.js\";\nimport { resolveIdempotencyKey } from \"./core/idempotency-key.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"./core/types.js\";\n\n/** Key-value store for idempotency deduplication. */\nexport interface IdempotencyStore {\n /** Returns true when the key was already recorded. */\n has(key: string): Promise<boolean>;\n /** Record a key, optionally expiring after `ttlMs` milliseconds. */\n set(key: string, ttlMs?: number): Promise<void>;\n}\n\n/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, number>();\n\n /** Returns true when the key exists and has not expired. */\n async has(key: string): Promise<boolean> {\n const expiresAt = this.entries.get(key);\n if (expiresAt === undefined) {\n return false;\n }\n if (expiresAt <= Date.now()) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n /** Record a key with optional TTL in milliseconds. */\n async set(key: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs !== undefined ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;\n this.entries.set(key, expiresAt);\n }\n}\n\n/** Options for {@link IdempotencyTransport}. */\nexport interface IdempotencyTransportOptions {\n /** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */\n store?: IdempotencyStore;\n /** TTL in milliseconds for recorded keys. Default: 24 hours. */\n ttlMs?: number;\n}\n\nfunction syntheticDedupedResult(options: MailOptions): SendResult {\n const from = parseAddresses(options.from)[0];\n const to = [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ];\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Deduped — idempotency key already recorded\",\n envelope: {\n from: from?.address ?? \"\",\n to,\n },\n deduped: true,\n };\n}\n\n/**\n * Transport decorator that skips duplicate sends when an idempotency key\n * was already recorded. Checks the store once before delegating to the inner\n * transport, so retries inside RetryTransport reuse the same key automatically.\n */\nexport class IdempotencyTransport implements Transport {\n private readonly store: IdempotencyStore;\n private readonly ttlMs: number;\n\n /** Wraps an inner transport with idempotency deduplication. */\n constructor(\n /** Transport that performs the actual send when the key is new. */\n private readonly inner: Transport,\n options?: IdempotencyTransportOptions,\n ) {\n this.store = options?.store ?? new MemoryIdempotencyStore();\n this.ttlMs = options?.ttlMs ?? 86_400_000;\n }\n\n /** Sends once per idempotency key; returns a synthetic result on duplicate keys. */\n async send(options: MailOptions): Promise<SendResult> {\n const key = resolveIdempotencyKey(options);\n if (key === undefined) {\n return this.inner.send(options);\n }\n\n if (await this.store.has(key)) {\n return syntheticDedupedResult(options);\n }\n\n const result = await this.inner.send(options);\n await this.store.set(key, this.ttlMs);\n return result;\n }\n\n /** Delegates batch sends to the inner transport when available. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (this.inner.sendBatch) {\n return this.inner.sendBatch(messages);\n }\n return Promise.all(messages.map((message) => this.send(message)));\n }\n\n /** Delegates to the inner transport verify or returns a default success result. */\n async verify(): Promise<VerifyResult> {\n if (this.inner.verify) {\n return this.inner.verify();\n }\n return { ok: true, provider: \"idempotency\" };\n }\n\n /** Delegates close to the inner transport if available. */\n async close(): Promise<void> {\n await this.inner.close?.();\n }\n}\n"
6
6
  ],
7
7
  "mappings": "iKAqCO,AAAM,LAAmD,LAC7C,QAAU,IAAI,SAGzB,IAAG,CAAC,EAA+B,CACvC,IAAM,EAAY,KAAK,QAAQ,IAAI,CAAG,EACtC,GAAI,IAAc,OAChB,MAAO,GAET,GAAI,GAAa,KAAK,IAAI,EAExB,OADA,KAAK,QAAQ,OAAO,CAAG,EAChB,GAET,MAAO,QAIH,IAAG,CAAC,EAAa,EAA+B,CACpD,IAAM,EAAY,IAAU,OAAY,KAAK,IAAI,EAAI,EAAQ,OAAO,kBACpE,KAAK,QAAQ,IAAI,EAAK,CAAS,EAEnC,CAUA,SAAS,CAAsB,CAAC,EAAkC,CAChE,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAK,CACT,GAAG,EAAc,EAAQ,EAAE,EAC3B,GAAI,EAAQ,GAAK,EAAc,EAAQ,EAAE,EAAI,CAAC,EAC9C,GAAI,EAAQ,IAAM,EAAc,EAAQ,GAAG,EAAI,CAAC,CAClD,EAEA,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,6CACV,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,IACF,EACA,QAAS,EACX,EAQK,MAAM,CAA0C,CAOlC,MANF,MACA,MAGjB,WAAW,CAEQ,EACjB,EACA,CAFiB,aAGjB,KAAK,MAAQ,GAAS,OAAS,IAAI,EACnC,KAAK,MAAQ,GAAS,OAAS,cAI3B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAM,EAAsB,CAAO,EACzC,GAAI,IAAQ,OACV,OAAO,KAAK,MAAM,KAAK,CAAO,EAGhC,GAAI,MAAM,KAAK,MAAM,IAAI,CAAG,EAC1B,OAAO,EAAuB,CAAO,EAGvC,IAAM,EAAS,MAAM,KAAK,MAAM,KAAK,CAAO,EAE5C,OADA,MAAM,KAAK,MAAM,IAAI,EAAK,KAAK,KAAK,EAC7B,OAIH,UAAS,CAAC,EAAgD,CAC9D,GAAI,KAAK,MAAM,UACb,OAAO,KAAK,MAAM,UAAU,CAAQ,EAEtC,OAAO,QAAQ,IAAI,EAAS,IAAI,CAAC,IAAY,KAAK,KAAK,CAAO,CAAC,CAAC,OAI5D,OAAM,EAA0B,CACpC,GAAI,KAAK,MAAM,OACb,OAAO,KAAK,MAAM,OAAO,EAE3B,MAAO,CAAE,GAAI,GAAM,SAAU,aAAc,OAIvC,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,QAAQ,EAE7B",
8
- "debugId": "8CC54845D79ED62264756E2164756E21",
8
+ "debugId": "D8BBD409B64A4DE564756E2164756E21",
9
9
  "names": []
10
10
  }