sently 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +94 -41
- package/dist/adapters/bun.js +2 -183
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.js +2 -77
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.js +2 -72
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.js +2 -180
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.js +2 -13
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/chunk-2kcwa9gt.js +4 -0
- package/dist/chunk-2kcwa9gt.js.map +11 -0
- package/dist/chunk-2t6hjer3.js +5 -0
- package/dist/chunk-2t6hjer3.js.map +10 -0
- package/dist/chunk-6yggz45h.js +5 -0
- package/dist/{chunk-794hc3m4.js.map → chunk-6yggz45h.js.map} +2 -2
- package/dist/chunk-dgkh77yp.js +4 -0
- package/dist/{chunk-ym3zzv8b.js.map → chunk-dgkh77yp.js.map} +2 -2
- package/dist/chunk-jfs80vhp.js +3 -0
- package/dist/{chunk-7fqv71z1.js.map → chunk-jfs80vhp.js.map} +2 -2
- package/dist/chunk-sqn04kae.js +4 -0
- package/dist/{chunk-v0bahtg2.js.map → chunk-sqn04kae.js.map} +1 -1
- package/dist/chunk-va2awz12.js +4 -0
- package/dist/{chunk-f4c9ttmr.js.map → chunk-va2awz12.js.map} +2 -2
- package/dist/chunk-wgtbr6ge.js +13 -0
- package/dist/{chunk-tymfm441.js.map → chunk-wgtbr6ge.js.map} +2 -2
- package/dist/core/smtp.js +2 -31
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.js +2 -180
- package/dist/detect.js.map +4 -5
- package/dist/dkim.d.ts +21 -0
- package/dist/dkim.js +9 -0
- package/dist/dkim.js.map +10 -0
- package/dist/index.d.ts +74 -16
- package/dist/index.js +85 -14
- package/dist/mailer.d.ts +16 -0
- package/dist/mailer.js +3 -0
- package/dist/mailer.js.map +9 -0
- package/dist/plugins/template.js +2 -28
- package/dist/plugins/template.js.map +2 -2
- package/dist/pool/pool.js +2 -16
- package/dist/pool/pool.js.map +5 -3
- package/dist/transports/brevo.js +2 -115
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.js +2 -119
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.js +2 -113
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -72
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.js +2 -109
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.js +2 -78
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.js +2 -132
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +5 -251
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -26
- package/dist/transports/smtp.js.map +1 -1
- package/package.json +17 -6
- package/dist/chunk-794hc3m4.js +0 -105
- package/dist/chunk-7fqv71z1.js +0 -251
- package/dist/chunk-f4c9ttmr.js +0 -154
- package/dist/chunk-mp5c9bfd.js +0 -270
- package/dist/chunk-mp5c9bfd.js.map +0 -11
- package/dist/chunk-tymfm441.js +0 -405
- package/dist/chunk-v0bahtg2.js +0 -6
- package/dist/chunk-x3szga4k.js +0 -367
- package/dist/chunk-x3szga4k.js.map +0 -11
- package/dist/chunk-ym3zzv8b.js +0 -74
package/dist/detect.js.map
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/
|
|
3
|
+
"sources": ["../src/detect.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"
|
|
6
|
-
"// src/detect.ts\nimport { runPlugins } from \"./core/plugin.js\";\nimport type {\n BulkSendOptions,\n BulkSendResult,\n CreateMailerOptions,\n Mailer,\n MailOptions,\n MailPlugin,\n Runtime,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n TLSOptions,\n Transport,\n VerifyResult,\n} from \"./core/types.js\";\nimport { SMTPPool } from \"./pool/pool.js\";\nimport { SMTPTransport } from \"./transports/smtp.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 */\nexport async function createMailer(options: CreateMailerOptions): Promise<Mailer> {\n if (\"transport\" in options) {\n return new MailerImpl(options.transport, options.plugins ?? []);\n }\n\n const smtpConfig = options as SMTPConfig;\n\n if (smtpConfig.pool) {\n return new MailerImpl(\n new SMTPPool(smtpConfig, {\n createAdapter: async () =>\n smtpConfig.adapter ??\n (await createDefaultAdapter({\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 smtpConfig.plugins,\n );\n }\n\n const adapter =\n smtpConfig.adapter ??\n (await createDefaultAdapter({\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 return new MailerImpl(new SMTPTransport({ ...smtpConfig, adapter }), smtpConfig.plugins);\n}\n\nclass MailerImpl implements Mailer {\n constructor(\n private readonly transport: Transport,\n private readonly plugins: MailPlugin[] = [],\n ) {}\n\n async send(options: MailOptions): Promise<SendResult> {\n const processed = await runPlugins(options, this.plugins);\n return this.transport.send(processed);\n }\n\n async sendBulk(messages: MailOptions[], options?: BulkSendOptions): Promise<BulkSendResult> {\n const concurrency = options?.concurrency ?? 1;\n const results: BulkSendResult[\"results\"] = new Array(messages.length);\n const queue = [...messages.entries()];\n let active = 0;\n\n await new Promise<void>((resolve) => {\n if (messages.length === 0) {\n resolve();\n return;\n }\n\n const maybeDone = (): void => {\n if (queue.length === 0 && active === 0) {\n resolve();\n }\n };\n\n const processNext = (): void => {\n if (queue.length === 0) {\n maybeDone();\n return;\n }\n\n const entry = queue.shift();\n if (entry === undefined) {\n maybeDone();\n return;\n }\n\n const [index, message] = entry;\n active++;\n\n void this.send(message)\n .then((result) => {\n results[index] = { status: \"sent\", result };\n options?.onSuccess?.(message, index, result);\n })\n .catch((error: unknown) => {\n results[index] = { status: \"failed\", error };\n options?.onError?.(message, index, error);\n })\n .finally(() => {\n active--;\n processNext();\n maybeDone();\n });\n };\n\n for (let i = 0; i < concurrency; i++) {\n processNext();\n }\n });\n\n let sent = 0;\n let failed = 0;\n for (const result of results) {\n if (result.status === \"sent\") {\n sent++;\n } else {\n failed++;\n }\n }\n\n return {\n total: messages.length,\n sent,\n failed,\n results,\n };\n }\n\n verify(): Promise<VerifyResult> {\n if (this.transport.verify) {\n return this.transport.verify();\n }\n return Promise.resolve({ ok: true, provider: \"mailer\" });\n }\n\n close(): Promise<void> {\n if (this.transport.close) {\n return this.transport.close();\n }\n return Promise.resolve();\n }\n}\n\ndeclare const Bun: unknown;\ndeclare const Deno: unknown;\n"
|
|
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"
|
|
7
6
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
7
|
+
"mappings": "6FAYO,GAAS,CAAa,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": "1210DC19744F68B364756E2164756E21",
|
|
10
9
|
"names": []
|
|
11
10
|
}
|
package/dist/dkim.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* DKIM (DomainKeys Identified Mail) signing per RFC 6376.
|
|
4
|
+
* Supports RSA-SHA256 and Ed25519-SHA256 using Web Crypto.
|
|
5
|
+
*
|
|
6
|
+
* Import from this entry when signing mail explicitly. MIME building lazy-loads
|
|
7
|
+
* DKIM only when a `dkim` option is passed to {@link buildMIME}.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { signDKIM } from "sently/dkim";
|
|
12
|
+
*
|
|
13
|
+
* const { header } = await signDKIM(rawMessage, {
|
|
14
|
+
* domainName: "example.com",
|
|
15
|
+
* keySelector: "2024",
|
|
16
|
+
* privateKey: process.env.DKIM_PRIVATE_KEY!,
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export type { DKIMSignResult } from "./core/dkim.js";
|
|
21
|
+
export { canonicalizeBodyRelaxed, canonicalizeHeadersRelaxed, importPrivateKey, signDKIM, } from "./core/dkim.js";
|
package/dist/dkim.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{E as G,H as E}from"./chunk-6yggz45h.js";import"./chunk-sqn04kae.js";var j=`\r
|
|
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
|
+
`).replace(/\r/g,`
|
|
4
|
+
`).split(`
|
|
5
|
+
`).map((K)=>K.replace(/[ \t]+$/g,"").replace(/[ \t]+/g," ")).filter((K)=>K.length>0);if(x.length===0)return j;return`${x.join(j)}${j}`}async function v(M,I){if(I==="ed25519-sha256"&&/OPENSSH PRIVATE KEY/i.test(M))throw Error("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");let x=B(M),K=J(x);try{if(I==="ed25519-sha256")return await crypto.subtle.importKey("pkcs8",K,{name:"Ed25519"},!1,["sign"]);return await crypto.subtle.importKey("pkcs8",K,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"])}catch(S){if(I==="ed25519-sha256"&&S instanceof DOMException)throw Error("Ed25519 DKIM requires Node.js ≥ 18.4, Bun ≥ 1.0, or Cloudflare Workers",{cause:S});throw S}}async function N(M,I){let x=I.algorithm??"rsa-sha256",K=(I.headerFieldNames??C).split(":").map((q)=>q.trim().toLowerCase()).filter(Boolean),S=new Set((I.skipFields??"").split(":").map((q)=>q.trim().toLowerCase()).filter(Boolean)),Y=K.filter((q)=>!S.has(q)),z=new TextDecoder().decode(M),D=z.indexOf(`\r
|
|
6
|
+
\r
|
|
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
|
+
|
|
9
|
+
//# debugId=6C34EDE3095573A464756E2164756E21
|
package/dist/dkim.js.map
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/dkim.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
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
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "6C34EDE3095573A464756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,19 +19,77 @@
|
|
|
19
19
|
* });
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
-
export {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
export {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
export {
|
|
23
|
+
/** Default Google OAuth2 token endpoint. */
|
|
24
|
+
GOOGLE_TOKEN_URL,
|
|
25
|
+
/** Microsoft OAuth2 token endpoint (common tenant). */
|
|
26
|
+
MICROSOFT_TOKEN_URL,
|
|
27
|
+
/** OAuth2 client with in-memory token cache and automatic refresh. */
|
|
28
|
+
OAuth2Client, } from "./auth/oauth2.js";
|
|
29
|
+
export {
|
|
30
|
+
/** SMTP protocol error with server response details. */
|
|
31
|
+
SMTPError, } from "./core/smtp.js";
|
|
32
|
+
export type { Address, AddressInput, Attachment, BrevoConfig, BulkSendOptions, BulkSendResult, CreateMailerOptions, DKIMConfig, Envelope, Mailer, MailgunConfig, MailOptions, MailPlugin, OAuth2Config, PoolConfig, PreviewConfig, RetryConfig, Runtime, SESConfig, SendResult, SMTPAuth, SMTPConfig, SocketAdapter, TLSOptions, Transport, TransportMailerOptions, VerifyResult, } from "./core/types.js";
|
|
33
|
+
export {
|
|
34
|
+
/** Create a ready-to-use Mailer instance. */
|
|
35
|
+
createMailer,
|
|
36
|
+
/** Detect the current JavaScript runtime. */
|
|
37
|
+
detectRuntime, } from "./detect.js";
|
|
38
|
+
export type { DKIMSignResult } from "./dkim.js";
|
|
39
|
+
export {
|
|
40
|
+
/** Import a PEM private key for DKIM signing. */
|
|
41
|
+
importPrivateKey,
|
|
42
|
+
/** Sign a raw MIME message with DKIM (RFC 6376). */
|
|
43
|
+
signDKIM, } from "./dkim.js";
|
|
44
|
+
export type {
|
|
45
|
+
/** Renders a template string with the given data object. */
|
|
46
|
+
TemplateEngine,
|
|
47
|
+
/** Configuration for the template plugin. */
|
|
48
|
+
TemplatePluginConfig, } from "./plugins/template.js";
|
|
49
|
+
export {
|
|
50
|
+
/** Built-in zero-dependency template engine using `{{variable}}` interpolation. */
|
|
51
|
+
simpleEngine,
|
|
52
|
+
/** Create a template plugin that renders HTML from a named template and data. */
|
|
53
|
+
templatePlugin, } from "./plugins/template.js";
|
|
54
|
+
export {
|
|
55
|
+
/** SMTP connection pool with optional rate limiting. */
|
|
56
|
+
SMTPPool, } from "./pool/pool.js";
|
|
57
|
+
export {
|
|
58
|
+
/** Error thrown when the Brevo API returns a non-success response. */
|
|
59
|
+
BrevoError,
|
|
60
|
+
/** Brevo HTTP API transport. */
|
|
61
|
+
BrevoTransport, } from "./transports/brevo.js";
|
|
62
|
+
export {
|
|
63
|
+
/** Error thrown when the Mailgun API returns a non-success response. */
|
|
64
|
+
MailgunError,
|
|
65
|
+
/** Mailgun HTTP API transport (multipart/form-data). */
|
|
66
|
+
MailgunTransport, } from "./transports/mailgun.js";
|
|
67
|
+
export {
|
|
68
|
+
/** Error thrown when the Postmark API returns a non-success response. */
|
|
69
|
+
PostmarkError,
|
|
70
|
+
/** Postmark HTTP API transport. */
|
|
71
|
+
PostmarkTransport, } from "./transports/postmark.js";
|
|
72
|
+
export {
|
|
73
|
+
/** Development transport that writes emails to disk instead of sending them. */
|
|
74
|
+
PreviewTransport, } from "./transports/preview.js";
|
|
75
|
+
export {
|
|
76
|
+
/** Error thrown when the Resend API returns a non-success response. */
|
|
77
|
+
ResendError,
|
|
78
|
+
/** Resend HTTP API transport. */
|
|
79
|
+
ResendTransport, } from "./transports/resend.js";
|
|
80
|
+
export {
|
|
81
|
+
/** Decorator transport that retries failed sends with configurable backoff. */
|
|
82
|
+
RetryTransport, } from "./transports/retry.js";
|
|
83
|
+
export {
|
|
84
|
+
/** Error thrown when the SendGrid API returns a non-success response. */
|
|
85
|
+
SendGridError,
|
|
86
|
+
/** SendGrid v3 HTTP API transport. */
|
|
87
|
+
SendGridTransport, } from "./transports/sendgrid.js";
|
|
88
|
+
export {
|
|
89
|
+
/** Error thrown when the AWS SES API returns a non-success response. */
|
|
90
|
+
SESError,
|
|
91
|
+
/** AWS SES v2 HTTP API transport. */
|
|
92
|
+
SESTransport, } from "./transports/ses.js";
|
|
93
|
+
export {
|
|
94
|
+
/** SMTP transport orchestrating adapter, MIME builder, and protocol logic. */
|
|
95
|
+
SMTPTransport, } from "./transports/smtp.js";
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,85 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
|
|
3
|
+
GOOGLE_TOKEN_URL,
|
|
4
|
+
|
|
5
|
+
MICROSOFT_TOKEN_URL,
|
|
6
|
+
|
|
7
|
+
OAuth2Client,
|
|
8
|
+
} from "./auth/oauth2.js";
|
|
9
|
+
export {
|
|
10
|
+
|
|
11
|
+
SMTPError,
|
|
12
|
+
} from "./core/smtp.js";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
|
|
16
|
+
createMailer,
|
|
17
|
+
|
|
18
|
+
detectRuntime,
|
|
19
|
+
} from "./detect.js";
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
|
|
23
|
+
importPrivateKey,
|
|
24
|
+
|
|
25
|
+
signDKIM,
|
|
26
|
+
} from "./dkim.js";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
|
|
30
|
+
simpleEngine,
|
|
31
|
+
|
|
32
|
+
templatePlugin,
|
|
33
|
+
} from "./plugins/template.js";
|
|
34
|
+
export {
|
|
35
|
+
|
|
36
|
+
SMTPPool,
|
|
37
|
+
} from "./pool/pool.js";
|
|
38
|
+
export {
|
|
39
|
+
|
|
40
|
+
BrevoError,
|
|
41
|
+
|
|
42
|
+
BrevoTransport,
|
|
43
|
+
} from "./transports/brevo.js";
|
|
44
|
+
export {
|
|
45
|
+
|
|
46
|
+
MailgunError,
|
|
47
|
+
|
|
48
|
+
MailgunTransport,
|
|
49
|
+
} from "./transports/mailgun.js";
|
|
50
|
+
export {
|
|
51
|
+
|
|
52
|
+
PostmarkError,
|
|
53
|
+
|
|
54
|
+
PostmarkTransport,
|
|
55
|
+
} from "./transports/postmark.js";
|
|
56
|
+
export {
|
|
57
|
+
|
|
58
|
+
PreviewTransport,
|
|
59
|
+
} from "./transports/preview.js";
|
|
60
|
+
export {
|
|
61
|
+
|
|
62
|
+
ResendError,
|
|
63
|
+
|
|
64
|
+
ResendTransport,
|
|
65
|
+
} from "./transports/resend.js";
|
|
66
|
+
export {
|
|
67
|
+
|
|
68
|
+
RetryTransport,
|
|
69
|
+
} from "./transports/retry.js";
|
|
70
|
+
export {
|
|
71
|
+
|
|
72
|
+
SendGridError,
|
|
73
|
+
|
|
74
|
+
SendGridTransport,
|
|
75
|
+
} from "./transports/sendgrid.js";
|
|
76
|
+
export {
|
|
77
|
+
|
|
78
|
+
SESError,
|
|
79
|
+
|
|
80
|
+
SESTransport,
|
|
81
|
+
} from "./transports/ses.js";
|
|
82
|
+
export {
|
|
83
|
+
|
|
84
|
+
SMTPTransport,
|
|
85
|
+
} from "./transports/smtp.js";
|
package/dist/mailer.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BulkSendOptions, BulkSendResult, Mailer, MailOptions, MailPlugin, SendResult, Transport, TransportMailerOptions, VerifyResult } from "./core/types.js";
|
|
2
|
+
export type { TransportMailerOptions };
|
|
3
|
+
/**
|
|
4
|
+
* Create a mailer that wraps a custom {@link Transport} (HTTP API, preview, retry, etc.).
|
|
5
|
+
*/
|
|
6
|
+
export declare function createMailer(options: TransportMailerOptions): Promise<Mailer>;
|
|
7
|
+
/** Internal mailer implementation shared with the full `sently` entry. */
|
|
8
|
+
export declare class MailerImpl implements Mailer {
|
|
9
|
+
private readonly transport;
|
|
10
|
+
private readonly plugins;
|
|
11
|
+
constructor(transport: Transport, plugins?: MailPlugin[]);
|
|
12
|
+
send(options: MailOptions): Promise<SendResult>;
|
|
13
|
+
sendBulk(messages: MailOptions[], options?: BulkSendOptions): Promise<BulkSendResult>;
|
|
14
|
+
verify(): Promise<VerifyResult>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
package/dist/mailer.js
ADDED
package/dist/plugins/template.js
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-sqn04kae.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
|
-
|
|
4
|
-
function simpleEngine(template, data) {
|
|
5
|
-
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
6
|
-
const val = data[key];
|
|
7
|
-
return val !== undefined && val !== null ? String(val) : "";
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
function templatePlugin(config) {
|
|
11
|
-
return (options) => {
|
|
12
|
-
if (!options.template) {
|
|
13
|
-
return options;
|
|
14
|
-
}
|
|
15
|
-
const tmpl = config.templates[options.template];
|
|
16
|
-
if (!tmpl) {
|
|
17
|
-
throw new Error(`sently: template "${options.template}" not found`);
|
|
18
|
-
}
|
|
19
|
-
const html = config.engine(tmpl, options.data ?? {});
|
|
20
|
-
const { template: _template, data: _data, ...rest } = options;
|
|
21
|
-
return { ...rest, html };
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export {
|
|
25
|
-
templatePlugin,
|
|
26
|
-
simpleEngine
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
//# debugId=BF4AF728A3AA093064756E2164756E21
|
|
3
|
+
//# debugId=094B3D1AC9B011AB64756E2164756E21
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Template plugin for sently.\n * Renders HTML email templates using a pluggable engine.\n * Built-in: simple {{variable}} interpolation (zero dependencies).\n * Bring your own engine: pass any render function.\n *\n * @example\n * ```ts\n * import { templatePlugin, simpleEngine } from 'sently/plugins/template'\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [\n * templatePlugin({\n * engine: simpleEngine,\n * templates: {\n * welcome: '<h1>Hello, {{name}}!</h1>',\n * reset: '<p>Reset link: {{link}}</p>',\n * },\n * }),\n * ],\n * })\n *\n * await mailer.send({\n * from: '...',\n * to: '...',\n * subject: 'Welcome!',\n * template: 'welcome',\n * data: { name: 'Ali' },\n * })\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Renders a template string with the given data object. */\nexport type TemplateEngine = (template: string, data: Record<string, unknown>) => string;\n\n/** Configuration for the template plugin. */\nexport interface TemplatePluginConfig {\n /**\n * Rendering function. Use `simpleEngine` for zero-dep {{var}} interpolation,\n * or pass any function: (template, data) => string.\n */\n engine: TemplateEngine;\n /**\n * Map of template names to HTML strings.\n * Templates are loaded once at plugin creation time.\n */\n templates: Record<string, string>;\n}\n\n/**\n * Built-in zero-dependency template engine.\n * Replaces {{variable}} with the matching value from data.\n * Unknown variables are replaced with an empty string.\n */\nexport function simpleEngine(template: string, data: Record<string, unknown>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => {\n const val = data[key];\n return val !== undefined && val !== null ? String(val) : \"\";\n });\n}\n\n/**\n * Create a template plugin from the given config.\n * The plugin reads options.template and options.data, renders the HTML,\n * sets options.html, and removes template/data before passing to the next plugin.\n */\nexport function templatePlugin(config: TemplatePluginConfig): MailPlugin {\n return (options: MailOptions): MailOptions => {\n if (!options.template) {\n return options;\n }\n\n const tmpl = config.templates[options.template];\n if (!tmpl) {\n throw new Error(`sently: template \"${options.template}\" not found`);\n }\n\n const html = config.engine(tmpl, options.data ?? {});\n const { template: _template, data: _data, ...rest } = options;\n return { ...rest, html };\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "6BAyDO,SAAS,CAAY,CAAC,EAAkB,EAAuC,CACpF,OAAO,EAAS,QAAQ,iBAAkB,CAAC,EAAG,IAAgB,CAC5D,IAAM,EAAM,EAAK,GACjB,OAAO,IAAQ,QAAa,IAAQ,KAAO,OAAO,CAAG,EAAI,GAC1D,EAQI,SAAS,CAAc,CAAC,EAA0C,CACvE,MAAO,CAAC,IAAsC,CAC5C,GAAI,CAAC,EAAQ,SACX,OAAO,EAGT,IAAM,EAAO,EAAO,UAAU,EAAQ,UACtC,GAAI,CAAC,EACH,MAAU,MAAM,qBAAqB,EAAQ,qBAAqB,EAGpE,IAAM,EAAO,EAAO,OAAO,EAAM,EAAQ,MAAQ,CAAC,CAAC,GAC3C,SAAU,EAAW,KAAM,KAAU,GAAS,EACtD,MAAO,IAAK,EAAM,MAAK",
|
|
8
|
+
"debugId": "094B3D1AC9B011AB64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/pool/pool.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RateLimiter,
|
|
3
|
-
SMTPPool
|
|
4
|
-
} from "../chunk-mp5c9bfd.js";
|
|
5
|
-
import"../chunk-7fqv71z1.js";
|
|
6
|
-
import"../chunk-ym3zzv8b.js";
|
|
7
|
-
import"../chunk-x3szga4k.js";
|
|
8
|
-
import"../chunk-tymfm441.js";
|
|
9
|
-
import"../chunk-f4c9ttmr.js";
|
|
10
|
-
import"../chunk-794hc3m4.js";
|
|
11
|
-
import"../chunk-v0bahtg2.js";
|
|
12
|
-
export {
|
|
13
|
-
SMTPPool,
|
|
14
|
-
RateLimiter
|
|
15
|
-
};
|
|
1
|
+
import{b as F,c as U,d as V,e as W}from"../chunk-jfs80vhp.js";import"../chunk-dgkh77yp.js";import{j as N}from"../chunk-2t6hjer3.js";import{p as Q}from"../chunk-va2awz12.js";import"../chunk-wgtbr6ge.js";import"../chunk-6yggz45h.js";import"../chunk-sqn04kae.js";async function X(k){let q=F(k.config),z=await k.createAdapter();await z.connect(k.connectHost,q.port),await U(z,q);let B=0,G=!0,H=Promise.resolve(),Z=k.maxMessages;return{get idle(){return G},get messageCount(){return B},get usable(){return B<Z},async send(J){let _=async()=>{G=!1;try{let $={...J,attachments:await Q(J.attachments)},b=await N($,q.dkim),A=await V(z,b);return B+=1,A}finally{G=!0}},K=H.then(_);return H=K.then(()=>{return},()=>{return}),K},async close(){await H,await W(z)}}}class Y{rateDelta;rateLimit;now;tokens;lastRefill;waiters=[];constructor(k,q,z=Date.now){this.rateDelta=k;this.rateLimit=q;this.now=z;this.tokens=k,this.lastRefill=z()}async acquire(){for(;;){if(this.refill(),this.tokens>0){this.tokens-=1;return}await new Promise((k)=>{this.waiters.push(k)})}}notify(){this.refill()}refill(){let q=this.now()-this.lastRefill;if(q>=this.rateLimit){let z=Math.floor(q/this.rateLimit);this.tokens=Math.min(this.rateDelta,this.tokens+z*this.rateDelta),this.lastRefill+=z*this.rateLimit;while(this.tokens>0&&this.waiters.length>0)this.tokens-=1,this.waiters.shift()?.()}}}class I{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 z=q.createAdapter;this.createAdapterFn=async()=>z()}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 Y(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,z)=>{this.queue.push({options:k,resolve:q,reject:z}),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 z=await k.send(q.options);if(q.resolve(z),!k.usable)await k.close(),this.removeConnection(k)}catch(z){q.reject(z),await k.close().catch(()=>{return}),this.removeConnection(k)}continue}if(this.connections.length<this.maxConnections){let q=this.queue.shift();if(!q)break;let z=await this.spawnConnection();try{let B=await z.send(q.options);if(q.resolve(B),!z.usable)await z.close(),this.removeConnection(z)}catch(B){q.reject(B),await z.close().catch(()=>{return}),this.removeConnection(z)}continue}break}}async spawnConnection(){let k=F(this.config),q=await X({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{I as SMTPPool,Y as RateLimiter};
|
|
16
2
|
|
|
17
|
-
//# debugId=
|
|
3
|
+
//# debugId=CC1D7092A5F1A6EB64756E2164756E21
|
package/dist/pool/pool.js.map
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
3
|
+
"sources": ["../src/pool/connection.ts", "../src/pool/pool.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
+
"import { buildMIME } from \"../core/mime.js\";\nimport type { MailOptions, SendResult, SMTPConfig } from \"../core/types.js\";\nimport { resolveAttachments } from \"../transports/resolve-attachments.js\";\nimport {\n closeSMTPSession,\n deliverSMTPMessage,\n openSMTPSession,\n resolveSMTPConfig,\n} from \"../transports/smtp.js\";\n\n/** A single pooled SMTP connection with a persistent session. */\nexport interface PooledConnection {\n /** Send one message over this connection. */\n send(options: MailOptions): Promise<SendResult>;\n /** Whether this connection is idle and available for work. */\n readonly idle: boolean;\n /** Number of messages sent on this connection. */\n readonly messageCount: number;\n /** Whether this connection can accept more messages before recycle. */\n readonly usable: boolean;\n /** Close the connection and end the SMTP session. */\n close(): Promise<void>;\n}\n\n/** Options for creating a pooled connection. */\nexport interface PooledConnectionOptions {\n /** SMTP configuration for the pooled session. */\n config: SMTPConfig;\n /** Maximum messages before this connection is recycled. */\n maxMessages: number;\n /** Hostname to connect to (may differ from config.host for direct MX). */\n connectHost: string;\n /** Factory that creates the socket adapter for this connection. */\n createAdapter: () => Promise<import(\"../core/types.js\").SocketAdapter>;\n}\n\n/**\n * Create a pooled SMTP connection with an open authenticated session.\n */\nexport async function createPooledConnection(\n options: PooledConnectionOptions,\n): Promise<PooledConnection> {\n const config = resolveSMTPConfig(options.config);\n const adapter = await options.createAdapter();\n await adapter.connect(options.connectHost, config.port);\n await openSMTPSession(adapter, config);\n\n let messageCount = 0;\n let idle = true;\n let sendChain: Promise<void> = Promise.resolve();\n\n const maxMessages = options.maxMessages;\n\n return {\n get idle(): boolean {\n return idle;\n },\n get messageCount(): number {\n return messageCount;\n },\n get usable(): boolean {\n return messageCount < maxMessages;\n },\n\n async send(mailOptions: MailOptions): Promise<SendResult> {\n const run = async (): Promise<SendResult> => {\n idle = false;\n try {\n const resolvedOptions = {\n ...mailOptions,\n attachments: await resolveAttachments(mailOptions.attachments),\n };\n const mime = await buildMIME(resolvedOptions, config.dkim);\n const result = await deliverSMTPMessage(adapter, mime);\n messageCount += 1;\n return result;\n } finally {\n idle = true;\n }\n };\n\n const resultPromise = sendChain.then(run);\n sendChain = resultPromise.then(\n () => undefined,\n () => undefined,\n );\n return resultPromise;\n },\n\n async close(): Promise<void> {\n await sendChain;\n await closeSMTPSession(adapter);\n },\n };\n}\n",
|
|
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 */\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 * Token bucket rate limiter with lazy refill on acquire.\n */\nclass RateLimiter {\n /** Remaining tokens in the current rate-limit window. */\n private tokens: number;\n /** Timestamp (ms) of the last token refill. */\n private lastRefill: number;\n /** Resolvers waiting for a token when the bucket is empty. */\n private waiters: Array<() => void> = [];\n\n /** Creates a rate limiter with the given burst size and window duration. */\n constructor(\n private readonly rateDelta: number,\n private readonly rateLimit: number,\n private readonly now: () => number = Date.now,\n ) {\n this.tokens = rateDelta;\n this.lastRefill = now();\n }\n\n /** Wait until a token is available, then consume one. */\n async acquire(): Promise<void> {\n for (;;) {\n this.refill();\n if (this.tokens > 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve);\n });\n }\n }\n\n /** Wake waiters after the clock advances (for testing). */\n notify(): void {\n this.refill();\n }\n\n /** Refills tokens based on elapsed time and wakes waiting acquirers. */\n private refill(): void {\n const t = this.now();\n const elapsed = t - this.lastRefill;\n if (elapsed >= this.rateLimit) {\n const periods = Math.floor(elapsed / this.rateLimit);\n this.tokens = Math.min(this.rateDelta, this.tokens + periods * this.rateDelta);\n this.lastRefill += periods * this.rateLimit;\n while (this.tokens > 0 && this.waiters.length > 0) {\n this.tokens -= 1;\n const next = this.waiters.shift();\n next?.();\n }\n }\n }\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 };\n"
|
|
5
7
|
],
|
|
6
|
-
"mappings": "",
|
|
7
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "CC1D7092A5F1A6EB64756E2164756E21",
|
|
8
10
|
"names": []
|
|
9
11
|
}
|
package/dist/transports/brevo.js
CHANGED
|
@@ -1,116 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extractEmails,
|
|
3
|
-
parseAddresses,
|
|
4
|
-
resolveAttachments
|
|
5
|
-
} from "../chunk-f4c9ttmr.js";
|
|
6
|
-
import {
|
|
7
|
-
encodeBase64
|
|
8
|
-
} from "../chunk-794hc3m4.js";
|
|
9
|
-
import"../chunk-v0bahtg2.js";
|
|
1
|
+
import{l as F,n as L,p as M}from"../chunk-va2awz12.js";import{E as K}from"../chunk-6yggz45h.js";import"../chunk-sqn04kae.js";class N extends Error{statusCode;code;constructor(q,w,C){super(q);this.statusCode=w;this.code=C;this.name="BrevoError"}}function H(q){return F(q).map((w)=>({email:w.address,...w.name?{name:w.name}:{}}))}class R{apiKey;constructor(q){this.apiKey=q.apiKey}async send(q){let w=await M(q.attachments),C=F(q.from)[0],Q={sender:C?{email:C.address,...C.name?{name:C.name}:{}}:{email:""},to:H(q.to),subject:q.subject,...q.cc?{cc:H(q.cc)}:{},...q.bcc?{bcc:H(q.bcc)}:{},...q.replyTo?{replyTo:(()=>{let z=F(q.replyTo)[0];return z?{email:z.address,...z.name?{name:z.name}:{}}:void 0})()}:{},...q.html?{htmlContent:q.html}:{},...q.text?{textContent:q.text}:{},...w.length>0?{attachment:w.map((z)=>({name:z.filename,content:z.content instanceof Uint8Array?K(z.content).replace(/\r\n/g,""):z.content}))}:{}},G=await fetch("https://api.brevo.com/v3/smtp/email",{method:"POST",headers:{"api-key":this.apiKey,"Content-Type":"application/json"},body:JSON.stringify(Q)}),D=await G.json();if(!G.ok)throw new N(D.message??"Brevo API error",G.status,D.code??"");let J=L(q.to);return{messageId:D.messageId??"",accepted:J,rejected:[],response:D.messageId??"sent",envelope:{from:C?.address??"",to:J}}}async verify(){try{let q=await fetch("https://api.brevo.com/v3/account",{headers:{"api-key":this.apiKey}}),w=await q.json();if(!q.ok)return{ok:!1,provider:"brevo",message:w.message??`HTTP ${q.status}`};return{ok:!0,provider:"brevo",...w.companyName?{message:w.companyName}:{}}}catch(q){return{ok:!1,provider:"brevo",message:q instanceof Error?q.message:String(q)}}}}export{R as BrevoTransport,N as BrevoError};
|
|
10
2
|
|
|
11
|
-
|
|
12
|
-
class BrevoError extends Error {
|
|
13
|
-
statusCode;
|
|
14
|
-
code;
|
|
15
|
-
constructor(message, statusCode, code) {
|
|
16
|
-
super(message);
|
|
17
|
-
this.statusCode = statusCode;
|
|
18
|
-
this.code = code;
|
|
19
|
-
this.name = "BrevoError";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function toAddressObjects(input) {
|
|
23
|
-
return parseAddresses(input).map((addr) => ({
|
|
24
|
-
email: addr.address,
|
|
25
|
-
...addr.name ? { name: addr.name } : {}
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
class BrevoTransport {
|
|
30
|
-
apiKey;
|
|
31
|
-
constructor(config) {
|
|
32
|
-
this.apiKey = config.apiKey;
|
|
33
|
-
}
|
|
34
|
-
async send(options) {
|
|
35
|
-
const attachments = await resolveAttachments(options.attachments);
|
|
36
|
-
const from = parseAddresses(options.from)[0];
|
|
37
|
-
const body = {
|
|
38
|
-
sender: from ? { email: from.address, ...from.name ? { name: from.name } : {} } : { email: "" },
|
|
39
|
-
to: toAddressObjects(options.to),
|
|
40
|
-
subject: options.subject,
|
|
41
|
-
...options.cc ? { cc: toAddressObjects(options.cc) } : {},
|
|
42
|
-
...options.bcc ? { bcc: toAddressObjects(options.bcc) } : {},
|
|
43
|
-
...options.replyTo ? {
|
|
44
|
-
replyTo: (() => {
|
|
45
|
-
const reply = parseAddresses(options.replyTo)[0];
|
|
46
|
-
return reply ? { email: reply.address, ...reply.name ? { name: reply.name } : {} } : undefined;
|
|
47
|
-
})()
|
|
48
|
-
} : {},
|
|
49
|
-
...options.html ? { htmlContent: options.html } : {},
|
|
50
|
-
...options.text ? { textContent: options.text } : {},
|
|
51
|
-
...attachments.length > 0 ? {
|
|
52
|
-
attachment: attachments.map((att) => ({
|
|
53
|
-
name: att.filename,
|
|
54
|
-
content: att.content instanceof Uint8Array ? encodeBase64(att.content).replace(/\r\n/g, "") : att.content
|
|
55
|
-
}))
|
|
56
|
-
} : {}
|
|
57
|
-
};
|
|
58
|
-
const response = await fetch("https://api.brevo.com/v3/smtp/email", {
|
|
59
|
-
method: "POST",
|
|
60
|
-
headers: {
|
|
61
|
-
"api-key": this.apiKey,
|
|
62
|
-
"Content-Type": "application/json"
|
|
63
|
-
},
|
|
64
|
-
body: JSON.stringify(body)
|
|
65
|
-
});
|
|
66
|
-
const payload = await response.json();
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw new BrevoError(payload.message ?? "Brevo API error", response.status, payload.code ?? "");
|
|
69
|
-
}
|
|
70
|
-
const toEmails = extractEmails(options.to);
|
|
71
|
-
return {
|
|
72
|
-
messageId: payload.messageId ?? "",
|
|
73
|
-
accepted: toEmails,
|
|
74
|
-
rejected: [],
|
|
75
|
-
response: payload.messageId ?? "sent",
|
|
76
|
-
envelope: {
|
|
77
|
-
from: from?.address ?? "",
|
|
78
|
-
to: toEmails
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async verify() {
|
|
83
|
-
try {
|
|
84
|
-
const response = await fetch("https://api.brevo.com/v3/account", {
|
|
85
|
-
headers: {
|
|
86
|
-
"api-key": this.apiKey
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
const payload = await response.json();
|
|
90
|
-
if (!response.ok) {
|
|
91
|
-
return {
|
|
92
|
-
ok: false,
|
|
93
|
-
provider: "brevo",
|
|
94
|
-
message: payload.message ?? `HTTP ${response.status}`
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
ok: true,
|
|
99
|
-
provider: "brevo",
|
|
100
|
-
...payload.companyName ? { message: payload.companyName } : {}
|
|
101
|
-
};
|
|
102
|
-
} catch (err) {
|
|
103
|
-
return {
|
|
104
|
-
ok: false,
|
|
105
|
-
provider: "brevo",
|
|
106
|
-
message: err instanceof Error ? err.message : String(err)
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
export {
|
|
112
|
-
BrevoTransport,
|
|
113
|
-
BrevoError
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
//# debugId=C5824397C8CE442364756E2164756E21
|
|
3
|
+
//# debugId=57FBF679971DFBE964756E2164756E21
|