sently 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +86 -98
  3. package/dist/adapters/bun.js +1 -1
  4. package/dist/adapters/bun.js.map +1 -1
  5. package/dist/adapters/cf.d.ts +2 -2
  6. package/dist/adapters/cf.js +2 -2
  7. package/dist/adapters/cf.js.map +2 -2
  8. package/dist/adapters/deno.d.ts +2 -2
  9. package/dist/adapters/deno.js +1 -1
  10. package/dist/adapters/deno.js.map +1 -1
  11. package/dist/adapters/node.js +1 -1
  12. package/dist/adapters/node.js.map +1 -1
  13. package/dist/auth/oauth2.js +1 -1
  14. package/dist/{chunk-qnz9z0d4.js → chunk-4mvp3b85.js} +2 -2
  15. package/dist/chunk-4mvp3b85.js.map +10 -0
  16. package/dist/chunk-74rha3ac.js +4 -0
  17. package/dist/chunk-74rha3ac.js.map +10 -0
  18. package/dist/{chunk-n601jhpd.js → chunk-a1c87hn1.js} +2 -2
  19. package/dist/chunk-a1c87hn1.js.map +10 -0
  20. package/dist/chunk-cvrbfke9.js +13 -0
  21. package/dist/{chunk-w3fnjh04.js.map → chunk-cvrbfke9.js.map} +2 -2
  22. package/dist/{chunk-wzdcz88x.js → chunk-d5rwm3br.js} +2 -2
  23. package/dist/{chunk-wzdcz88x.js.map → chunk-d5rwm3br.js.map} +1 -1
  24. package/dist/{chunk-y4qxdyqn.js → chunk-gh5n759p.js} +3 -3
  25. package/dist/{chunk-y4qxdyqn.js.map → chunk-gh5n759p.js.map} +1 -1
  26. package/dist/{chunk-y72qggqf.js → chunk-gkkv7yqm.js} +2 -2
  27. package/dist/{chunk-y72qggqf.js.map → chunk-gkkv7yqm.js.map} +1 -1
  28. package/dist/chunk-ka6t8g77.js +4 -0
  29. package/dist/chunk-ka6t8g77.js.map +11 -0
  30. package/dist/{chunk-tfxje74k.js → chunk-kebhy657.js} +2 -2
  31. package/dist/{chunk-tfxje74k.js.map → chunk-kebhy657.js.map} +1 -1
  32. package/dist/{chunk-rszb1n1d.js → chunk-keksa6fx.js} +2 -2
  33. package/dist/{chunk-rszb1n1d.js.map → chunk-keksa6fx.js.map} +1 -1
  34. package/dist/{chunk-z7j2n9k8.js → chunk-r7ee9tk7.js} +2 -2
  35. package/dist/{chunk-z7j2n9k8.js.map → chunk-r7ee9tk7.js.map} +1 -1
  36. package/dist/{chunk-348wndn3.js → chunk-rm78s1ek.js} +2 -2
  37. package/dist/{chunk-348wndn3.js.map → chunk-rm78s1ek.js.map} +1 -1
  38. package/dist/core/errors.d.ts +1 -1
  39. package/dist/core/errors.js +2 -2
  40. package/dist/core/errors.js.map +1 -1
  41. package/dist/core/smtp.js +2 -2
  42. package/dist/core/smtp.js.map +1 -1
  43. package/dist/core/types.d.ts +4 -8
  44. package/dist/detect.d.ts +1 -7
  45. package/dist/detect.js +2 -2
  46. package/dist/detect.js.map +3 -4
  47. package/dist/dkim.js +2 -2
  48. package/dist/dkim.js.map +1 -1
  49. package/dist/idempotency.js +2 -2
  50. package/dist/idempotency.js.map +2 -2
  51. package/dist/index.d.ts +12 -98
  52. package/dist/index.js +4 -98
  53. package/dist/mailer.js +2 -2
  54. package/dist/mailer.js.map +1 -1
  55. package/dist/plugins/react.js +2 -2
  56. package/dist/plugins/react.js.map +1 -1
  57. package/dist/plugins/template.js +1 -1
  58. package/dist/pool/pool.js +2 -2
  59. package/dist/pool/pool.js.map +1 -1
  60. package/dist/smtp-mailer.d.ts +23 -0
  61. package/dist/smtp-mailer.js +3 -0
  62. package/dist/smtp-mailer.js.map +10 -0
  63. package/dist/transports/brevo.js +2 -2
  64. package/dist/transports/brevo.js.map +2 -2
  65. package/dist/transports/mailgun.js +2 -2
  66. package/dist/transports/mailgun.js.map +3 -3
  67. package/dist/transports/postmark.js +2 -2
  68. package/dist/transports/postmark.js.map +3 -3
  69. package/dist/transports/preview.js +2 -2
  70. package/dist/transports/preview.js.map +1 -1
  71. package/dist/transports/resend.js +2 -2
  72. package/dist/transports/resend.js.map +2 -2
  73. package/dist/transports/retry.js +2 -2
  74. package/dist/transports/retry.js.map +4 -3
  75. package/dist/transports/sendgrid.js +2 -2
  76. package/dist/transports/sendgrid.js.map +2 -2
  77. package/dist/transports/ses.js +2 -2
  78. package/dist/transports/ses.js.map +2 -2
  79. package/dist/transports/smtp.d.ts +1 -0
  80. package/dist/transports/smtp.js +2 -2
  81. package/dist/transports/smtp.js.map +1 -1
  82. package/dist/webhooks/brevo.js +1 -1
  83. package/dist/webhooks/mailgun.js +1 -1
  84. package/dist/webhooks/postmark.js +1 -1
  85. package/dist/webhooks/resend.js +1 -1
  86. package/dist/webhooks/sendgrid.js +1 -1
  87. package/dist/webhooks/ses.js +1 -1
  88. package/dist/webhooks/timing-safe-equal.js +1 -1
  89. package/package.json +5 -1
  90. package/dist/chunk-3y1tr1vt.js +0 -4
  91. package/dist/chunk-3y1tr1vt.js.map +0 -11
  92. package/dist/chunk-n601jhpd.js.map +0 -10
  93. package/dist/chunk-qnz9z0d4.js.map +0 -10
  94. package/dist/chunk-t0en64h9.js +0 -4
  95. package/dist/chunk-t0en64h9.js.map +0 -10
  96. package/dist/chunk-w3fnjh04.js +0 -13
