sently 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +61 -30
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +1 -1
- package/dist/adapters/deno.js +1 -1
- package/dist/adapters/node.js +1 -1
- package/dist/auth/oauth2.js +1 -1
- package/dist/{chunk-zkczgj2c.js → chunk-348wndn3.js} +2 -2
- package/dist/{chunk-zkczgj2c.js.map → chunk-348wndn3.js.map} +1 -1
- package/dist/chunk-3y1tr1vt.js +4 -0
- package/dist/chunk-3y1tr1vt.js.map +11 -0
- package/dist/chunk-n601jhpd.js +4 -0
- package/dist/chunk-n601jhpd.js.map +10 -0
- package/dist/{chunk-c8ya9em0.js → chunk-qnz9z0d4.js} +2 -2
- package/dist/{chunk-c8ya9em0.js.map → chunk-qnz9z0d4.js.map} +1 -1
- package/dist/{chunk-mg8m26nr.js → chunk-rszb1n1d.js} +2 -2
- package/dist/{chunk-mg8m26nr.js.map → chunk-rszb1n1d.js.map} +1 -1
- package/dist/chunk-t0en64h9.js +4 -0
- package/dist/chunk-t0en64h9.js.map +10 -0
- package/dist/{chunk-vws105eb.js → chunk-tfxje74k.js} +2 -2
- package/dist/{chunk-vws105eb.js.map → chunk-tfxje74k.js.map} +1 -1
- package/dist/chunk-w3fnjh04.js +13 -0
- package/dist/chunk-w3fnjh04.js.map +11 -0
- package/dist/{chunk-gxq2aamv.js → chunk-wzdcz88x.js} +2 -2
- package/dist/{chunk-gxq2aamv.js.map → chunk-wzdcz88x.js.map} +1 -1
- package/dist/{chunk-b0qkndv9.js → chunk-y4qxdyqn.js} +3 -3
- package/dist/{chunk-b0qkndv9.js.map → chunk-y4qxdyqn.js.map} +1 -1
- package/dist/{chunk-aeqxkeh3.js → chunk-y72qggqf.js} +2 -2
- package/dist/{chunk-aeqxkeh3.js.map → chunk-y72qggqf.js.map} +1 -1
- package/dist/{chunk-k0gw3qmb.js → chunk-z7j2n9k8.js} +2 -2
- package/dist/{chunk-k0gw3qmb.js.map → chunk-z7j2n9k8.js.map} +1 -1
- package/dist/core/errors.d.ts +55 -0
- package/dist/core/errors.js +3 -0
- package/dist/core/errors.js.map +9 -0
- package/dist/core/smtp.d.ts +3 -3
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +28 -1
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +3 -3
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +1 -1
- package/dist/errors.d.ts +29 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +9 -0
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -0
- package/dist/mailer.d.ts +4 -3
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/plugins/react.js +2 -2
- package/dist/plugins/react.js.map +1 -1
- package/dist/plugins/template.js +1 -1
- package/dist/pool/pool.js +2 -2
- package/dist/pool/pool.js.map +2 -2
- package/dist/transports/brevo.d.ts +3 -3
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +3 -3
- package/dist/transports/mailgun.d.ts +2 -1
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +3 -3
- package/dist/transports/postmark.d.ts +2 -1
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +3 -3
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +1 -1
- package/dist/transports/resend.d.ts +2 -1
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +3 -3
- package/dist/transports/retry.d.ts +9 -0
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -4
- package/dist/transports/sendgrid.d.ts +2 -1
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +3 -3
- package/dist/transports/ses.d.ts +3 -3
- package/dist/transports/ses.js +5 -5
- package/dist/transports/ses.js.map +3 -3
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/webhooks/brevo.js +1 -1
- package/dist/webhooks/mailgun.js +1 -1
- package/dist/webhooks/postmark.js +1 -1
- package/dist/webhooks/resend.js +1 -1
- package/dist/webhooks/sendgrid.js +1 -1
- package/dist/webhooks/ses.js +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/package.json +5 -1
- package/dist/chunk-9ea4hx7m.js +0 -4
- package/dist/chunk-9ea4hx7m.js.map +0 -11
- package/dist/chunk-9p4vev6j.js +0 -13
- package/dist/chunk-9p4vev6j.js.map +0 -11
package/dist/index.d.ts
CHANGED
|
@@ -26,10 +26,18 @@ GOOGLE_TOKEN_URL,
|
|
|
26
26
|
MICROSOFT_TOKEN_URL,
|
|
27
27
|
/** OAuth2 client with in-memory token cache and automatic refresh. */
|
|
28
28
|
OAuth2Client, } from "./auth/oauth2.js";
|
|
29
|
+
export type { SentlyErrorCode, SentlyErrorOptions } from "./core/errors.js";
|
|
30
|
+
export {
|
|
31
|
+
/** Map an HTTP status code to a stable sently error code. */
|
|
32
|
+
httpStatusToSentlyCode,
|
|
33
|
+
/** Base error class for all sently transport and protocol failures. */
|
|
34
|
+
SentlyError,
|
|
35
|
+
/** Map an SMTP response code to a stable sently error code. */
|
|
36
|
+
smtpCodeToSentlyCode, } from "./core/errors.js";
|
|
29
37
|
export {
|
|
30
38
|
/** SMTP protocol error with server response details. */
|
|
31
39
|
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";
|
|
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";
|
|
33
41
|
export {
|
|
34
42
|
/** Create a ready-to-use Mailer instance. */
|
|
35
43
|
createMailer,
|
package/dist/index.js
CHANGED
package/dist/mailer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { BulkSendOptions, BulkSendResult, Mailer, MailOptions, MailPlugin, SendResult, Transport, TransportMailerOptions, VerifyResult } from "./core/types.js";
|
|
2
|
-
export type { TransportMailerOptions };
|
|
1
|
+
import type { BulkSendOptions, BulkSendResult, Mailer, MailerHookContext, MailerHooks, MailOptions, MailPlugin, SendResult, Transport, TransportMailerOptions, VerifyResult } from "./core/types.js";
|
|
2
|
+
export type { MailerHookContext, MailerHooks, TransportMailerOptions };
|
|
3
3
|
/**
|
|
4
4
|
* Create a mailer that wraps a custom {@link Transport} (HTTP API, preview, retry, etc.).
|
|
5
5
|
*/
|
|
@@ -8,7 +8,8 @@ export declare function createMailer(options: TransportMailerOptions): Promise<M
|
|
|
8
8
|
export declare class MailerImpl implements Mailer {
|
|
9
9
|
private readonly transport;
|
|
10
10
|
private readonly plugins;
|
|
11
|
-
|
|
11
|
+
private readonly hooks?;
|
|
12
|
+
constructor(transport: Transport, plugins?: MailPlugin[], hooks?: MailerHooks | undefined);
|
|
12
13
|
send(options: MailOptions): Promise<SendResult>;
|
|
13
14
|
private processMessage;
|
|
14
15
|
sendBulk(messages: MailOptions[], options?: BulkSendOptions): Promise<BulkSendResult>;
|
package/dist/mailer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
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};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=1771C28406C2AFAA64756E2164756E21
|
package/dist/mailer.js.map
CHANGED
package/dist/plugins/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
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};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=9D511B0951A46E1B64756E2164756E21
|
|
@@ -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": "
|
|
8
|
+
"debugId": "9D511B0951A46E1B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/plugins/template.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
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};
|
|
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-
|
|
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};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=F95E0F4F4A8DD72A64756E2164756E21
|
package/dist/pool/pool.js.map
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
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
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
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "F95E0F4F4A8DD72A64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { SentlyError } from "../core/errors.js";
|
|
1
2
|
import type { BrevoConfig, MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
3
|
/** Error thrown when the Brevo API returns a non-success response. */
|
|
3
|
-
export declare class BrevoError extends
|
|
4
|
+
export declare class BrevoError extends SentlyError {
|
|
4
5
|
readonly statusCode: number;
|
|
5
|
-
readonly code: string;
|
|
6
6
|
/** Creates a Brevo API error with status code and error code. */
|
|
7
|
-
constructor(message: string, statusCode: number,
|
|
7
|
+
constructor(message: string, statusCode: number, apiCode: string);
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Brevo HTTP API transport.
|
package/dist/transports/brevo.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{s as
|
|
1
|
+
import{s as R}from"../chunk-z7j2n9k8.js";import{A as M,y as G}from"../chunk-y4qxdyqn.js";import{O as L}from"../chunk-wzdcz88x.js";import{S as N,T as Q}from"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";class U extends N{statusCode;constructor(q,w,D){super(q,Q(w),{statusCode:w,provider:"brevo"});this.statusCode=w;this.name="BrevoError",this.code=D}}function J(q){return G(q).map((w)=>({email:w.address,...w.name?{name:w.name}:{}}))}class X{apiKey;constructor(q){this.apiKey=q.apiKey}async send(q){let w=await R(q.attachments),D=G(q.from)[0],W={sender:D?{email:D.address,...D.name?{name:D.name}:{}}:{email:""},to:J(q.to),subject:q.subject,...q.cc?{cc:J(q.cc)}:{},...q.bcc?{bcc:J(q.bcc)}:{},...q.replyTo?{replyTo:(()=>{let z=G(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?L(z.content).replace(/\r\n/g,""):z.content}))}:{}},H=await fetch("https://api.brevo.com/v3/smtp/email",{method:"POST",headers:{"api-key":this.apiKey,"Content-Type":"application/json"},body:JSON.stringify(W)}),F=await H.json();if(!H.ok)throw new U(F.message??"Brevo API error",H.status,F.code??"");let K=M(q.to);return{messageId:F.messageId??"",accepted:K,rejected:[],response:F.messageId??"sent",envelope:{from:D?.address??"",to:K}}}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{X as BrevoTransport,U as BrevoError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=0FADE175A06AED6D64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/brevo.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends
|
|
5
|
+
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends SentlyError {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n apiCode: string,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"brevo\",\n });\n this.name = \"BrevoError\";\n this.code = apiCode;\n }\n}\n\nfunction toAddressObjects(input: MailOptions[\"to\"]): Array<{ email: string; name?: string }> {\n return parseAddresses(input).map((addr) => ({\n email: addr.address,\n ...(addr.name ? { name: addr.name } : {}),\n }));\n}\n\n/**\n * Brevo HTTP API transport.\n */\nexport class BrevoTransport implements Transport {\n /** Brevo API key used for Authorization. */\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: attachments.map((att) => ({\n name: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n messageId?: string;\n message?: string;\n code?: string;\n };\n\n if (!response.ok) {\n throw new BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "mOA2BO,CAAM,KAAmB,JAAY,JAIxB,WAFlB,WAAW,CACT,EACgB,EAChB,EACA,CACA,MAAM,EAAS,EAAuB,CAAU,EAAG,CACjD,aACA,SAAU,OACZ,CAAC,EANe,kBAOhB,KAAK,KAAO,aACZ,KAAK,KAAO,EAEhB,CAEA,SAAS,CAAgB,CAAC,EAAmE,CAC3F,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC,KAAU,CAC1C,MAAO,EAAK,WACR,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CACzC,EAAE,EAMG,MAAM,CAAoC,CAE9B,OAGjB,WAAW,CAAC,EAAqB,CAC/B,KAAK,OAAS,EAAO,YAIjB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GAEpC,EAAgC,CACpC,OAAQ,EACJ,CAAE,MAAO,EAAK,WAAa,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CAAG,EACjE,CAAE,MAAO,EAAG,EAChB,GAAI,EAAiB,EAAQ,EAAE,EAC/B,QAAS,EAAQ,WACb,EAAQ,GAAK,CAAE,GAAI,EAAiB,EAAQ,EAAE,CAAE,EAAI,CAAC,KACrD,EAAQ,IAAM,CAAE,IAAK,EAAiB,EAAQ,GAAG,CAAE,EAAI,CAAC,KACxD,EAAQ,QACR,CACE,SAAU,IAAM,CACd,IAAM,EAAQ,EAAe,EAAQ,OAA4B,EAAE,GACnE,OAAO,EACH,CAAE,MAAO,EAAM,WAAa,EAAM,KAAO,CAAE,KAAM,EAAM,IAAK,EAAI,CAAC,CAAG,EACpE,SACH,CACL,EACA,CAAC,KACD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAY,OAAS,EACrB,CACE,WAAY,EAAY,IAAI,CAAC,KAAS,CACpC,KAAM,EAAI,SACV,QACE,EAAI,mBAAmB,WACnB,EAAa,EAAI,OAAO,EAAE,QAAQ,QAAS,EAAE,EAC7C,EAAI,OACZ,EAAE,CACJ,EACA,CAAC,CACP,EAEM,EAAW,MAAM,MAAM,sCAAuC,CAClE,OAAQ,OACR,QAAS,CACP,UAAW,KAAK,OAChB,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAMrC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EACR,EAAQ,SAAW,kBACnB,EAAS,OACT,EAAQ,MAAQ,EAClB,EAGF,IAAM,EAAW,EAAc,EAAQ,EAAE,EACzC,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,WAAa,OAC/B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACN,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,mCAAoC,CAC/D,QAAS,CACP,UAAW,KAAK,MAClB,CACF,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,EAAQ,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CACL,GAAI,GACJ,SAAU,WACN,EAAQ,YAAc,CAAE,QAAS,EAAQ,WAAY,EAAI,CAAC,CAChE,EACA,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
|
|
8
|
+
"debugId": "0FADE175A06AED6D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { SentlyError } from "../core/errors.js";
|
|
1
2
|
import type { MailgunConfig, MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
3
|
/** Error thrown when the Mailgun API returns a non-success response. */
|
|
3
|
-
export declare class MailgunError extends
|
|
4
|
+
export declare class MailgunError extends SentlyError {
|
|
4
5
|
readonly statusCode: number;
|
|
5
6
|
readonly apiError: unknown;
|
|
6
7
|
/** Creates a Mailgun API error with status code and response payload. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{s as
|
|
1
|
+
import{s as W}from"../chunk-z7j2n9k8.js";import{A as Q,y as G,z as J}from"../chunk-y4qxdyqn.js";import{O as N}from"../chunk-wzdcz88x.js";import{S as R,T as U}from"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";class X extends R{statusCode;apiError;constructor(q,z,F){super(q,U(z),{statusCode:z,provider:"mailgun",cause:F});this.statusCode=z;this.apiError=F;this.name="MailgunError"}}class ${apiKey;domain;baseUrl;constructor(q){this.apiKey=q.apiKey,this.domain=q.domain,this.baseUrl=q.region==="eu"?"https://api.eu.mailgun.net":"https://api.mailgun.net"}async send(q){let z=await W(q.attachments),F=G(q.from)[0],w=new FormData;if(w.append("from",F?J(F):""),w.append("to",G(q.to).map(J).join(", ")),q.cc)w.append("cc",G(q.cc).map(J).join(", "));if(q.bcc)w.append("bcc",G(q.bcc).map(J).join(", "));if(w.append("subject",q.subject),q.text)w.append("text",q.text);if(q.html)w.append("html",q.html);if(q.replyTo){let D=G(q.replyTo)[0];if(D)w.append("h:Reply-To",J(D))}for(let D of z){let Z=D.content instanceof Uint8Array?D.content:new TextEncoder().encode(String(D.content??"")),_=new Blob([new Uint8Array(Z)],{type:D.contentType??"application/octet-stream"});w.append("attachment",_,D.filename)}let Y=N(`api:${this.apiKey}`).replace(/\r\n/g,""),L=await fetch(`${this.baseUrl}/v3/${this.domain}/messages`,{method:"POST",headers:{Authorization:`Basic ${Y}`},body:w}),K=await L.json();if(!L.ok)throw new X(K.message??"Mailgun API error",L.status,K);let O=Q(q.to);return{messageId:K.id??"",accepted:O,rejected:[],response:K.message??"queued",envelope:{from:F?.address??"",to:O}}}async verify(){try{let q=N(`api:${this.apiKey}`).replace(/\r\n/g,""),z=await fetch(`${this.baseUrl}/v3/domains`,{headers:{Authorization:`Basic ${q}`}});if(!z.ok)return{ok:!1,provider:"mailgun",message:(await z.json().catch(()=>({}))).message??`HTTP ${z.status}`};return{ok:!0,provider:"mailgun",message:"API key is valid"}}catch(q){return{ok:!1,provider:"mailgun",message:q instanceof Error?q.message:String(q)}}}}export{$ as MailgunTransport,X as MailgunError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=CFC80730F3E78F5964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/mailgun.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Mailgun HTTP transport for sently.\n * Uses the Mailgun Messages API v3 with multipart/form-data.\n *\n * @example\n * ```ts\n * import { MailgunTransport } from \"sently/transports/mailgun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new MailgunTransport({\n * apiKey: \"key-...\",\n * domain: \"mg.example.com\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n MailgunConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Mailgun API returns a non-success response. */\nexport class MailgunError extends
|
|
5
|
+
"/**\n * @module\n * Mailgun HTTP transport for sently.\n * Uses the Mailgun Messages API v3 with multipart/form-data.\n *\n * @example\n * ```ts\n * import { MailgunTransport } from \"sently/transports/mailgun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new MailgunTransport({\n * apiKey: \"key-...\",\n * domain: \"mg.example.com\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n MailgunConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Mailgun API returns a non-success response. */\nexport class MailgunError extends SentlyError {\n /** Creates a Mailgun API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"mailgun\",\n cause: apiError,\n });\n this.name = \"MailgunError\";\n }\n}\n\n/**\n * Mailgun HTTP API transport (multipart/form-data).\n */\nexport class MailgunTransport implements Transport {\n /** Mailgun API key for basic authentication. */\n private readonly apiKey: string;\n /** Mailgun sending domain. */\n private readonly domain: string;\n /** Mailgun API base URL (US or EU region). */\n private readonly baseUrl: string;\n\n /** Creates a Mailgun transport with the given API key and domain. */\n constructor(config: MailgunConfig) {\n this.apiKey = config.apiKey;\n this.domain = config.domain;\n this.baseUrl =\n config.region === \"eu\" ? \"https://api.eu.mailgun.net\" : \"https://api.mailgun.net\";\n }\n\n /** Sends an email via the Mailgun Messages API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const form = new FormData();\n\n form.append(\"from\", from ? toMIMEHeader(from) : \"\");\n form.append(\"to\", parseAddresses(options.to).map(toMIMEHeader).join(\", \"));\n\n if (options.cc) {\n form.append(\"cc\", parseAddresses(options.cc).map(toMIMEHeader).join(\", \"));\n }\n if (options.bcc) {\n form.append(\"bcc\", parseAddresses(options.bcc).map(toMIMEHeader).join(\", \"));\n }\n\n form.append(\"subject\", options.subject);\n\n if (options.text) {\n form.append(\"text\", options.text);\n }\n if (options.html) {\n form.append(\"html\", options.html);\n }\n if (options.replyTo) {\n const replyTo = parseAddresses(options.replyTo)[0];\n if (replyTo) {\n form.append(\"h:Reply-To\", toMIMEHeader(replyTo));\n }\n }\n\n for (const attachment of attachments) {\n const content =\n attachment.content instanceof Uint8Array\n ? attachment.content\n : new TextEncoder().encode(String(attachment.content ?? \"\"));\n const blob = new Blob([new Uint8Array(content)], {\n type: attachment.contentType ?? \"application/octet-stream\",\n });\n form.append(\"attachment\", blob, attachment.filename);\n }\n\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${auth}`,\n },\n body: form,\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new MailgunError(payload.message ?? \"Mailgun API error\", response.status, payload);\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.id ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.message ?? \"queued\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Mailgun API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/domains`, {\n headers: {\n Authorization: `Basic ${auth}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"mailgun\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"mailgun\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"mailgun\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "0OA+BO,CAAM,KAAqB,JAAY,JAI1B,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MAAM,EAAS,EAAuB,CAAU,EAAG,CACjD,aACA,SAAU,UACV,MAAO,CACT,CAAC,EAPe,kBACA,gBAOhB,KAAK,KAAO,eAEhB,CAKO,MAAM,CAAsC,CAEhC,OAEA,OAEA,QAGjB,WAAW,CAAC,EAAuB,CACjC,KAAK,OAAS,EAAO,OACrB,KAAK,OAAS,EAAO,OACrB,KAAK,QACH,EAAO,SAAW,KAAO,6BAA+B,+BAItD,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAO,IAAI,SAKjB,GAHA,EAAK,OAAO,OAAQ,EAAO,EAAa,CAAI,EAAI,EAAE,EAClD,EAAK,OAAO,KAAM,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAErE,EAAQ,GACV,EAAK,OAAO,KAAM,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAE3E,GAAI,EAAQ,IACV,EAAK,OAAO,MAAO,EAAe,EAAQ,GAAG,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAK7E,GAFA,EAAK,OAAO,UAAW,EAAQ,OAAO,EAElC,EAAQ,KACV,EAAK,OAAO,OAAQ,EAAQ,IAAI,EAElC,GAAI,EAAQ,KACV,EAAK,OAAO,OAAQ,EAAQ,IAAI,EAElC,GAAI,EAAQ,QAAS,CACnB,IAAM,EAAU,EAAe,EAAQ,OAAO,EAAE,GAChD,GAAI,EACF,EAAK,OAAO,aAAc,EAAa,CAAO,CAAC,EAInD,QAAW,KAAc,EAAa,CACpC,IAAM,EACJ,EAAW,mBAAmB,WAC1B,EAAW,QACX,IAAI,YAAY,EAAE,OAAO,OAAO,EAAW,SAAW,EAAE,CAAC,EACzD,EAAO,IAAI,KAAK,CAAC,IAAI,WAAW,CAAO,CAAC,EAAG,CAC/C,KAAM,EAAW,aAAe,0BAClC,CAAC,EACD,EAAK,OAAO,aAAc,EAAM,EAAW,QAAQ,EAGrD,IAAM,EAAO,EAAa,OAAO,KAAK,QAAQ,EAAE,QAAQ,QAAS,EAAE,EAC7D,EAAW,MAAM,MAAM,GAAG,KAAK,cAAc,KAAK,kBAAmB,CACzE,OAAQ,OACR,QAAS,CACP,cAAe,SAAS,GAC1B,EACA,KAAM,CACR,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EAAa,EAAQ,SAAW,oBAAqB,EAAS,OAAQ,CAAO,EAGzF,IAAM,EAAW,EAAc,EAAQ,EAAE,EACzC,MAAO,CACL,UAAW,EAAQ,IAAM,GACzB,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,SAAW,SAC7B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACN,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAO,EAAa,OAAO,KAAK,QAAQ,EAAE,QAAQ,QAAS,EAAE,EAC7D,EAAW,MAAM,MAAM,GAAG,KAAK,qBAAsB,CACzD,QAAS,CACP,cAAe,SAAS,GAC1B,CACF,CAAC,EAED,GAAI,CAAC,EAAS,GAEZ,MAAO,CACL,GAAI,GACJ,SAAU,UACV,SAJe,MAAM,EAAS,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,GAIpC,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CAAE,GAAI,GAAM,SAAU,UAAW,QAAS,kBAAmB,EACpE,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,UACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
|
|
8
|
+
"debugId": "CFC80730F3E78F5964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SentlyError } from "../core/errors.js";
|
|
1
2
|
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
3
|
/** Postmark API configuration. */
|
|
3
4
|
export interface PostmarkConfig {
|
|
@@ -5,7 +6,7 @@ export interface PostmarkConfig {
|
|
|
5
6
|
serverToken: string;
|
|
6
7
|
}
|
|
7
8
|
/** Error thrown when the Postmark API returns a non-success response. */
|
|
8
|
-
export declare class PostmarkError extends
|
|
9
|
+
export declare class PostmarkError extends SentlyError {
|
|
9
10
|
readonly statusCode: number;
|
|
10
11
|
readonly apiError: unknown;
|
|
11
12
|
/** Creates a Postmark API error with status code and response payload. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{s as
|
|
1
|
+
import{s as U}from"../chunk-z7j2n9k8.js";import{A as J,y as D,z as F}from"../chunk-y4qxdyqn.js";import{O}from"../chunk-wzdcz88x.js";import{S as Q,T as R}from"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";class W extends Q{statusCode;apiError;constructor(q,w,G){super(q,R(w),{statusCode:w,provider:"postmark",cause:G});this.statusCode=w;this.apiError=G;this.name="PostmarkError"}}class Z{serverToken;constructor(q){this.serverToken=q.serverToken}async send(q){let w=await U(q.attachments),G=D(q.from)[0],X={From:G?F(G):"",To:D(q.to).map(F).join(", "),Subject:q.subject,...q.cc?{Cc:D(q.cc).map(F).join(", ")}:{},...q.bcc?{Bcc:D(q.bcc).map(F).join(", ")}:{},...q.replyTo?{ReplyTo:D(q.replyTo).map(F).join(", ")}:{},...q.text?{TextBody:q.text}:{},...q.html?{HtmlBody:q.html}:{},...q.headers?{Headers:Object.entries(q.headers).map(([z,Y])=>({Name:z,Value:Y}))}:{},...w.length>0?{Attachments:w.map((z)=>({Name:z.filename,Content:z.content instanceof Uint8Array?O(z.content).replace(/\r\n/g,""):z.content,ContentType:z.contentType??"application/octet-stream",...z.contentId?{ContentID:z.contentId}:{}}))}:{}},L=await fetch("https://api.postmarkapp.com/email",{method:"POST",headers:{"X-Postmark-Server-Token":this.serverToken,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(X)}),K=await L.json();if(!L.ok)throw new W(K.Message??"Postmark API error",L.status,K);return{messageId:K.MessageID??q.messageId??"",accepted:J(q.to),rejected:[],response:K.Message??"OK",envelope:{from:G?.address??"",to:[...J(q.to),...q.cc?J(q.cc):[],...q.bcc?J(q.bcc):[]]}}}async verify(){try{let q=await fetch("https://api.postmarkapp.com/server",{headers:{"X-Postmark-Server-Token":this.serverToken,Accept:"application/json"}}),w=await q.json();if(!q.ok)return{ok:!1,provider:"postmark",message:w.Message??`HTTP ${q.status}`};return{ok:!0,provider:"postmark",...w.Name?{message:w.Name}:{}}}catch(q){return{ok:!1,provider:"postmark",message:q instanceof Error?q.message:String(q)}}}}export{Z as PostmarkTransport,W as PostmarkError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=9220429864D05AC264756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/postmark.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Postmark HTTP API transport for sending email via api.postmarkapp.com.\n *\n * @example\n * ```ts\n * import { PostmarkTransport } from \"sently/transports/postmark\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new PostmarkTransport({ serverToken: process.env.POSTMARK_TOKEN! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Postmark API configuration. */\nexport interface PostmarkConfig {\n /** Postmark server API token. */\n serverToken: string;\n}\n\n/** Error thrown when the Postmark API returns a non-success response. */\nexport class PostmarkError extends
|
|
5
|
+
"/**\n * @module\n * Postmark HTTP API transport for sending email via api.postmarkapp.com.\n *\n * @example\n * ```ts\n * import { PostmarkTransport } from \"sently/transports/postmark\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new PostmarkTransport({ serverToken: process.env.POSTMARK_TOKEN! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Postmark API configuration. */\nexport interface PostmarkConfig {\n /** Postmark server API token. */\n serverToken: string;\n}\n\n/** Error thrown when the Postmark API returns a non-success response. */\nexport class PostmarkError extends SentlyError {\n /** Creates a Postmark API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"postmark\",\n cause: apiError,\n });\n this.name = \"PostmarkError\";\n }\n}\n\n/**\n * Postmark HTTP API transport.\n */\nexport class PostmarkTransport implements Transport {\n /** Postmark server API token for the X-Postmark-Server-Token header. */\n private readonly serverToken: string;\n\n /** Creates a Postmark transport with the given server token. */\n constructor(config: PostmarkConfig) {\n this.serverToken = config.serverToken;\n }\n\n /** Sends an email via the Postmark HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body = {\n From: from ? toMIMEHeader(from) : \"\",\n To: parseAddresses(options.to).map(toMIMEHeader).join(\", \"),\n Subject: options.subject,\n ...(options.cc ? { Cc: parseAddresses(options.cc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.bcc ? { Bcc: parseAddresses(options.bcc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.replyTo\n ? { ReplyTo: parseAddresses(options.replyTo).map(toMIMEHeader).join(\", \") }\n : {}),\n ...(options.text ? { TextBody: options.text } : {}),\n ...(options.html ? { HtmlBody: options.html } : {}),\n ...(options.headers\n ? { Headers: Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value })) }\n : {}),\n ...(attachments.length > 0\n ? {\n Attachments: attachments.map((att) => ({\n Name: att.filename,\n Content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ContentType: att.contentType ?? \"application/octet-stream\",\n ...(att.contentId ? { ContentID: att.contentId } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.postmarkapp.com/email\", {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n MessageID?: string;\n Message?: string;\n ErrorCode?: number;\n };\n\n if (!response.ok) {\n throw new PostmarkError(payload.Message ?? \"Postmark API error\", response.status, payload);\n }\n\n return {\n messageId: payload.MessageID ?? options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: payload.Message ?? \"OK\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Postmark server token by fetching server info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.postmarkapp.com/server\", {\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n Accept: \"application/json\",\n },\n });\n\n const payload = (await response.json()) as { Name?: string; Message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"postmark\",\n message: payload.Message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"postmark\",\n ...(payload.Name ? { message: payload.Name } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"postmark\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9220429864D05AC264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{r as L}from"../chunk-
|
|
1
|
+
import{r as L}from"../chunk-y72qggqf.js";import{s as N}from"../chunk-z7j2n9k8.js";import{A as K}from"../chunk-y4qxdyqn.js";import"../chunk-wzdcz88x.js";import{V as J}from"../chunk-rszb1n1d.js";function T(k){return k.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"no-subject"}function U(k){if(k==="darwin")return"open";if(k==="win32")return"start";return"xdg-open"}class V{outDir;open;format;constructor(k){this.outDir=k?.outDir??"./.emails",this.open=k?.open??!1,this.format=k?.format??"eml"}async send(k){let H=await N(k.attachments),P={...k,attachments:H},D=await L(P),F=await import("node:fs/promises");await F.mkdir(this.outDir,{recursive:!0});let Q=`${Date.now()}-${T(k.subject)}.${this.format}`,B=`${this.outDir}/${Q}`;if(this.format==="html"){let G=k.html??(k.text?`<pre>${k.text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`:"");await F.writeFile(B,G,"utf8")}else await F.writeFile(B,D.raw);if(console.log(`[sently preview] Written: ${B}`),this.open){let{spawn:G}=await import("node:child_process"),R=U(process.platform);G(R,[B],{detached:!0,stdio:"ignore"}).unref()}return{messageId:D.messageId,accepted:K(k.to),rejected:[],response:`preview: ${B}`,envelope:D.envelope}}async verify(){return{ok:!0,provider:"preview"}}}export{V as PreviewTransport};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=8D1AFBC1DE72071D64756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * PreviewTransport — development transport that writes emails to disk\n * instead of sending them. Optionally opens in the default browser.\n * Use in place of a real transport during local development.\n *\n * @example\n * ```ts\n * import { PreviewTransport } from 'sently/transports/preview'\n * const transport = new PreviewTransport({ outDir: './.emails', open: true })\n * ```\n */\nimport { extractEmails } from \"../core/address.js\";\nimport { buildMIME } from \"../core/mime.js\";\nimport type {\n MailOptions,\n PreviewConfig,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\nfunction sanitizeSubject(subject: string): string {\n const sanitized = subject.replace(/[^a-zA-Z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n return sanitized.slice(0, 40) || \"no-subject\";\n}\n\nfunction getOpenCommand(platform: string): string {\n if (platform === \"darwin\") {\n return \"open\";\n }\n if (platform === \"win32\") {\n return \"start\";\n }\n return \"xdg-open\";\n}\n\n/**\n * Development transport that writes emails to disk instead of sending them.\n */\nexport class PreviewTransport implements Transport {\n /** Directory where preview `.eml` or `.html` files are written. */\n private readonly outDir: string;\n /** Whether to open the written file in the default browser. */\n private readonly open: boolean;\n /** Output format: full MIME (`.eml`) or HTML body only. */\n private readonly format: \"eml\" | \"html\";\n\n /** Creates a preview transport with optional output directory and format. */\n constructor(config?: PreviewConfig) {\n this.outDir = config?.outDir ?? \"./.emails\";\n this.open = config?.open ?? false;\n this.format = config?.format ?? \"eml\";\n }\n\n /** Writes the message to disk and returns a synthetic SendResult. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const resolvedOptions = { ...options, attachments };\n const mime = await buildMIME(resolvedOptions);\n\n const fs = await import(\"node:fs/promises\");\n await fs.mkdir(this.outDir, { recursive: true });\n\n const filename = `${Date.now()}-${sanitizeSubject(options.subject)}.${this.format}`;\n const filepath = `${this.outDir}/${filename}`;\n\n if (this.format === \"html\") {\n const html =\n options.html ??\n (options.text\n ? `<pre>${options.text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")}</pre>`\n : \"\");\n await fs.writeFile(filepath, html, \"utf8\");\n } else {\n await fs.writeFile(filepath, mime.raw);\n }\n\n console.log(`[sently preview] Written: ${filepath}`);\n\n if (this.open) {\n const { spawn } = await import(\"node:child_process\");\n const command = getOpenCommand(process.platform);\n spawn(command, [filepath], { detached: true, stdio: \"ignore\" }).unref();\n }\n\n return {\n messageId: mime.messageId,\n accepted: extractEmails(options.to),\n rejected: [],\n response: `preview: ${filepath}`,\n envelope: mime.envelope,\n };\n }\n\n /** Always succeeds — preview transport requires no external connectivity. */\n async verify(): Promise<VerifyResult> {\n return { ok: true, provider: \"preview\" };\n }\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "qNAuBA,IAAS,JAAe,JAAC,HAAyB,CAEhD,OADkB,EAAQ,QAAQ,iBAAkB,GAAG,EAAE,QAAQ,SAAU,EAAE,EAC5D,MAAM,EAAG,EAAE,GAAK,aAGnC,SAAS,CAAc,CAAC,EAA0B,CAChD,GAAI,IAAa,SACf,MAAO,OAET,GAAI,IAAa,QACf,MAAO,QAET,MAAO,WAMF,MAAM,CAAsC,CAEhC,OAEA,KAEA,OAGjB,WAAW,CAAC,EAAwB,CAClC,KAAK,OAAS,GAAQ,QAAU,YAChC,KAAK,KAAO,GAAQ,MAAQ,GAC5B,KAAK,OAAS,GAAQ,QAAU,WAI5B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAkB,IAAK,EAAS,aAAY,EAC5C,EAAO,MAAM,EAAU,CAAe,EAEtC,EAAK,KAAa,4BACxB,MAAM,EAAG,MAAM,KAAK,OAAQ,CAAE,UAAW,EAAK,CAAC,EAE/C,IAAM,EAAW,GAAG,KAAK,IAAI,KAAK,EAAgB,EAAQ,OAAO,KAAK,KAAK,SACrE,EAAW,GAAG,KAAK,UAAU,IAEnC,GAAI,KAAK,SAAW,OAAQ,CAC1B,IAAM,EACJ,EAAQ,OACP,EAAQ,KACL,QAAQ,EAAQ,KAAK,QAAQ,KAAM,OAAO,EAAE,QAAQ,KAAM,MAAM,EAAE,QAAQ,KAAM,MAAM,UACtF,IACN,MAAM,EAAG,UAAU,EAAU,EAAM,MAAM,EAEzC,WAAM,EAAG,UAAU,EAAU,EAAK,GAAG,EAKvC,GAFA,QAAQ,IAAI,6BAA6B,GAAU,EAE/C,KAAK,KAAM,CACb,IAAQ,SAAU,KAAa,8BACzB,EAAU,EAAe,QAAQ,QAAQ,EAC/C,EAAM,EAAS,CAAC,CAAQ,EAAG,CAAE,SAAU,GAAM,MAAO,QAAS,CAAC,EAAE,MAAM,EAGxE,MAAO,CACL,UAAW,EAAK,UAChB,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,YAAY,IACtB,SAAU,EAAK,QACjB,OAII,OAAM,EAA0B,CACpC,MAAO,CAAE,GAAI,GAAM,SAAU,SAAU,EAE3C",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "8D1AFBC1DE72071D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SentlyError } from "../core/errors.js";
|
|
1
2
|
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
2
3
|
/** Resend API configuration. */
|
|
3
4
|
export interface ResendConfig {
|
|
@@ -13,7 +14,7 @@ export interface ResendConfig {
|
|
|
13
14
|
/** Maximum messages per Resend batch request (overridable per account). */
|
|
14
15
|
export declare const RESEND_BATCH_MAX = 100;
|
|
15
16
|
/** Error thrown when the Resend API returns a non-success response. */
|
|
16
|
-
export declare class ResendError extends
|
|
17
|
+
export declare class ResendError extends SentlyError {
|
|
17
18
|
readonly statusCode: number;
|
|
18
19
|
readonly apiError: unknown;
|
|
19
20
|
/** Creates a Resend API error with status code and response payload. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{q as
|
|
1
|
+
import{q as b}from"../chunk-qp4y0kp2.js";import{s as X}from"../chunk-z7j2n9k8.js";import{w as I}from"../chunk-tfxje74k.js";import{A as G,y as $,z as v}from"../chunk-y4qxdyqn.js";import{O as V}from"../chunk-wzdcz88x.js";import{S as K,T as N}from"../chunk-n601jhpd.js";import"../chunk-rszb1n1d.js";var Z=100;class Y extends K{statusCode;apiError;constructor(q,w,z){super(q,N(w),{statusCode:w,provider:"resend",cause:z});this.statusCode=w;this.apiError=z;this.name="ResendError"}}class H{apiKey;baseUrl;rateLimiter;batchMax=Z;constructor(q){this.apiKey=q.apiKey,this.baseUrl=q.baseUrl??"https://api.resend.com";let w=q.rateDelta??2;if(w>0)this.rateLimiter=new I(w,q.rateLimit??1000);else this.rateLimiter=null}async buildEmailBody(q){let w=await X(q.attachments),z=$(q.from)[0];return{from:z?v(z):"",to:G(q.to),subject:q.subject,...q.cc?{cc:G(q.cc)}:{},...q.bcc?{bcc:G(q.bcc)}:{},...q.replyTo?{reply_to:G(q.replyTo)}:{},...q.text?{text:q.text}:{},...q.html?{html:q.html}:{},...q.headers?{headers:q.headers}:{},...w.length>0?{attachments:w.map((F)=>({filename:F.filename,content:F.content instanceof Uint8Array?V(F.content).replace(/\r\n/g,""):F.content,...F.contentType?{content_type:F.contentType}:{}}))}:{}}}toSendResult(q,w){let z=$(q.from)[0];return{messageId:w.id??q.messageId??"",accepted:G(q.to),rejected:[],response:w.message??"Email sent",envelope:{from:z?.address??"",to:[...G(q.to),...q.cc?G(q.cc):[],...q.bcc?G(q.bcc):[]]},...w.batchError!==void 0?{batchError:w.batchError}:{}}}async send(q){let w=await this.buildEmailBody(q),z=b(q),F=await fetch(`${this.baseUrl}/emails`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json",...z?{"Idempotency-Key":z}:{}},body:JSON.stringify(w)}),O=await F.json();if(!F.ok)throw new Y(O.message??"Resend API error",F.status,O);return this.toSendResult(q,O)}async sendBatch(q){if(q.length===0)return[];let w=[];for(let z=0;z<q.length;z+=Z){if(z>0&&this.rateLimiter)await this.rateLimiter.acquire();let F=q.slice(z,z+Z),O=await Promise.all(F.map((J)=>this.buildEmailBody(J))),U=await fetch(`${this.baseUrl}/emails/batch`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(O)}),Q=await U.json();if(!U.ok)throw new Y(Q.message??"Resend batch API error",U.status,Q);let B=Q.data??[];for(let J=0;J<F.length;J++){let L=F[J],W=B[J];if(W?.id!==void 0){w.push(this.toSendResult(L,{id:W.id,...Q.message!==void 0?{message:Q.message}:{}}));continue}let P=W?.error??"Batch item failed";w.push(this.toSendResult(L,{message:P,batchError:new Y(P,U.status,W??{})}))}}return w}async verify(){try{let q=await fetch(`${this.baseUrl}/domains`,{headers:{Authorization:`Bearer ${this.apiKey}`}});if(!q.ok)return{ok:!1,provider:"resend",message:(await q.json().catch(()=>({}))).message??`HTTP ${q.status}`};return{ok:!0,provider:"resend",message:"API key is valid"}}catch(q){return{ok:!1,provider:"resend",message:q instanceof Error?q.message:String(q)}}}}export{H as ResendTransport,Y as ResendError,Z as RESEND_BATCH_MAX};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=AB58264C830939D764756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/resend.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Resend HTTP API transport for sending email via api.resend.com.\n *\n * @example\n * ```ts\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"onboarding@yourdomain.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * html: \"<p>Sent via Resend</p>\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { resolveIdempotencyKey } from \"../core/idempotency-key.js\";\nimport { RateLimiter } from \"../core/rate-limiter.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Resend API configuration. */\nexport interface ResendConfig {\n /** Resend API key (starts with `re_`). */\n apiKey: string;\n /** API base URL. Default: `https://api.resend.com`. */\n baseUrl?: string;\n /** Max batch HTTP requests per rate window. Default: `2` (Resend API limit). Set `0` to disable. */\n rateDelta?: number;\n /** Rate limit window in milliseconds. Default: `1000`. */\n rateLimit?: number;\n}\n\n/** Maximum messages per Resend batch request (overridable per account). */\nexport const RESEND_BATCH_MAX = 100;\n\n/** Error thrown when the Resend API returns a non-success response. */\nexport class ResendError extends
|
|
5
|
+
"/**\n * @module\n * Resend HTTP API transport for sending email via api.resend.com.\n *\n * @example\n * ```ts\n * import { ResendTransport } from \"sently/transports/resend\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),\n * });\n *\n * await mailer.send({\n * from: \"onboarding@yourdomain.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * html: \"<p>Sent via Resend</p>\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport { resolveIdempotencyKey } from \"../core/idempotency-key.js\";\nimport { RateLimiter } from \"../core/rate-limiter.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Resend API configuration. */\nexport interface ResendConfig {\n /** Resend API key (starts with `re_`). */\n apiKey: string;\n /** API base URL. Default: `https://api.resend.com`. */\n baseUrl?: string;\n /** Max batch HTTP requests per rate window. Default: `2` (Resend API limit). Set `0` to disable. */\n rateDelta?: number;\n /** Rate limit window in milliseconds. Default: `1000`. */\n rateLimit?: number;\n}\n\n/** Maximum messages per Resend batch request (overridable per account). */\nexport const RESEND_BATCH_MAX = 100;\n\n/** Error thrown when the Resend API returns a non-success response. */\nexport class ResendError extends SentlyError {\n /** Creates a Resend API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"resend\",\n cause: apiError,\n });\n this.name = \"ResendError\";\n }\n}\n\n/**\n * Resend HTTP API transport.\n */\nexport class ResendTransport implements Transport {\n /** Resend API key for Bearer authentication. */\n private readonly apiKey: string;\n /** Resend API base URL. */\n private readonly baseUrl: string;\n /** Optional rate limiter for multi-chunk batch sends. */\n private readonly rateLimiter: RateLimiter | null;\n\n /** Maximum messages per batch HTTP request. */\n readonly batchMax = RESEND_BATCH_MAX;\n\n /** Creates a Resend transport with the given API key. */\n constructor(config: ResendConfig) {\n this.apiKey = config.apiKey;\n this.baseUrl = config.baseUrl ?? \"https://api.resend.com\";\n\n const rateDelta = config.rateDelta ?? 2;\n if (rateDelta > 0) {\n this.rateLimiter = new RateLimiter(rateDelta, config.rateLimit ?? 1000);\n } else {\n this.rateLimiter = null;\n }\n }\n\n /** Build the JSON body for a single Resend email. */\n private async buildEmailBody(options: MailOptions): Promise<Record<string, unknown>> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n return {\n from: from ? toMIMEHeader(from) : \"\",\n to: extractEmails(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: extractEmails(options.cc) } : {}),\n ...(options.bcc ? { bcc: extractEmails(options.bcc) } : {}),\n ...(options.replyTo ? { reply_to: extractEmails(options.replyTo) } : {}),\n ...(options.text ? { text: options.text } : {}),\n ...(options.html ? { html: options.html } : {}),\n ...(options.headers ? { headers: options.headers } : {}),\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n filename: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ...(att.contentType ? { content_type: att.contentType } : {}),\n })),\n }\n : {}),\n };\n }\n\n /** Map a Resend API response id to a normalized SendResult. */\n private toSendResult(\n options: MailOptions,\n payload: { id?: string; message?: string; batchError?: unknown },\n ): SendResult {\n const from = parseAddresses(options.from)[0];\n return {\n messageId: payload.id ?? options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: payload.message ?? \"Email sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n ...(payload.batchError !== undefined ? { batchError: payload.batchError } : {}),\n };\n }\n\n /** Sends an email via the Resend HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const body = await this.buildEmailBody(options);\n const idempotencyKey = resolveIdempotencyKey(options);\n\n const response = await fetch(`${this.baseUrl}/emails`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n ...(idempotencyKey ? { \"Idempotency-Key\": idempotencyKey } : {}),\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new ResendError(payload.message ?? \"Resend API error\", response.status, payload);\n }\n\n return this.toSendResult(options, payload);\n }\n\n /** Send up to {@link RESEND_BATCH_MAX} attachment-free messages per batch request. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (messages.length === 0) {\n return [];\n }\n\n const results: SendResult[] = [];\n for (let offset = 0; offset < messages.length; offset += RESEND_BATCH_MAX) {\n if (offset > 0 && this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n\n const chunk = messages.slice(offset, offset + RESEND_BATCH_MAX);\n const bodies = await Promise.all(chunk.map((message) => this.buildEmailBody(message)));\n\n const response = await fetch(`${this.baseUrl}/emails/batch`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(bodies),\n });\n\n const payload = (await response.json()) as {\n data?: Array<{ id?: string; error?: string }>;\n message?: string;\n };\n\n if (!response.ok) {\n throw new ResendError(\n payload.message ?? \"Resend batch API error\",\n response.status,\n payload,\n );\n }\n\n const ids = payload.data ?? [];\n for (let i = 0; i < chunk.length; i++) {\n const message = chunk[i] as MailOptions;\n const item = ids[i];\n if (item?.id !== undefined) {\n results.push(\n this.toSendResult(message, {\n id: item.id,\n ...(payload.message !== undefined ? { message: payload.message } : {}),\n }),\n );\n continue;\n }\n\n const errorMessage = item?.error ?? \"Batch item failed\";\n results.push(\n this.toSendResult(message, {\n message: errorMessage,\n batchError: new ResendError(errorMessage, response.status, item ?? {}),\n }),\n );\n }\n }\n\n return results;\n }\n\n /** Verifies the Resend API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(`${this.baseUrl}/domains`, {\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"resend\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"resend\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"resend\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "AB58264C830939D764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -17,12 +17,21 @@ export declare class RetryTransport implements Transport {
|
|
|
17
17
|
private readonly retryOn;
|
|
18
18
|
/** Optional callback invoked before each retry attempt. */
|
|
19
19
|
private readonly onRetry;
|
|
20
|
+
/** Additional onRetry callback wired by {@link MailerImpl} hooks per send. */
|
|
21
|
+
private mailerOnRetry;
|
|
20
22
|
/** Wraps an inner transport with retry logic and optional backoff configuration. */
|
|
21
23
|
constructor(
|
|
22
24
|
/** Transport that performs the actual send on each attempt. */
|
|
23
25
|
inner: Transport, config?: RetryConfig,
|
|
24
26
|
/** Injectable sleep function for testing backoff timing. */
|
|
25
27
|
_sleep?: (ms: number) => Promise<void>);
|
|
28
|
+
/**
|
|
29
|
+
* Register a per-send onRetry callback from mailer lifecycle hooks.
|
|
30
|
+
* Do not share one {@link RetryTransport} across multiple mailers — the last
|
|
31
|
+
* `setMailerOnRetry` wins; overwriting an active callback logs a dev warning.
|
|
32
|
+
* @internal Used by {@link MailerImpl}; cleared after each send.
|
|
33
|
+
*/
|
|
34
|
+
setMailerOnRetry(callback: RetryConfig["onRetry"] | undefined): void;
|
|
26
35
|
/** Sends with retries according to configured backoff and retry rules. */
|
|
27
36
|
send(options: MailOptions): Promise<SendResult>;
|
|
28
37
|
/** Delegates to the inner transport verify or returns a default success result. */
|