@@ -1,4 +1,4 @@
1
- import{O as D,R as J}from"./chunk-wzdcz88x.js";var Q="https://oauth2.googleapis.com/token",$="https://login.microsoftonline.com/common/oauth2/v2.0/token",V=30000;class W{config;cachedToken=null;expiresAt=0;refreshPromise=null;constructor(m){if(this.config=m,m.accessToken)this.cachedToken=m.accessToken,this.expiresAt=Date.now()+3600000}async getAccessToken(){if(this.config.getToken)return this.config.getToken();if(this.cachedToken&&Date.now()<this.expiresAt-V)return this.cachedToken;if(!this.refreshPromise)this.refreshPromise=this.refreshAccessToken().finally(()=>{this.refreshPromise=null});return this.refreshPromise}async refreshAccessToken(){if(this.config.getToken){let v=await this.config.getToken();return this.cachedToken=v,this.expiresAt=Date.now()+3600000,v}let m=this.config.tokenUrl??Q,z=new URLSearchParams({client_id:this.config.clientId,client_secret:this.config.clientSecret,refresh_token:this.config.refreshToken,grant_type:"refresh_token"}),q=await fetch(m,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:z.toString()});if(!q.ok){let v=await q.text();throw Error(`OAuth2 token refresh failed (${q.status}): ${v}`)}let C=await q.json();return this.cachedToken=C.access_token,this.expiresAt=Date.now()+C.expires_in*1000,C.access_token}async buildXOAUTH2(){let m=await this.getAccessToken(),z=`user=${this.config.user}\x01auth=Bearer ${m}\x01\x01`;return D(J(z)).replace(/\r\n/g,"")}}
1
+ import{P as D,S as J}from"./chunk-d5rwm3br.js";var Q="https://oauth2.googleapis.com/token",$="https://login.microsoftonline.com/common/oauth2/v2.0/token",V=30000;class W{config;cachedToken=null;expiresAt=0;refreshPromise=null;constructor(m){if(this.config=m,m.accessToken)this.cachedToken=m.accessToken,this.expiresAt=Date.now()+3600000}async getAccessToken(){if(this.config.getToken)return this.config.getToken();if(this.cachedToken&&Date.now()<this.expiresAt-V)return this.cachedToken;if(!this.refreshPromise)this.refreshPromise=this.refreshAccessToken().finally(()=>{this.refreshPromise=null});return this.refreshPromise}async refreshAccessToken(){if(this.config.getToken){let v=await this.config.getToken();return this.cachedToken=v,this.expiresAt=Date.now()+3600000,v}let m=this.config.tokenUrl??Q,z=new URLSearchParams({client_id:this.config.clientId,client_secret:this.config.clientSecret,refresh_token:this.config.refreshToken,grant_type:"refresh_token"}),q=await fetch(m,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:z.toString()});if(!q.ok){let v=await q.text();throw Error(`OAuth2 token refresh failed (${q.status}): ${v}`)}let C=await q.json();return this.cachedToken=C.access_token,this.expiresAt=Date.now()+C.expires_in*1000,C.access_token}async buildXOAUTH2(){let m=await this.getAccessToken(),z=`user=${this.config.user}\x01auth=Bearer ${m}\x01\x01`;return D(J(z)).replace(/\r\n/g,"")}}
2
2
  export{Q as n,$ as o,W as p};
3
3
 
4
- //# debugId=46CE46483A53D9B364756E2164756E21
4
+ //# debugId=C457D8666BDFCFF264756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * OAuth2 / XOAUTH2 authentication for SMTP.\n * Supports Gmail, Microsoft 365, and custom OAuth2 providers.\n *\n * @example\n * ```ts\n * import { OAuth2Client } from \"sently/auth/oauth2\";\n * const client = new OAuth2Client({\n * user: \"me@gmail.com\",\n * clientId: \"...\",\n * clientSecret: \"...\",\n * refreshToken: \"...\",\n * });\n * const token = await client.getAccessToken();\n * ```\n */\nimport { encodeBase64, encodeUtf8 } from \"../core/base64.js\";\nimport type { OAuth2Config } from \"../core/types.js\";\n\n/** Default Google OAuth2 token endpoint. */\nexport const GOOGLE_TOKEN_URL = \"https://oauth2.googleapis.com/token\";\n\n/** Microsoft OAuth2 token endpoint (common tenant). */\nexport const MICROSOFT_TOKEN_URL = \"https://login.microsoftonline.com/common/oauth2/v2.0/token\";\n\n/** OAuth2 token endpoint response shape. */\nexport interface TokenResponse {\n /** Bearer access token for API or SMTP XOAUTH2. */\n access_token: string;\n /** Token lifetime in seconds. */\n expires_in: number;\n /** Token type (typically `\"Bearer\"`). */\n token_type: string;\n}\n\nconst EXPIRY_BUFFER_MS = 30_000;\n\n/**\n * OAuth2 client with in-memory token cache and automatic refresh.\n */\nexport class OAuth2Client {\n /** OAuth2 configuration supplied at construction. */\n private readonly config: OAuth2Config;\n /** Cached access token, or null before the first fetch. */\n private cachedToken: string | null = null;\n /** Expiry timestamp (ms) for the cached access token. */\n private expiresAt = 0;\n /** In-flight refresh promise used to deduplicate concurrent refreshes. */\n private refreshPromise: Promise<string> | null = null;\n\n /** Creates an OAuth2 client from configuration. */\n constructor(config: OAuth2Config) {\n this.config = config;\n if (config.accessToken) {\n this.cachedToken = config.accessToken;\n this.expiresAt = Date.now() + 3_600_000;\n }\n }\n\n /**\n * Get a valid access token (cached when still valid, refreshed otherwise).\n */\n async getAccessToken(): Promise<string> {\n if (this.config.getToken) {\n return this.config.getToken();\n }\n\n if (this.cachedToken && Date.now() < this.expiresAt - EXPIRY_BUFFER_MS) {\n return this.cachedToken;\n }\n\n if (!this.refreshPromise) {\n this.refreshPromise = this.refreshAccessToken().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n\n /**\n * Force-refresh the access token regardless of expiry.\n */\n async refreshAccessToken(): Promise<string> {\n if (this.config.getToken) {\n const token = await this.config.getToken();\n this.cachedToken = token;\n this.expiresAt = Date.now() + 3_600_000;\n return token;\n }\n\n const tokenUrl = this.config.tokenUrl ?? GOOGLE_TOKEN_URL;\n const body = new URLSearchParams({\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n refresh_token: this.config.refreshToken,\n grant_type: \"refresh_token\",\n });\n\n const response = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OAuth2 token refresh failed (${response.status}): ${text}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n this.cachedToken = data.access_token;\n this.expiresAt = Date.now() + data.expires_in * 1000;\n return data.access_token;\n }\n\n /**\n * Build the XOAUTH2 SASL string for SMTP AUTH (base64-encoded).\n */\n async buildXOAUTH2(): Promise<string> {\n const token = await this.getAccessToken();\n const raw = `user=${this.config.user}\\x01auth=Bearer ${token}\\x01\\x01`;\n return encodeBase64(encodeUtf8(raw)).replace(/\\r\\n/g, \"\");\n }\n}\n"
6
6
  ],
7
7
  "mappings": "+CAqBO,IAAM,EAAmB,sCAGnB,EAAsB,6DAY7B,EAAmB,MAKlB,MAAM,CAAa,CAEP,OAET,YAA6B,KAE7B,UAAY,EAEZ,eAAyC,KAGjD,WAAW,CAAC,EAAsB,CAEhC,GADA,KAAK,OAAS,EACV,EAAO,YACT,KAAK,YAAc,EAAO,YAC1B,KAAK,UAAY,KAAK,IAAI,EAAI,aAO5B,eAAc,EAAoB,CACtC,GAAI,KAAK,OAAO,SACd,OAAO,KAAK,OAAO,SAAS,EAG9B,GAAI,KAAK,aAAe,KAAK,IAAI,EAAI,KAAK,UAAY,EACpD,OAAO,KAAK,YAGd,GAAI,CAAC,KAAK,eACR,KAAK,eAAiB,KAAK,mBAAmB,EAAE,QAAQ,IAAM,CAC5D,KAAK,eAAiB,KACvB,EAEH,OAAO,KAAK,oBAMR,mBAAkB,EAAoB,CAC1C,GAAI,KAAK,OAAO,SAAU,CACxB,IAAM,EAAQ,MAAM,KAAK,OAAO,SAAS,EAGzC,OAFA,KAAK,YAAc,EACnB,KAAK,UAAY,KAAK,IAAI,EAAI,QACvB,EAGT,IAAM,EAAW,KAAK,OAAO,UAAY,EACnC,EAAO,IAAI,gBAAgB,CAC/B,UAAW,KAAK,OAAO,SACvB,cAAe,KAAK,OAAO,aAC3B,cAAe,KAAK,OAAO,aAC3B,WAAY,eACd,CAAC,EAEK,EAAW,MAAM,MAAM,EAAU,CACrC,OAAQ,OACR,QAAS,CAAE,eAAgB,mCAAoC,EAC/D,KAAM,EAAK,SAAS,CACtB,CAAC,EAED,GAAI,CAAC,EAAS,GAAI,CAChB,IAAM,EAAO,MAAM,EAAS,KAAK,EACjC,MAAU,MAAM,gCAAgC,EAAS,YAAY,GAAM,EAG7E,IAAM,EAAQ,MAAM,EAAS,KAAK,EAGlC,OAFA,KAAK,YAAc,EAAK,aACxB,KAAK,UAAY,KAAK,IAAI,EAAI,EAAK,WAAa,KACzC,EAAK,kBAMR,aAAY,EAAoB,CACpC,IAAM,EAAQ,MAAM,KAAK,eAAe,EAClC,EAAM,QAAQ,KAAK,OAAO,uBAAuB,YACvD,OAAO,EAAa,EAAW,CAAG,CAAC,EAAE,QAAQ,QAAS,EAAE,EAE5D",
8
- "debugId": "46CE46483A53D9B364756E2164756E21",
8
+ "debugId": "C457D8666BDFCFF264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -3,7 +3,7 @@
3
3
  * Unified error hierarchy for sently transports and SMTP.
4
4
  */
5
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";
6
+ export type SentlyErrorCode = "INVALID_CONFIG" | "SMTP_AUTH_FAILED" | "RATE_LIMITED" | "TIMEOUT" | "CONNECTION_FAILED" | "PROVIDER_ERROR" | "BAD_REQUEST";
7
7
  /** Options for constructing a {@link SentlyError}. */
8
8
  export interface SentlyErrorOptions {
9
9
  /** HTTP status code when the failure originated from an HTTP transport. */
@@ -1,3 +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};
1
+ import{T as a,U as b,V as c}from"../chunk-a1c87hn1.js";import"../chunk-keksa6fx.js";export{c as smtpCodeToSentlyCode,b as httpStatusToSentlyCode,a as SentlyError};
2
2
 
3
- //# debugId=4F713DE42335730C64756E2164756E21
3
+ //# debugId=E285CF097BE506C264756E2164756E21
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "4F713DE42335730C64756E2164756E21",
7
+ "debugId": "E285CF097BE506C264756E2164756E21",
8
8
  "names": []
9
9
  }
package/dist/core/smtp.js CHANGED
@@ -1,3 +1,3 @@
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};
1
+ import{D as a,E as b,F as c,G as d,H as e,I as f,J as g,K as h,L as i,M as j,N as k,O as l}from"../chunk-cvrbfke9.js";import"../chunk-d5rwm3br.js";import"../chunk-a1c87hn1.js";import"../chunk-keksa6fx.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=C99031F5474716F164756E2164756E21
3
+ //# debugId=F63A50D277E84AF764756E2164756E21
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "C99031F5474716F164756E2164756E21",
7
+ "debugId": "F63A50D277E84AF764756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -280,7 +280,7 @@ export interface BulkSendOptions {
280
280
  onSuccess?: (message: MailOptions, index: number, result: SendResult) => void;
281
281
  /** Callback fired after each failed send (does not throw) */
282
282
  onError?: (message: MailOptions, index: number, error: unknown) => void;
283
- /** Max concurrent sends. Defaults to pool maxConnections or 1 */
283
+ /** Max concurrent sends. Defaults to 1. */
284
284
  concurrency?: number;
285
285
  /** When true, stop sending after the first failure. Default: false */
286
286
  stopOnError?: boolean;
@@ -378,14 +378,10 @@ export interface TransportMailerOptions {
378
378
  /** Optional lifecycle hooks for metrics and tracing on every send. */
379
379
  hooks?: MailerHooks;
380
380
  }
381
- /** Options for {@link createMailer} custom transport or SMTP config. */
382
- export type CreateMailerOptions = ({
383
- transport: Transport;
384
- plugins?: MailPlugin[];
385
- hooks?: MailerHooks;
386
- } & Partial<SMTPConfig>) | (SMTPConfig & {
381
+ /** Options for {@link createSMTPMailer} from `sently/smtp` or the main barrel. */
382
+ export type SMTPMailerOptions = SMTPConfig & {
387
383
  hooks?: MailerHooks;
388
- });
384
+ };
389
385
  /**
390
386
  * A mail plugin transforms MailOptions before the message is built.
391
387
  * Plugins run sequentially. Each receives the output of the previous.
package/dist/detect.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CreateMailerOptions, Mailer, Runtime, SocketAdapter, TLSOptions } from "./core/types.js";
1
+ import type { Runtime, SocketAdapter, TLSOptions } from "./core/types.js";
2
2
  /** Detect the current JavaScript runtime. */
3
3
  export declare function detectRuntime(): Runtime;
4
4
  /**
@@ -9,9 +9,3 @@ export declare function createDefaultAdapter(options?: {
9
9
  connectionTimeout?: number;
10
10
  tls?: TLSOptions;
11
11
  }): Promise<SocketAdapter>;
12
- /**
13
- * Create a ready-to-use Mailer instance.
14
- *
15
- * For HTTP transports and smallest bundles, prefer `import { createMailer } from "sently/mailer"`.
16
- */
17
- export declare function createMailer(options: CreateMailerOptions): Promise<Mailer>;
package/dist/detect.js CHANGED
@@ -1,3 +1,3 @@
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};
1
+ import{t as a,u as b}from"./chunk-74rha3ac.js";import"./chunk-keksa6fx.js";export{a as detectRuntime,b as createDefaultAdapter};
2
2
 
3
- //# debugId=29F94DB13623212064756E2164756E21
3
+ //# debugId=9582229C18BADC5064756E2164756E21
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/detect.ts"],
3
+ "sources": [],
4
4
  "sourcesContent": [
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
5
  ],
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",
6
+ "mappings": "",
7
+ "debugId": "9582229C18BADC5064756E2164756E21",
9
8
  "names": []
10
9
  }
package/dist/dkim.js CHANGED
@@ -1,4 +1,4 @@
1
- import{O as G,R as E}from"./chunk-wzdcz88x.js";import"./chunk-rszb1n1d.js";var j=`\r
1
+ import{P as G,S as E}from"./chunk-d5rwm3br.js";import"./chunk-keksa6fx.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{O as G,R as E}from"./chunk-wzdcz88x.js";import"./chunk-rszb1n1d.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=A424387239B7E1A864756E2164756E21
9
+ //# debugId=E9167771F8AA372D64756E2164756E21
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": "A424387239B7E1A864756E2164756E21",
8
+ "debugId": "E9167771F8AA372D64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
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};
1
+ import{q as F}from"./chunk-qp4y0kp2.js";import{B,z as C}from"./chunk-gh5n759p.js";import"./chunk-d5rwm3br.js";import"./chunk-keksa6fx.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=D8BBD409B64A4DE564756E2164756E21
3
+ //# debugId=48F7CB8FE23D7A4364756E2164756E21
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
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
- "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": "D8BBD409B64A4DE564756E2164756E21",
7
+ "mappings": "4JAqCO,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": "48F7CB8FE23D7A4364756E2164756E21",
9
9
  "names": []
10
10
  }
package/dist/index.d.ts CHANGED
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * @module
3
- * Main sently entrypoint — runtime detection, mailer factory, and shared types.
3
+ * Main sently entrypoint — shared types, mailer factories, runtime detection, and OAuth2.
4
+ *
5
+ * Import transports, webhooks, idempotency, DKIM, and plugins from their subpaths
6
+ * (e.g. `sently/transports/resend`, `sently/webhooks`) for the smallest bundle.
4
7
  *
5
8
  * @example
6
9
  * ```ts
7
- * import { createMailer } from "sently";
10
+ * import { createMailer } from "sently/mailer";
11
+ * import { ResendTransport } from "sently/transports/resend";
8
12
  *
9
13
  * const mailer = await createMailer({
10
- * host: "smtp.example.com",
11
- * auth: { user: "you@example.com", pass: "secret" },
14
+ * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
12
15
  * });
13
16
  *
14
17
  * await mailer.send({
@@ -34,102 +37,13 @@ httpStatusToSentlyCode,
34
37
  SentlyError,
35
38
  /** Map an SMTP response code to a stable sently error code. */
36
39
  smtpCodeToSentlyCode, } from "./core/errors.js";
40
+ export type { Address, AddressInput, Attachment, BrevoConfig, BulkSendOptions, BulkSendResult, DKIMConfig, Envelope, Mailer, MailerHookContext, MailerHooks, MailgunConfig, MailOptions, MailPlugin, OAuth2Config, PoolConfig, PreviewConfig, RetryConfig, Runtime, SESConfig, SendResult, SMTPAuth, SMTPConfig, SMTPMailerOptions, SocketAdapter, TLSOptions, Transport, TransportMailerOptions, VerifyResult, } from "./core/types.js";
37
41
  export {
38
- /** SMTP protocol error with server response details. */
39
- SMTPError, } from "./core/smtp.js";
40
- export type { Address, AddressInput, Attachment, BrevoConfig, BulkSendOptions, BulkSendResult, CreateMailerOptions, DKIMConfig, Envelope, Mailer, MailerHookContext, MailerHooks, MailgunConfig, MailOptions, MailPlugin, OAuth2Config, PoolConfig, PreviewConfig, RetryConfig, Runtime, SESConfig, SendResult, SMTPAuth, SMTPConfig, SocketAdapter, TLSOptions, Transport, TransportMailerOptions, VerifyResult, } from "./core/types.js";
41
- export {
42
- /** Create a ready-to-use Mailer instance. */
43
- createMailer,
44
42
  /** Detect the current JavaScript runtime. */
45
43
  detectRuntime, } from "./detect.js";
46
- export type { DKIMSignResult } from "./dkim.js";
47
- export {
48
- /** Import a PEM private key for DKIM signing. */
49
- importPrivateKey,
50
- /** Sign a raw MIME message with DKIM (RFC 6376). */
51
- signDKIM, } from "./dkim.js";
52
- export type {
53
- /** Key-value store for idempotency deduplication. */
54
- IdempotencyStore,
55
- /** Options for {@link IdempotencyTransport}. */
56
- IdempotencyTransportOptions, } from "./idempotency.js";
57
- export {
58
- /** Transport decorator that deduplicates sends on retry or replay. */
59
- IdempotencyTransport,
60
- /** In-memory idempotency store with optional TTL expiry. */
61
- MemoryIdempotencyStore, } from "./idempotency.js";
62
- export type {
63
- /** Renders a template string with the given data object. */
64
- TemplateEngine,
65
- /** Configuration for the template plugin. */
66
- TemplatePluginConfig, } from "./plugins/template.js";
67
- export {
68
- /** Built-in zero-dependency template engine using `{{variable}}` interpolation. */
69
- simpleEngine,
70
- /** Create a template plugin that renders HTML from a named template and data. */
71
- templatePlugin, } from "./plugins/template.js";
72
- export {
73
- /** SMTP connection pool with optional rate limiting. */
74
- SMTPPool, } from "./pool/pool.js";
75
- export {
76
- /** Error thrown when the Brevo API returns a non-success response. */
77
- BrevoError,
78
- /** Brevo HTTP API transport. */
79
- BrevoTransport, } from "./transports/brevo.js";
80
- export {
81
- /** Error thrown when the Mailgun API returns a non-success response. */
82
- MailgunError,
83
- /** Mailgun HTTP API transport (multipart/form-data). */
84
- MailgunTransport, } from "./transports/mailgun.js";
85
- export {
86
- /** Error thrown when the Postmark API returns a non-success response. */
87
- PostmarkError,
88
- /** Postmark HTTP API transport. */
89
- PostmarkTransport, } from "./transports/postmark.js";
90
- export {
91
- /** Development transport that writes emails to disk instead of sending them. */
92
- PreviewTransport, } from "./transports/preview.js";
93
- export {
94
- /** Maximum messages per Resend batch request. */
95
- RESEND_BATCH_MAX,
96
- /** Error thrown when the Resend API returns a non-success response. */
97
- ResendError,
98
- /** Resend HTTP API transport. */
99
- ResendTransport, } from "./transports/resend.js";
100
- export {
101
- /** Decorator transport that retries failed sends with configurable backoff. */
102
- RetryTransport, } from "./transports/retry.js";
103
- export {
104
- /** Error thrown when the SendGrid API returns a non-success response. */
105
- SendGridError,
106
- /** SendGrid v3 HTTP API transport. */
107
- SendGridTransport, } from "./transports/sendgrid.js";
108
- export {
109
- /** Error thrown when the AWS SES API returns a non-success response. */
110
- SESError,
111
- /** AWS SES v2 HTTP API transport. */
112
- SESTransport, } from "./transports/ses.js";
113
44
  export {
114
- /** SMTP transport orchestrating adapter, MIME builder, and protocol logic. */
115
- SMTPTransport, } from "./transports/smtp.js";
116
- export type { EmailEvent } from "./webhooks.js";
45
+ /** Create a mailer for custom transports (HTTP APIs, preview, retry). Prefer `sently/mailer` for smallest bundles. */
46
+ createMailer, } from "./mailer.js";
117
47
  export {
118
- /** Parse a Brevo webhook payload into normalized email events. */
119
- parseBrevoWebhook,
120
- /** Parse a Mailgun webhook payload into normalized email events. */
121
- parseMailgunWebhook,
122
- /** Parse a Postmark webhook payload into normalized email events. */
123
- parsePostmarkWebhook,
124
- /** Parse a Resend webhook payload into normalized email events. */
125
- parseResendWebhook,
126
- /** Parse a SendGrid Event Webhook payload into normalized email events. */
127
- parseSendGridWebhook,
128
- /** Parse an AWS SES SNS webhook payload into normalized email events. */
129
- parseSesWebhook,
130
- /** Verify a Mailgun webhook using the nested signature object. */
131
- verifyMailgunPayload,
132
- /** Verify a Mailgun webhook HMAC signature. */
133
- verifyMailgunSignature,
134
- /** Verify a Resend webhook Svix-style signature. */
135
- verifyResendSignature, } from "./webhooks.js";
48
+ /** Create a mailer from SMTP host/port config (pooling, adapters). Prefer `sently/smtp` for smallest SMTP bundles. */
49
+ createSMTPMailer, } from "./smtp-mailer.js";
package/dist/index.js CHANGED
@@ -15,110 +15,16 @@ export {
15
15
 
16
16
  smtpCodeToSentlyCode,
17
17
  } from "./core/errors.js";
18
- export {
19
-
20
- SMTPError,
21
- } from "./core/smtp.js";
22
18
 
23
19
  export {
24
20
 
25
- createMailer,
26
-
27
21
  detectRuntime,
28
22
  } from "./detect.js";
29
-
30
- export {
31
-
32
- importPrivateKey,
33
-
34
- signDKIM,
35
- } from "./dkim.js";
36
-
37
- export {
38
-
39
- IdempotencyTransport,
40
-
41
- MemoryIdempotencyStore,
42
- } from "./idempotency.js";
43
-
44
23
  export {
45
24
 
46
- simpleEngine,
47
-
48
- templatePlugin,
49
- } from "./plugins/template.js";
50
- export {
51
-
52
- SMTPPool,
53
- } from "./pool/pool.js";
54
- export {
55
-
56
- BrevoError,
57
-
58
- BrevoTransport,
59
- } from "./transports/brevo.js";
60
- export {
61
-
62
- MailgunError,
63
-
64
- MailgunTransport,
65
- } from "./transports/mailgun.js";
66
- export {
67
-
68
- PostmarkError,
69
-
70
- PostmarkTransport,
71
- } from "./transports/postmark.js";
72
- export {
73
-
74
- PreviewTransport,
75
- } from "./transports/preview.js";
76
- export {
77
-
78
- RESEND_BATCH_MAX,
79
-
80
- ResendError,
81
-
82
- ResendTransport,
83
- } from "./transports/resend.js";
84
- export {
85
-
86
- RetryTransport,
87
- } from "./transports/retry.js";
88
- export {
89
-
90
- SendGridError,
91
-
92
- SendGridTransport,
93
- } from "./transports/sendgrid.js";
94
- export {
95
-
96
- SESError,
97
-
98
- SESTransport,
99
- } from "./transports/ses.js";
100
- export {
101
-
102
- SMTPTransport,
103
- } from "./transports/smtp.js";
104
-
25
+ createMailer,
26
+ } from "./mailer.js";
105
27
  export {
106
28
 
107
- parseBrevoWebhook,
108
-
109
- parseMailgunWebhook,
110
-
111
- parsePostmarkWebhook,
112
-
113
- parseResendWebhook,
114
-
115
- parseSendGridWebhook,
116
-
117
- parseSesWebhook,
118
-
119
- verifyMailgunPayload,
120
-
121
- verifyMailgunSignature,
122
-
123
- verifyResendSignature,
124
- } from "./webhooks.js";
29
+ createSMTPMailer,
30
+ } from "./smtp-mailer.js";
package/dist/mailer.js CHANGED
@@ -1,3 +1,3 @@
1
- import{t as a,u as b}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"./chunk-rszb1n1d.js";export{a as createMailer,b as MailerImpl};
1
+ import{v as a,w as b}from"./chunk-ka6t8g77.js";import"./chunk-kebhy657.js";import"./chunk-gh5n759p.js";import"./chunk-d5rwm3br.js";import"./chunk-a1c87hn1.js";import"./chunk-keksa6fx.js";export{a as createMailer,b as MailerImpl};
2
2
 
3
- //# debugId=1771C28406C2AFAA64756E2164756E21
3
+ //# debugId=473A1A6D56DBB5E664756E2164756E21
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "1771C28406C2AFAA64756E2164756E21",
7
+ "debugId": "473A1A6D56DBB5E664756E2164756E21",
8
8
  "names": []
9
9
  }
@@ -1,3 +1,3 @@
1
- import{V as B}from"../chunk-rszb1n1d.js";function F(){return async(j)=>{if(j.react===void 0||j.react===null)return j;let{render:k}=await import("@react-email/render"),q=j.html??await k(j.react,{pretty:!1}),z=j.text??await k(j.react,{plainText:!0}),{react:D,...A}=j;return{...A,html:q,text:z}}}export{F as reactPlugin};
1
+ import{W as B}from"../chunk-keksa6fx.js";function F(){return async(j)=>{if(j.react===void 0||j.react===null)return j;let{render:k}=await import("@react-email/render"),q=j.html??await k(j.react,{pretty:!1}),z=j.text??await k(j.react,{plainText:!0}),{react:D,...A}=j;return{...A,html:q,text:z}}}export{F as reactPlugin};
2
2
 
3
- //# debugId=9D511B0951A46E1B64756E2164756E21
3
+ //# debugId=311F9607C4B15F8264756E2164756E21
@@ -5,6 +5,6 @@
5
5
  "/**\n * @module\n * React Email plugin for sently.\n * Renders a React element to HTML and plain text via `@react-email/render`.\n *\n * @example\n * ```ts\n * import { reactPlugin } from \"sently/react\";\n * import { WelcomeEmail } from \"./emails/welcome\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [reactPlugin()],\n * });\n *\n * await mailer.send({\n * from: \"...\",\n * to: \"...\",\n * subject: \"Welcome!\",\n * react: WelcomeEmail({ name: \"Ali\" }),\n * });\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Mail options with a React element body (requires `react` peer). */\nexport interface ReactMailOptions extends Omit<MailOptions, \"react\"> {\n /** React element rendered to html + text by the react plugin. */\n react?: import(\"react\").ReactElement;\n}\n\n/**\n * Create a plugin that renders `options.react` to HTML and plain text.\n * Explicit `html` / `text` values always win. The `react` field is stripped\n * before options reach the transport.\n */\nexport function reactPlugin(): MailPlugin {\n return async (options: MailOptions): Promise<MailOptions> => {\n if (options.react === undefined || options.react === null) {\n return options;\n }\n\n const { render } = await import(\"@react-email/render\");\n\n const html =\n options.html ??\n (await render(options.react as Parameters<typeof render>[0], { pretty: false }));\n\n const text =\n options.text ??\n (await render(options.react as Parameters<typeof render>[0], { plainText: true }));\n\n const { react: _react, ...rest } = options;\n return { ...rest, html, text };\n };\n}\n"
6
6
  ],
7
7
  "mappings": "yCAoCO,SAAS,CAAW,EAAe,CACxC,MAAO,OAAO,IAA+C,CAC3D,GAAI,EAAQ,QAAU,QAAa,EAAQ,QAAU,KACnD,OAAO,EAGT,IAAQ,UAAW,KAAa,+BAE1B,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,OAAQ,EAAM,CAAC,EAE1E,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,UAAW,EAAK,CAAC,GAE1E,MAAO,KAAW,GAAS,EACnC,MAAO,IAAK,EAAM,OAAM,MAAK",
8
- "debugId": "9D511B0951A46E1B64756E2164756E21",
8
+ "debugId": "311F9607C4B15F8264756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,3 +1,3 @@
1
- import"../chunk-rszb1n1d.js";function E(q,j){return q.replace(/\{\{(\w+)\}\}/g,(z,A)=>{let w=j[A];return w!==void 0&&w!==null?String(w):""})}function F(q){return(j)=>{if(!j.template)return j;let z=q.templates[j.template];if(!z)throw Error(`sently: template "${j.template}" not found`);let A=q.engine(z,j.data??{}),{template:w,data:D,...B}=j;return{...B,html:A}}}export{F as templatePlugin,E as simpleEngine};
1
+ import"../chunk-keksa6fx.js";function E(q,j){return q.replace(/\{\{(\w+)\}\}/g,(z,A)=>{let w=j[A];return w!==void 0&&w!==null?String(w):""})}function F(q){return(j)=>{if(!j.template)return j;let z=q.templates[j.template];if(!z)throw Error(`sently: template "${j.template}" not found`);let A=q.engine(z,j.data??{}),{template:w,data:D,...B}=j;return{...B,html:A}}}export{F as templatePlugin,E as simpleEngine};
2
2
 
3
3
  //# debugId=094B3D1AC9B011AB64756E2164756E21
package/dist/pool/pool.js CHANGED
@@ -1,3 +1,3 @@
1
- import{i as B,j as Q,k as U,l as V}from"../chunk-qnz9z0d4.js";import"../chunk-348wndn3.js";import{r as K}from"../chunk-y72qggqf.js";import{s as N}from"../chunk-z7j2n9k8.js";import{w as G}from"../chunk-tfxje74k.js";import"../chunk-y4qxdyqn.js";import"../chunk-w3fnjh04.js";import"../chunk-wzdcz88x.js";import"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";async function W(k){let q=B(k.config),w=await k.createAdapter();await w.connect(k.connectHost,q.port),await Q(w,q);let z=0,D=!0,F=Promise.resolve(),X=k.maxMessages;return{get idle(){return D},get messageCount(){return z},get usable(){return z<X},async send(H){let Y=async()=>{D=!1;try{let Z={...H,attachments:await N(H.attachments)},_=await K(Z,q.dkim),$=await U(w,_);return z+=1,$}finally{D=!0}},J=F.then(Y);return F=J.then(()=>{return},()=>{return}),J},async close(){await F,await V(w)}}}class b{config;maxConnections;maxMessages;createAdapterFn;rateLimiter;connections=[];queue=[];draining=!1;closed=!1;processChain=Promise.resolve();constructor(k,q){if(this.config=k,this.maxConnections=k.maxConnections??5,this.maxMessages=k.maxMessages??100,q?.createAdapter){let w=q.createAdapter;this.createAdapterFn=async()=>w()}else if(k.adapter)this.createAdapterFn=async()=>k.adapter;else throw Error("SMTPPool requires config.adapter or options.createAdapter");if(k.rateDelta!==void 0&&k.rateDelta>0)this.rateLimiter=new G(k.rateDelta,k.rateLimit??1000,q?.now);else this.rateLimiter=null}async send(k){if(this.draining)throw Error("SMTPPool is closing — no new messages accepted");if(this.closed)throw Error("SMTPPool is closed");if(this.rateLimiter)await this.rateLimiter.acquire();return new Promise((q,w)=>{this.queue.push({options:k,resolve:q,reject:w}),this.scheduleProcess()})}scheduleProcess(){this.processChain=this.processChain.then(()=>this.processQueue()).catch(()=>{return})}async verify(){try{let k=await this.spawnConnection();try{return{ok:!0,provider:"smtp-pool"}}finally{await k.close(),this.removeConnection(k)}}catch(k){return{ok:!1,provider:"smtp-pool",message:k instanceof Error?k.message:String(k)}}}async close(){this.draining=!0,await this.drainQueue(),await Promise.allSettled(this.connections.map((k)=>k.close())),this.connections.length=0,this.closed=!0}get connectionCount(){return this.connections.length}get queueSize(){return this.queue.length}async processQueue(){if(this.draining)return;while(this.queue.length>0){let k=this.connections.find((q)=>q.idle&&q.usable);if(k){let q=this.queue.shift();if(!q)break;try{let w=await k.send(q.options);if(q.resolve(w),!k.usable)await k.close(),this.removeConnection(k)}catch(w){q.reject(w),await k.close().catch(()=>{return}),this.removeConnection(k)}continue}if(this.connections.length<this.maxConnections){let q=this.queue.shift();if(!q)break;let w=await this.spawnConnection();try{let z=await w.send(q.options);if(q.resolve(z),!w.usable)await w.close(),this.removeConnection(w)}catch(z){q.reject(z),await w.close().catch(()=>{return}),this.removeConnection(w)}continue}break}}async spawnConnection(){let k=B(this.config),q=await W({config:this.config,maxMessages:this.maxMessages,connectHost:k.host,createAdapter:this.createAdapterFn});return this.connections.push(q),q}removeConnection(k){let q=this.connections.indexOf(k);if(q>=0)this.connections.splice(q,1)}async drainQueue(){while(this.queue.length>0||this.connections.some((k)=>!k.idle))if(await this.processQueue(),this.queue.length>0)await new Promise((k)=>setTimeout(k,10))}}export{b as SMTPPool,G as RateLimiter};
1
+ import{i as B,j as Q,k as U,l as V}from"../chunk-4mvp3b85.js";import"../chunk-rm78s1ek.js";import{r as K}from"../chunk-gkkv7yqm.js";import{s as N}from"../chunk-r7ee9tk7.js";import{x as G}from"../chunk-kebhy657.js";import"../chunk-gh5n759p.js";import"../chunk-cvrbfke9.js";import"../chunk-d5rwm3br.js";import"../chunk-a1c87hn1.js";import"../chunk-keksa6fx.js";async function W(k){let q=B(k.config),w=await k.createAdapter();await w.connect(k.connectHost,q.port),await Q(w,q);let z=0,D=!0,F=Promise.resolve(),X=k.maxMessages;return{get idle(){return D},get messageCount(){return z},get usable(){return z<X},async send(H){let Y=async()=>{D=!1;try{let Z={...H,attachments:await N(H.attachments)},_=await K(Z,q.dkim),$=await U(w,_);return z+=1,$}finally{D=!0}},J=F.then(Y);return F=J.then(()=>{return},()=>{return}),J},async close(){await F,await V(w)}}}class b{config;maxConnections;maxMessages;createAdapterFn;rateLimiter;connections=[];queue=[];draining=!1;closed=!1;processChain=Promise.resolve();constructor(k,q){if(this.config=k,this.maxConnections=k.maxConnections??5,this.maxMessages=k.maxMessages??100,q?.createAdapter){let w=q.createAdapter;this.createAdapterFn=async()=>w()}else if(k.adapter)this.createAdapterFn=async()=>k.adapter;else throw Error("SMTPPool requires config.adapter or options.createAdapter");if(k.rateDelta!==void 0&&k.rateDelta>0)this.rateLimiter=new G(k.rateDelta,k.rateLimit??1000,q?.now);else this.rateLimiter=null}async send(k){if(this.draining)throw Error("SMTPPool is closing — no new messages accepted");if(this.closed)throw Error("SMTPPool is closed");if(this.rateLimiter)await this.rateLimiter.acquire();return new Promise((q,w)=>{this.queue.push({options:k,resolve:q,reject:w}),this.scheduleProcess()})}scheduleProcess(){this.processChain=this.processChain.then(()=>this.processQueue()).catch(()=>{return})}async verify(){try{let k=await this.spawnConnection();try{return{ok:!0,provider:"smtp-pool"}}finally{await k.close(),this.removeConnection(k)}}catch(k){return{ok:!1,provider:"smtp-pool",message:k instanceof Error?k.message:String(k)}}}async close(){this.draining=!0,await this.drainQueue(),await Promise.allSettled(this.connections.map((k)=>k.close())),this.connections.length=0,this.closed=!0}get connectionCount(){return this.connections.length}get queueSize(){return this.queue.length}async processQueue(){if(this.draining)return;while(this.queue.length>0){let k=this.connections.find((q)=>q.idle&&q.usable);if(k){let q=this.queue.shift();if(!q)break;try{let w=await k.send(q.options);if(q.resolve(w),!k.usable)await k.close(),this.removeConnection(k)}catch(w){q.reject(w),await k.close().catch(()=>{return}),this.removeConnection(k)}continue}if(this.connections.length<this.maxConnections){let q=this.queue.shift();if(!q)break;let w=await this.spawnConnection();try{let z=await w.send(q.options);if(q.resolve(z),!w.usable)await w.close(),this.removeConnection(w)}catch(z){q.reject(z),await w.close().catch(()=>{return}),this.removeConnection(w)}continue}break}}async spawnConnection(){let k=B(this.config),q=await W({config:this.config,maxMessages:this.maxMessages,connectHost:k.host,createAdapter:this.createAdapterFn});return this.connections.push(q),q}removeConnection(k){let q=this.connections.indexOf(k);if(q>=0)this.connections.splice(q,1)}async drainQueue(){while(this.queue.length>0||this.connections.some((k)=>!k.idle))if(await this.processQueue(),this.queue.length>0)await new Promise((k)=>setTimeout(k,10))}}export{b as SMTPPool,G as RateLimiter};
2
2
 
3
- //# debugId=F95E0F4F4A8DD72A64756E2164756E21
3
+ //# debugId=EEBF20CE45142C1F64756E2164756E21
@@ -6,6 +6,6 @@
6
6
  "/**\n * @module\n * SMTP connection pool with optional rate limiting.\n *\n * @example\n * ```ts\n * import { SMTPPool } from \"sently/pool\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n *\n * const pool = new SMTPPool({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * pool: true,\n * maxConnections: 5,\n * });\n *\n * await pool.send({\n * from: \"you@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Pooled send\",\n * });\n * ```\n */\n\nimport { RateLimiter } from \"../core/rate-limiter.js\";\nimport type {\n MailOptions,\n PoolConfig,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveSMTPConfig } from \"../transports/smtp.js\";\nimport { createPooledConnection, type PooledConnection } from \"./connection.js\";\n\n/** Options for {@link SMTPPool}. */\nexport interface SMTPPoolOptions {\n /** Factory for a new socket adapter per pooled connection. */\n createAdapter?: () => Promise<SocketAdapter> | SocketAdapter;\n /** Injectable clock for rate limiting (testing). */\n now?: () => number;\n}\n\ninterface QueueEntry {\n options: MailOptions;\n resolve: (result: SendResult) => void;\n reject: (error: unknown) => void;\n}\n\n/**\n * SMTP connection pool with optional rate limiting.\n */\nexport class SMTPPool implements Transport {\n /** Resolved SMTP and pool configuration. */\n private readonly config: SMTPConfig & PoolConfig;\n /** Maximum simultaneous pooled connections. */\n private readonly maxConnections: number;\n /** Maximum messages per connection before recycle. */\n private readonly maxMessages: number;\n /** Factory that creates a socket adapter for each new connection. */\n private readonly createAdapterFn: () => Promise<SocketAdapter>;\n /** Optional token-bucket rate limiter, or null when disabled. */\n private readonly rateLimiter: RateLimiter | null;\n /** Active pooled SMTP connections. */\n private readonly connections: PooledConnection[] = [];\n /** Pending send operations waiting for a connection. */\n private readonly queue: QueueEntry[] = [];\n /** True while {@link close} is draining; rejects new sends. */\n private draining = false;\n /** True after the pool has fully closed. */\n private closed = false;\n /** Serializes queue processing to avoid concurrent drain races. */\n private processChain: Promise<void> = Promise.resolve();\n\n /** Creates an SMTP connection pool. */\n constructor(config: SMTPConfig & PoolConfig, options?: SMTPPoolOptions) {\n this.config = config;\n this.maxConnections = config.maxConnections ?? 5;\n this.maxMessages = config.maxMessages ?? 100;\n\n if (options?.createAdapter) {\n const factory = options.createAdapter;\n this.createAdapterFn = async () => factory();\n } else if (config.adapter) {\n this.createAdapterFn = async () => config.adapter as SocketAdapter;\n } else {\n throw new Error(\"SMTPPool requires config.adapter or options.createAdapter\");\n }\n\n if (config.rateDelta !== undefined && config.rateDelta > 0) {\n this.rateLimiter = new RateLimiter(config.rateDelta, config.rateLimit ?? 1000, options?.now);\n } else {\n this.rateLimiter = null;\n }\n }\n\n /** Sends a message through the pool. */\n async send(options: MailOptions): Promise<SendResult> {\n if (this.draining) {\n throw new Error(\"SMTPPool is closing — no new messages accepted\");\n }\n if (this.closed) {\n throw new Error(\"SMTPPool is closed\");\n }\n if (this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n return new Promise<SendResult>((resolve, reject) => {\n this.queue.push({ options, resolve, reject });\n this.scheduleProcess();\n });\n }\n\n /** Schedules asynchronous processing of the send queue. */\n private scheduleProcess(): void {\n this.processChain = this.processChain.then(() => this.processQueue()).catch(() => undefined);\n }\n\n /** Verifies connectivity using a temporary connection. */\n async verify(): Promise<VerifyResult> {\n try {\n const conn = await this.spawnConnection();\n try {\n return { ok: true, provider: \"smtp-pool\" };\n } finally {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp-pool\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Drains the queue and closes all connections. */\n async close(): Promise<void> {\n this.draining = true;\n await this.drainQueue();\n await Promise.allSettled(this.connections.map((c) => c.close()));\n this.connections.length = 0;\n this.closed = true;\n }\n\n /** Current number of open pooled connections. */\n get connectionCount(): number {\n return this.connections.length;\n }\n\n /** Number of messages waiting in the send queue. */\n get queueSize(): number {\n return this.queue.length;\n }\n\n /** Dispatches queued messages to idle or newly spawned connections. */\n private async processQueue(): Promise<void> {\n if (this.draining) {\n return;\n }\n\n while (this.queue.length > 0) {\n const idleConn = this.connections.find((c) => c.idle && c.usable);\n if (idleConn) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n try {\n const result = await idleConn.send(entry.options);\n entry.resolve(result);\n if (!idleConn.usable) {\n await idleConn.close();\n this.removeConnection(idleConn);\n }\n } catch (err) {\n entry.reject(err);\n await idleConn.close().catch(() => undefined);\n this.removeConnection(idleConn);\n }\n continue;\n }\n\n if (this.connections.length < this.maxConnections) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n const conn = await this.spawnConnection();\n try {\n const result = await conn.send(entry.options);\n entry.resolve(result);\n if (!conn.usable) {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n entry.reject(err);\n await conn.close().catch(() => undefined);\n this.removeConnection(conn);\n }\n continue;\n }\n\n break;\n }\n }\n\n /** Opens a new authenticated pooled SMTP connection. */\n private async spawnConnection(): Promise<PooledConnection> {\n const resolved = resolveSMTPConfig(this.config);\n const conn = await createPooledConnection({\n config: this.config,\n maxMessages: this.maxMessages,\n connectHost: resolved.host,\n createAdapter: this.createAdapterFn,\n });\n this.connections.push(conn);\n return conn;\n }\n\n /** Removes a connection from the active pool list. */\n private removeConnection(conn: PooledConnection): void {\n const index = this.connections.indexOf(conn);\n if (index >= 0) {\n this.connections.splice(index, 1);\n }\n }\n\n /** Waits until the send queue and in-flight work are fully drained. */\n private async drainQueue(): Promise<void> {\n while (this.queue.length > 0 || this.connections.some((c) => !c.idle)) {\n await this.processQueue();\n if (this.queue.length > 0) {\n await new Promise((r) => setTimeout(r, 10));\n }\n }\n }\n}\n\n/** @internal Exposed for deterministic rate limiter tests. */\nexport { RateLimiter } from \"../core/rate-limiter.js\";\n"
7
7
  ],
8
8
  "mappings": "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",
9
- "debugId": "F95E0F4F4A8DD72A64756E2164756E21",
9
+ "debugId": "EEBF20CE45142C1F64756E2164756E21",
10
10
  "names": []
11
11
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @module
3
+ * SMTP-capable mailer factory — host/port config, pooling, and runtime adapters.
4
+ *
5
+ * Import from `sently/smtp` or use {@link createSMTPMailer} from the main package.
6
+ * For HTTP transports, use `sently/mailer` instead (~10 KB smaller in app bundles).
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { createSMTPMailer } from "sently/smtp";
11
+ *
12
+ * const mailer = await createSMTPMailer({
13
+ * host: "smtp.example.com",
14
+ * port: 587,
15
+ * auth: { user: "you@example.com", pass: "secret" },
16
+ * });
17
+ * ```
18
+ */
19
+ import type { Mailer, SMTPMailerOptions } from "./core/types.js";
20
+ /**
21
+ * Create a mailer from SMTP relay options (host, port, auth, pool, adapter).
22
+ */
23
+ export declare function createSMTPMailer(options: SMTPMailerOptions): Promise<Mailer>;