sently 0.4.6 → 0.5.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 +81 -0
- package/README.md +180 -43
- 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-32dj0v9g.js +5 -0
- package/dist/{chunk-794hc3m4.js.map → chunk-32dj0v9g.js.map} +2 -2
- package/dist/chunk-cvmk9h1r.js +4 -0
- package/dist/chunk-cvmk9h1r.js.map +10 -0
- package/dist/chunk-j5m37k1c.js +4 -0
- package/dist/{chunk-ym3zzv8b.js.map → chunk-j5m37k1c.js.map} +2 -2
- package/dist/chunk-kdqxhybf.js +4 -0
- package/dist/chunk-kdqxhybf.js.map +10 -0
- package/dist/chunk-m2njnxre.js +13 -0
- package/dist/{chunk-tymfm441.js.map → chunk-m2njnxre.js.map} +2 -2
- package/dist/chunk-nhec1319.js +5 -0
- package/dist/chunk-nhec1319.js.map +10 -0
- package/dist/chunk-nj3wmg69.js +3 -0
- package/dist/{chunk-7fqv71z1.js.map → chunk-nj3wmg69.js.map} +2 -2
- package/dist/chunk-ntqmn6pe.js +4 -0
- package/dist/chunk-ntqmn6pe.js.map +10 -0
- package/dist/chunk-pjzsvsm6.js +4 -0
- package/dist/chunk-pjzsvsm6.js.map +11 -0
- package/dist/chunk-rhade81v.js +4 -0
- package/dist/{chunk-v0bahtg2.js.map → chunk-rhade81v.js.map} +1 -1
- package/dist/chunk-zwm5bfa4.js +4 -0
- package/dist/chunk-zwm5bfa4.js.map +10 -0
- package/dist/core/idempotency-key.d.ts +6 -0
- package/dist/core/rate-limiter.d.ts +23 -0
- package/dist/core/smtp.js +2 -31
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +42 -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/idempotency.d.ts +46 -0
- package/dist/idempotency.js +3 -0
- package/dist/idempotency.js.map +10 -0
- package/dist/index.d.ts +106 -16
- package/dist/index.js +115 -14
- package/dist/mailer.d.ts +17 -0
- package/dist/mailer.js +3 -0
- package/dist/mailer.js.map +9 -0
- package/dist/plugins/react.d.ts +35 -0
- package/dist/plugins/react.js +3 -0
- package/dist/plugins/react.js.map +10 -0
- package/dist/plugins/template.js +2 -28
- package/dist/plugins/template.js.map +2 -2
- package/dist/pool/pool.d.ts +1 -23
- package/dist/pool/pool.js +2 -16
- package/dist/pool/pool.js.map +5 -3
- package/dist/react.d.ts +20 -0
- package/dist/react.js +3 -0
- package/dist/react.js.map +9 -0
- 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.d.ts +16 -0
- package/dist/transports/resend.js +2 -109
- package/dist/transports/resend.js.map +3 -3
- package/dist/transports/retry.d.ts +2 -0
- package/dist/transports/retry.js +2 -78
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.d.ts +8 -0
- package/dist/transports/sendgrid.js +2 -132
- package/dist/transports/sendgrid.js.map +3 -3
- 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/dist/webhooks/brevo.d.ts +3 -0
- package/dist/webhooks/mailgun.d.ts +13 -0
- package/dist/webhooks/postmark.d.ts +3 -0
- package/dist/webhooks/resend.d.ts +12 -0
- package/dist/webhooks/sendgrid.d.ts +3 -0
- package/dist/webhooks/ses.d.ts +6 -0
- package/dist/webhooks/timing-safe-equal.d.ts +9 -0
- package/dist/webhooks/types.d.ts +32 -0
- package/dist/webhooks.d.ts +26 -0
- package/dist/webhooks.js +3 -0
- package/dist/webhooks.js.map +9 -0
- package/package.json +45 -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-f4c9ttmr.js.map +0 -11
- 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/core/types.d.ts
CHANGED
|
@@ -60,6 +60,16 @@ export interface MailOptions {
|
|
|
60
60
|
template?: string;
|
|
61
61
|
/** Template variables passed to the rendering engine */
|
|
62
62
|
data?: Record<string, unknown>;
|
|
63
|
+
/**
|
|
64
|
+
* React element rendered to html + text by the sently/react plugin.
|
|
65
|
+
* Requires `@react-email/render`.
|
|
66
|
+
*/
|
|
67
|
+
react?: unknown;
|
|
68
|
+
/**
|
|
69
|
+
* Idempotency key for deduplicating sends on retry or replay.
|
|
70
|
+
* When absent but `messageId` is set, transports derive a stable key from it.
|
|
71
|
+
*/
|
|
72
|
+
idempotencyKey?: string;
|
|
63
73
|
}
|
|
64
74
|
/** Result returned after a message is accepted for delivery. */
|
|
65
75
|
export interface SendResult {
|
|
@@ -73,6 +83,10 @@ export interface SendResult {
|
|
|
73
83
|
response: string;
|
|
74
84
|
/** SMTP envelope used for delivery. */
|
|
75
85
|
envelope: Envelope;
|
|
86
|
+
/** When true, the send was skipped because an idempotency key was already recorded. */
|
|
87
|
+
deduped?: boolean;
|
|
88
|
+
/** Set when this item failed inside an otherwise successful batch HTTP response. */
|
|
89
|
+
batchError?: unknown;
|
|
76
90
|
}
|
|
77
91
|
/** SMTP envelope addresses (MAIL FROM / RCPT TO). */
|
|
78
92
|
export interface Envelope {
|
|
@@ -122,6 +136,16 @@ export interface VerifyResult {
|
|
|
122
136
|
export interface Transport {
|
|
123
137
|
/** Send a message through this transport. */
|
|
124
138
|
send(options: MailOptions): Promise<SendResult>;
|
|
139
|
+
/**
|
|
140
|
+
* Send multiple messages in one provider batch request when supported.
|
|
141
|
+
* Messages with attachments may be excluded by the mailer and sent individually.
|
|
142
|
+
*/
|
|
143
|
+
sendBatch?(messages: MailOptions[]): Promise<SendResult[]>;
|
|
144
|
+
/**
|
|
145
|
+
* When set, {@link Mailer.sendBulk} splits batch sends into chunks of this size.
|
|
146
|
+
* Omit when the transport sends all messages in one HTTP request (e.g. SendGrid).
|
|
147
|
+
*/
|
|
148
|
+
batchMax?: number;
|
|
125
149
|
/** Test connectivity and credentials without sending mail. */
|
|
126
150
|
verify?(): Promise<VerifyResult>;
|
|
127
151
|
/** Release resources held by the transport. */
|
|
@@ -258,6 +282,17 @@ export interface BulkSendOptions {
|
|
|
258
282
|
onError?: (message: MailOptions, index: number, error: unknown) => void;
|
|
259
283
|
/** Max concurrent sends. Defaults to pool maxConnections or 1 */
|
|
260
284
|
concurrency?: number;
|
|
285
|
+
/** When true, stop sending after the first failure. Default: false */
|
|
286
|
+
stopOnError?: boolean;
|
|
287
|
+
/**
|
|
288
|
+
* Max batch HTTP requests per rate window (e.g. Resend default 2 req/s).
|
|
289
|
+
* Set to `0` to disable batch rate limiting. Default: `2`.
|
|
290
|
+
*/
|
|
291
|
+
rateDelta?: number;
|
|
292
|
+
/** Rate limit window in milliseconds. Default: `1000`. */
|
|
293
|
+
rateLimit?: number;
|
|
294
|
+
/** Injectable clock for batch rate limiting (testing). */
|
|
295
|
+
now?: () => number;
|
|
261
296
|
}
|
|
262
297
|
/** Result of a batch send operation. */
|
|
263
298
|
export interface BulkSendResult {
|
|
@@ -312,6 +347,13 @@ export interface PreviewConfig {
|
|
|
312
347
|
*/
|
|
313
348
|
format?: "eml" | "html";
|
|
314
349
|
}
|
|
350
|
+
/** Options for {@link createMailer} from `sently/mailer` — transport-only, smallest bundle. */
|
|
351
|
+
export interface TransportMailerOptions {
|
|
352
|
+
/** Transport that sends the message (HTTP API, SMTP wrapper, preview, etc.). */
|
|
353
|
+
transport: Transport;
|
|
354
|
+
/** Optional plugins run before each send. */
|
|
355
|
+
plugins?: MailPlugin[];
|
|
356
|
+
}
|
|
315
357
|
/** Options for {@link createMailer} — custom transport or SMTP config. */
|
|
316
358
|
export type CreateMailerOptions = ({
|
|
317
359
|
transport: Transport;
|
package/dist/detect.d.ts
CHANGED
|
@@ -11,5 +11,7 @@ export declare function createDefaultAdapter(options?: {
|
|
|
11
11
|
}): Promise<SocketAdapter>;
|
|
12
12
|
/**
|
|
13
13
|
* Create a ready-to-use Mailer instance.
|
|
14
|
+
*
|
|
15
|
+
* For HTTP transports and smallest bundles, prefer `import { createMailer } from "sently/mailer"`.
|
|
14
16
|
*/
|
|
15
17
|
export declare function createMailer(options: CreateMailerOptions): Promise<Mailer>;
|
package/dist/detect.js
CHANGED
|
@@ -1,181 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SMTPPool
|
|
3
|
-
} from "./chunk-mp5c9bfd.js";
|
|
4
|
-
import {
|
|
5
|
-
SMTPTransport
|
|
6
|
-
} from "./chunk-7fqv71z1.js";
|
|
7
|
-
import"./chunk-ym3zzv8b.js";
|
|
8
|
-
import"./chunk-x3szga4k.js";
|
|
9
|
-
import"./chunk-tymfm441.js";
|
|
10
|
-
import"./chunk-f4c9ttmr.js";
|
|
11
|
-
import"./chunk-794hc3m4.js";
|
|
12
|
-
import {
|
|
13
|
-
__require
|
|
14
|
-
} from "./chunk-v0bahtg2.js";
|
|
1
|
+
import{r as z,s as w}from"./chunk-pjzsvsm6.js";import"./chunk-ntqmn6pe.js";import{K as v}from"./chunk-rhade81v.js";function K(){if(typeof Bun<"u")return"bun";if(typeof Deno<"u")return"deno";if(typeof caches<"u"&&globalThis.navigator?.userAgent==="Cloudflare-Workers")return"cf-workers";if(typeof window<"u")return"browser";if(typeof process<"u"&&process.versions?.node)return"node";return"unknown"}async function x(k){let h=K();switch(h){case"node":{let{NodeAdapter:q}=await import("./adapters/node.js");return new q(k)}case"bun":{let{BunAdapter:q}=await import("./adapters/bun.js");return new q(k)}case"deno":{let{DenoAdapter:q}=await import("./adapters/deno.js");return new q(k)}case"cf-workers":{let{CloudflareAdapter:q}=await import("./adapters/cf.js");return new q(k)}default:throw Error(`No socket adapter available for runtime: ${h}`)}}async function Q(k){if("transport"in k)return z({transport:k.transport,...k.plugins!==void 0?{plugins:k.plugins}:{}});let h=k,q={...h.secure!==void 0?{secure:h.secure}:{},...h.connectionTimeout!==void 0?{connectionTimeout:h.connectionTimeout}:{},...h.tls!==void 0?{tls:h.tls}:{}};if(h.pool){let{SMTPPool:J}=await import("./pool/pool.js");return new w(new J(h,{createAdapter:async()=>h.adapter??await x(q)}),h.plugins)}let E=h.adapter??await x(q),{SMTPTransport:G}=await import("./transports/smtp.js");return new w(new G({...h,adapter:E}),h.plugins)}export{K as detectRuntime,Q as createMailer,x as createDefaultAdapter};
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
async function runPlugins(options, plugins) {
|
|
18
|
-
if (!plugins || plugins.length === 0) {
|
|
19
|
-
return options;
|
|
20
|
-
}
|
|
21
|
-
let current = options;
|
|
22
|
-
for (const plugin of plugins) {
|
|
23
|
-
current = await plugin(current);
|
|
24
|
-
}
|
|
25
|
-
return current;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/detect.ts
|
|
29
|
-
function detectRuntime() {
|
|
30
|
-
if (typeof Bun !== "undefined") {
|
|
31
|
-
return "bun";
|
|
32
|
-
}
|
|
33
|
-
if (typeof Deno !== "undefined") {
|
|
34
|
-
return "deno";
|
|
35
|
-
}
|
|
36
|
-
if (typeof caches !== "undefined" && globalThis.navigator?.userAgent === "Cloudflare-Workers") {
|
|
37
|
-
return "cf-workers";
|
|
38
|
-
}
|
|
39
|
-
if (typeof window !== "undefined") {
|
|
40
|
-
return "browser";
|
|
41
|
-
}
|
|
42
|
-
if (typeof process !== "undefined" && process.versions?.node) {
|
|
43
|
-
return "node";
|
|
44
|
-
}
|
|
45
|
-
return "unknown";
|
|
46
|
-
}
|
|
47
|
-
async function createDefaultAdapter(options) {
|
|
48
|
-
const runtime = detectRuntime();
|
|
49
|
-
switch (runtime) {
|
|
50
|
-
case "node": {
|
|
51
|
-
const { NodeAdapter } = await import("./adapters/node.js");
|
|
52
|
-
return new NodeAdapter(options);
|
|
53
|
-
}
|
|
54
|
-
case "bun": {
|
|
55
|
-
const { BunAdapter } = await import("./adapters/bun.js");
|
|
56
|
-
return new BunAdapter(options);
|
|
57
|
-
}
|
|
58
|
-
case "deno": {
|
|
59
|
-
const { DenoAdapter } = await import("./adapters/deno.js");
|
|
60
|
-
return new DenoAdapter(options);
|
|
61
|
-
}
|
|
62
|
-
case "cf-workers": {
|
|
63
|
-
const { CloudflareAdapter } = await import("./adapters/cf.js");
|
|
64
|
-
return new CloudflareAdapter(options);
|
|
65
|
-
}
|
|
66
|
-
default:
|
|
67
|
-
throw new Error(`No socket adapter available for runtime: ${runtime}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function createMailer(options) {
|
|
71
|
-
if ("transport" in options) {
|
|
72
|
-
return new MailerImpl(options.transport, options.plugins ?? []);
|
|
73
|
-
}
|
|
74
|
-
const smtpConfig = options;
|
|
75
|
-
if (smtpConfig.pool) {
|
|
76
|
-
return new MailerImpl(new SMTPPool(smtpConfig, {
|
|
77
|
-
createAdapter: async () => smtpConfig.adapter ?? await createDefaultAdapter({
|
|
78
|
-
...smtpConfig.secure !== undefined ? { secure: smtpConfig.secure } : {},
|
|
79
|
-
...smtpConfig.connectionTimeout !== undefined ? { connectionTimeout: smtpConfig.connectionTimeout } : {},
|
|
80
|
-
...smtpConfig.tls !== undefined ? { tls: smtpConfig.tls } : {}
|
|
81
|
-
})
|
|
82
|
-
}), smtpConfig.plugins);
|
|
83
|
-
}
|
|
84
|
-
const adapter = smtpConfig.adapter ?? await createDefaultAdapter({
|
|
85
|
-
...smtpConfig.secure !== undefined ? { secure: smtpConfig.secure } : {},
|
|
86
|
-
...smtpConfig.connectionTimeout !== undefined ? { connectionTimeout: smtpConfig.connectionTimeout } : {},
|
|
87
|
-
...smtpConfig.tls !== undefined ? { tls: smtpConfig.tls } : {}
|
|
88
|
-
});
|
|
89
|
-
return new MailerImpl(new SMTPTransport({ ...smtpConfig, adapter }), smtpConfig.plugins);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
class MailerImpl {
|
|
93
|
-
transport;
|
|
94
|
-
plugins;
|
|
95
|
-
constructor(transport, plugins = []) {
|
|
96
|
-
this.transport = transport;
|
|
97
|
-
this.plugins = plugins;
|
|
98
|
-
}
|
|
99
|
-
async send(options) {
|
|
100
|
-
const processed = await runPlugins(options, this.plugins);
|
|
101
|
-
return this.transport.send(processed);
|
|
102
|
-
}
|
|
103
|
-
async sendBulk(messages, options) {
|
|
104
|
-
const concurrency = options?.concurrency ?? 1;
|
|
105
|
-
const results = new Array(messages.length);
|
|
106
|
-
const queue = [...messages.entries()];
|
|
107
|
-
let active = 0;
|
|
108
|
-
await new Promise((resolve) => {
|
|
109
|
-
if (messages.length === 0) {
|
|
110
|
-
resolve();
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const maybeDone = () => {
|
|
114
|
-
if (queue.length === 0 && active === 0) {
|
|
115
|
-
resolve();
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
const processNext = () => {
|
|
119
|
-
if (queue.length === 0) {
|
|
120
|
-
maybeDone();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const entry = queue.shift();
|
|
124
|
-
if (entry === undefined) {
|
|
125
|
-
maybeDone();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const [index, message] = entry;
|
|
129
|
-
active++;
|
|
130
|
-
this.send(message).then((result) => {
|
|
131
|
-
results[index] = { status: "sent", result };
|
|
132
|
-
options?.onSuccess?.(message, index, result);
|
|
133
|
-
}).catch((error) => {
|
|
134
|
-
results[index] = { status: "failed", error };
|
|
135
|
-
options?.onError?.(message, index, error);
|
|
136
|
-
}).finally(() => {
|
|
137
|
-
active--;
|
|
138
|
-
processNext();
|
|
139
|
-
maybeDone();
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
for (let i = 0;i < concurrency; i++) {
|
|
143
|
-
processNext();
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
let sent = 0;
|
|
147
|
-
let failed = 0;
|
|
148
|
-
for (const result of results) {
|
|
149
|
-
if (result.status === "sent") {
|
|
150
|
-
sent++;
|
|
151
|
-
} else {
|
|
152
|
-
failed++;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
total: messages.length,
|
|
157
|
-
sent,
|
|
158
|
-
failed,
|
|
159
|
-
results
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
verify() {
|
|
163
|
-
if (this.transport.verify) {
|
|
164
|
-
return this.transport.verify();
|
|
165
|
-
}
|
|
166
|
-
return Promise.resolve({ ok: true, provider: "mailer" });
|
|
167
|
-
}
|
|
168
|
-
close() {
|
|
169
|
-
if (this.transport.close) {
|
|
170
|
-
return this.transport.close();
|
|
171
|
-
}
|
|
172
|
-
return Promise.resolve();
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
export {
|
|
176
|
-
detectRuntime,
|
|
177
|
-
createMailer,
|
|
178
|
-
createDefaultAdapter
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
//# debugId=685AB5DE4D1E74D864756E2164756E21
|
|
3
|
+
//# debugId=7BF2493D1AD14E8964756E2164756E21
|
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": "+HAYO,GAAS,LAAa,EAAY,CACvC,GAAI,OAAO,IAAQ,IACjB,MAAO,MAET,GAAI,OAAO,KAAS,IAClB,MAAO,OAET,GAAI,OAAO,OAAW,KAAe,WAAW,WAAW,YAAc,qBACvE,MAAO,aAET,GAAI,OAAO,OAAW,IACpB,MAAO,UAET,GAAI,OAAO,QAAY,KAAe,QAAQ,UAAU,KACtD,MAAO,OAET,MAAO,UAMT,eAAsB,CAAoB,CAAC,EAIhB,CACzB,IAAM,EAAU,EAAc,EAE9B,OAAQ,OACD,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,MAAO,CACV,IAAQ,cAAe,KAAa,6BACpC,OAAO,IAAI,EAAW,CAAO,CAC/B,KACK,OAAQ,CACX,IAAQ,eAAgB,KAAa,8BACrC,OAAO,IAAI,EAAY,CAAO,CAChC,KACK,aAAc,CACjB,IAAQ,qBAAsB,KAAa,4BAC3C,OAAO,IAAI,EAAkB,CAAO,CACtC,SAEE,MAAU,MAAM,4CAA4C,GAAS,GAS3E,eAAsB,CAAY,CAAC,EAA+C,CAChF,GAAI,cAAe,EACjB,OAAO,EAAsB,CAC3B,UAAW,EAAQ,aACf,EAAQ,UAAY,OAAY,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,CACtE,CAAC,EAGH,IAAM,EAAa,EACb,EAAiB,IACjB,EAAW,SAAW,OAAY,CAAE,OAAQ,EAAW,MAAO,EAAI,CAAC,KACnE,EAAW,oBAAsB,OACjC,CAAE,kBAAmB,EAAW,iBAAkB,EAClD,CAAC,KACD,EAAW,MAAQ,OAAY,CAAE,IAAK,EAAW,GAAI,EAAI,CAAC,CAChE,EAEA,GAAI,EAAW,KAAM,CACnB,IAAQ,YAAa,KAAa,0BAClC,OAAO,IAAI,EACT,IAAI,EAAS,EAAY,CACvB,cAAe,SACb,EAAW,SAAY,MAAM,EAAqB,CAAc,CACpE,CAAC,EACD,EAAW,OACb,EAGF,IAAM,EAAU,EAAW,SAAY,MAAM,EAAqB,CAAc,GACxE,iBAAkB,KAAa,gCAEvC,OAAO,IAAI,EAAW,IAAI,EAAc,IAAK,EAAY,SAAQ,CAAC,EAAG,EAAW,OAAO",
|
|
8
|
+
"debugId": "7BF2493D1AD14E8964756E2164756E21",
|
|
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{G,J as E}from"./chunk-32dj0v9g.js";import"./chunk-rhade81v.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=D2033210B783CB1164756E2164756E21
|
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": "D2033210B783CB1164756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MailOptions, SendResult, Transport, VerifyResult } from "./core/types.js";
|
|
2
|
+
/** Key-value store for idempotency deduplication. */
|
|
3
|
+
export interface IdempotencyStore {
|
|
4
|
+
/** Returns true when the key was already recorded. */
|
|
5
|
+
has(key: string): Promise<boolean>;
|
|
6
|
+
/** Record a key, optionally expiring after `ttlMs` milliseconds. */
|
|
7
|
+
set(key: string, ttlMs?: number): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */
|
|
10
|
+
export declare class MemoryIdempotencyStore implements IdempotencyStore {
|
|
11
|
+
private readonly entries;
|
|
12
|
+
/** Returns true when the key exists and has not expired. */
|
|
13
|
+
has(key: string): Promise<boolean>;
|
|
14
|
+
/** Record a key with optional TTL in milliseconds. */
|
|
15
|
+
set(key: string, ttlMs?: number): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/** Options for {@link IdempotencyTransport}. */
|
|
18
|
+
export interface IdempotencyTransportOptions {
|
|
19
|
+
/** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */
|
|
20
|
+
store?: IdempotencyStore;
|
|
21
|
+
/** TTL in milliseconds for recorded keys. Default: 24 hours. */
|
|
22
|
+
ttlMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Transport decorator that skips duplicate sends when an idempotency key
|
|
26
|
+
* was already recorded. Checks the store once before delegating to the inner
|
|
27
|
+
* transport, so retries inside RetryTransport reuse the same key automatically.
|
|
28
|
+
*/
|
|
29
|
+
export declare class IdempotencyTransport implements Transport {
|
|
30
|
+
/** Transport that performs the actual send when the key is new. */
|
|
31
|
+
private readonly inner;
|
|
32
|
+
private readonly store;
|
|
33
|
+
private readonly ttlMs;
|
|
34
|
+
/** Wraps an inner transport with idempotency deduplication. */
|
|
35
|
+
constructor(
|
|
36
|
+
/** Transport that performs the actual send when the key is new. */
|
|
37
|
+
inner: Transport, options?: IdempotencyTransportOptions);
|
|
38
|
+
/** Sends once per idempotency key; returns a synthetic result on duplicate keys. */
|
|
39
|
+
send(options: MailOptions): Promise<SendResult>;
|
|
40
|
+
/** Delegates batch sends to the inner transport when available. */
|
|
41
|
+
sendBatch(messages: MailOptions[]): Promise<SendResult[]>;
|
|
42
|
+
/** Delegates to the inner transport verify or returns a default success result. */
|
|
43
|
+
verify(): Promise<VerifyResult>;
|
|
44
|
+
/** Delegates close to the inner transport if available. */
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{j as F}from"./chunk-zwm5bfa4.js";import{n as C,p as B}from"./chunk-kdqxhybf.js";import"./chunk-32dj0v9g.js";import"./chunk-rhade81v.js";class G{entries=new Map;async has(q){let w=this.entries.get(q);if(w===void 0)return!1;if(w<=Date.now())return this.entries.delete(q),!1;return!0}async set(q,w){let z=w!==void 0?Date.now()+w:Number.POSITIVE_INFINITY;this.entries.set(q,z)}}function H(q){let w=C(q.from)[0],z=[...B(q.to),...q.cc?B(q.cc):[],...q.bcc?B(q.bcc):[]];return{messageId:q.messageId??"",accepted:B(q.to),rejected:[],response:"Deduped — idempotency key already recorded",envelope:{from:w?.address??"",to:z},deduped:!0}}class J{inner;store;ttlMs;constructor(q,w){this.inner=q;this.store=w?.store??new G,this.ttlMs=w?.ttlMs??86400000}async send(q){let w=F(q);if(w===void 0)return this.inner.send(q);if(await this.store.has(w))return H(q);let z=await this.inner.send(q);return await this.store.set(w,this.ttlMs),z}async sendBatch(q){if(this.inner.sendBatch)return this.inner.sendBatch(q);return Promise.all(q.map((w)=>this.send(w)))}async verify(){if(this.inner.verify)return this.inner.verify();return{ok:!0,provider:"idempotency"}}async close(){await this.inner.close?.()}}export{G as MemoryIdempotencyStore,J as IdempotencyTransport};
|
|
2
|
+
|
|
3
|
+
//# debugId=C9F463170D0BFD9764756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/idempotency.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * IdempotencyTransport — decorator that deduplicates sends on retry or replay.\n *\n * Wrap **outside** RetryTransport so all retry attempts share one key:\n *\n * ```ts\n * new IdempotencyTransport(new RetryTransport(inner))\n * ```\n *\n * For production, supply a shared store (Redis, Dragonfly, etc.).\n *\n * @example\n * ```ts\n * import { IdempotencyTransport, MemoryIdempotencyStore } from \"sently/idempotency\";\n * import { RetryTransport } from \"sently/transports/retry\";\n * import { ResendTransport } from \"sently/transports/resend\";\n *\n * const transport = new IdempotencyTransport(\n * new RetryTransport(new ResendTransport({ apiKey })),\n * { store: new MemoryIdempotencyStore(), ttlMs: 86_400_000 },\n * );\n * ```\n */\nimport { extractEmails, parseAddresses } from \"./core/address.js\";\nimport { resolveIdempotencyKey } from \"./core/idempotency-key.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"./core/types.js\";\n\n/** Key-value store for idempotency deduplication. */\nexport interface IdempotencyStore {\n /** Returns true when the key was already recorded. */\n has(key: string): Promise<boolean>;\n /** Record a key, optionally expiring after `ttlMs` milliseconds. */\n set(key: string, ttlMs?: number): Promise<void>;\n}\n\n/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, number>();\n\n /** Returns true when the key exists and has not expired. */\n async has(key: string): Promise<boolean> {\n const expiresAt = this.entries.get(key);\n if (expiresAt === undefined) {\n return false;\n }\n if (expiresAt <= Date.now()) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n /** Record a key with optional TTL in milliseconds. */\n async set(key: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs !== undefined ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;\n this.entries.set(key, expiresAt);\n }\n}\n\n/** Options for {@link IdempotencyTransport}. */\nexport interface IdempotencyTransportOptions {\n /** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */\n store?: IdempotencyStore;\n /** TTL in milliseconds for recorded keys. Default: 24 hours. */\n ttlMs?: number;\n}\n\nfunction syntheticDedupedResult(options: MailOptions): SendResult {\n const from = parseAddresses(options.from)[0];\n const to = [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ];\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Deduped — idempotency key already recorded\",\n envelope: {\n from: from?.address ?? \"\",\n to,\n },\n deduped: true,\n };\n}\n\n/**\n * Transport decorator that skips duplicate sends when an idempotency key\n * was already recorded. Checks the store once before delegating to the inner\n * transport, so retries inside RetryTransport reuse the same key automatically.\n */\nexport class IdempotencyTransport implements Transport {\n private readonly store: IdempotencyStore;\n private readonly ttlMs: number;\n\n /** Wraps an inner transport with idempotency deduplication. */\n constructor(\n /** Transport that performs the actual send when the key is new. */\n private readonly inner: Transport,\n options?: IdempotencyTransportOptions,\n ) {\n this.store = options?.store ?? new MemoryIdempotencyStore();\n this.ttlMs = options?.ttlMs ?? 86_400_000;\n }\n\n /** Sends once per idempotency key; returns a synthetic result on duplicate keys. */\n async send(options: MailOptions): Promise<SendResult> {\n const key = resolveIdempotencyKey(options);\n if (key === undefined) {\n return this.inner.send(options);\n }\n\n if (await this.store.has(key)) {\n return syntheticDedupedResult(options);\n }\n\n const result = await this.inner.send(options);\n await this.store.set(key, this.ttlMs);\n return result;\n }\n\n /** Delegates batch sends to the inner transport when available. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (this.inner.sendBatch) {\n return this.inner.sendBatch(messages);\n }\n return Promise.all(messages.map((message) => this.send(message)));\n }\n\n /** Delegates to the inner transport verify or returns a default success result. */\n async verify(): Promise<VerifyResult> {\n if (this.inner.verify) {\n return this.inner.verify();\n }\n return { ok: true, provider: \"idempotency\" };\n }\n\n /** Delegates close to the inner transport if available. */\n async close(): Promise<void> {\n await this.inner.close?.();\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "iKAqCO,AAAM,LAAmD,LAC7C,QAAU,IAAI,SAGzB,IAAG,CAAC,EAA+B,CACvC,IAAM,EAAY,KAAK,QAAQ,IAAI,CAAG,EACtC,GAAI,IAAc,OAChB,MAAO,GAET,GAAI,GAAa,KAAK,IAAI,EAExB,OADA,KAAK,QAAQ,OAAO,CAAG,EAChB,GAET,MAAO,QAIH,IAAG,CAAC,EAAa,EAA+B,CACpD,IAAM,EAAY,IAAU,OAAY,KAAK,IAAI,EAAI,EAAQ,OAAO,kBACpE,KAAK,QAAQ,IAAI,EAAK,CAAS,EAEnC,CAUA,SAAS,CAAsB,CAAC,EAAkC,CAChE,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAK,CACT,GAAG,EAAc,EAAQ,EAAE,EAC3B,GAAI,EAAQ,GAAK,EAAc,EAAQ,EAAE,EAAI,CAAC,EAC9C,GAAI,EAAQ,IAAM,EAAc,EAAQ,GAAG,EAAI,CAAC,CAClD,EAEA,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,6CACV,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,IACF,EACA,QAAS,EACX,EAQK,MAAM,CAA0C,CAOlC,MANF,MACA,MAGjB,WAAW,CAEQ,EACjB,EACA,CAFiB,aAGjB,KAAK,MAAQ,GAAS,OAAS,IAAI,EACnC,KAAK,MAAQ,GAAS,OAAS,cAI3B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAM,EAAsB,CAAO,EACzC,GAAI,IAAQ,OACV,OAAO,KAAK,MAAM,KAAK,CAAO,EAGhC,GAAI,MAAM,KAAK,MAAM,IAAI,CAAG,EAC1B,OAAO,EAAuB,CAAO,EAGvC,IAAM,EAAS,MAAM,KAAK,MAAM,KAAK,CAAO,EAE5C,OADA,MAAM,KAAK,MAAM,IAAI,EAAK,KAAK,KAAK,EAC7B,OAIH,UAAS,CAAC,EAAgD,CAC9D,GAAI,KAAK,MAAM,UACb,OAAO,KAAK,MAAM,UAAU,CAAQ,EAEtC,OAAO,QAAQ,IAAI,EAAS,IAAI,CAAC,IAAY,KAAK,KAAK,CAAO,CAAC,CAAC,OAI5D,OAAM,EAA0B,CACpC,GAAI,KAAK,MAAM,OACb,OAAO,KAAK,MAAM,OAAO,EAE3B,MAAO,CAAE,GAAI,GAAM,SAAU,aAAc,OAIvC,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,QAAQ,EAE7B",
|
|
8
|
+
"debugId": "C9F463170D0BFD9764756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,19 +19,109 @@
|
|
|
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
|
+
/** Key-value store for idempotency deduplication. */
|
|
46
|
+
IdempotencyStore,
|
|
47
|
+
/** Options for {@link IdempotencyTransport}. */
|
|
48
|
+
IdempotencyTransportOptions, } from "./idempotency.js";
|
|
49
|
+
export {
|
|
50
|
+
/** Transport decorator that deduplicates sends on retry or replay. */
|
|
51
|
+
IdempotencyTransport,
|
|
52
|
+
/** In-memory idempotency store with optional TTL expiry. */
|
|
53
|
+
MemoryIdempotencyStore, } from "./idempotency.js";
|
|
54
|
+
export type {
|
|
55
|
+
/** Renders a template string with the given data object. */
|
|
56
|
+
TemplateEngine,
|
|
57
|
+
/** Configuration for the template plugin. */
|
|
58
|
+
TemplatePluginConfig, } from "./plugins/template.js";
|
|
59
|
+
export {
|
|
60
|
+
/** Built-in zero-dependency template engine using `{{variable}}` interpolation. */
|
|
61
|
+
simpleEngine,
|
|
62
|
+
/** Create a template plugin that renders HTML from a named template and data. */
|
|
63
|
+
templatePlugin, } from "./plugins/template.js";
|
|
64
|
+
export {
|
|
65
|
+
/** SMTP connection pool with optional rate limiting. */
|
|
66
|
+
SMTPPool, } from "./pool/pool.js";
|
|
67
|
+
export {
|
|
68
|
+
/** Error thrown when the Brevo API returns a non-success response. */
|
|
69
|
+
BrevoError,
|
|
70
|
+
/** Brevo HTTP API transport. */
|
|
71
|
+
BrevoTransport, } from "./transports/brevo.js";
|
|
72
|
+
export {
|
|
73
|
+
/** Error thrown when the Mailgun API returns a non-success response. */
|
|
74
|
+
MailgunError,
|
|
75
|
+
/** Mailgun HTTP API transport (multipart/form-data). */
|
|
76
|
+
MailgunTransport, } from "./transports/mailgun.js";
|
|
77
|
+
export {
|
|
78
|
+
/** Error thrown when the Postmark API returns a non-success response. */
|
|
79
|
+
PostmarkError,
|
|
80
|
+
/** Postmark HTTP API transport. */
|
|
81
|
+
PostmarkTransport, } from "./transports/postmark.js";
|
|
82
|
+
export {
|
|
83
|
+
/** Development transport that writes emails to disk instead of sending them. */
|
|
84
|
+
PreviewTransport, } from "./transports/preview.js";
|
|
85
|
+
export {
|
|
86
|
+
/** Maximum messages per Resend batch request. */
|
|
87
|
+
RESEND_BATCH_MAX,
|
|
88
|
+
/** Error thrown when the Resend API returns a non-success response. */
|
|
89
|
+
ResendError,
|
|
90
|
+
/** Resend HTTP API transport. */
|
|
91
|
+
ResendTransport, } from "./transports/resend.js";
|
|
92
|
+
export {
|
|
93
|
+
/** Decorator transport that retries failed sends with configurable backoff. */
|
|
94
|
+
RetryTransport, } from "./transports/retry.js";
|
|
95
|
+
export {
|
|
96
|
+
/** Error thrown when the SendGrid API returns a non-success response. */
|
|
97
|
+
SendGridError,
|
|
98
|
+
/** SendGrid v3 HTTP API transport. */
|
|
99
|
+
SendGridTransport, } from "./transports/sendgrid.js";
|
|
100
|
+
export {
|
|
101
|
+
/** Error thrown when the AWS SES API returns a non-success response. */
|
|
102
|
+
SESError,
|
|
103
|
+
/** AWS SES v2 HTTP API transport. */
|
|
104
|
+
SESTransport, } from "./transports/ses.js";
|
|
105
|
+
export {
|
|
106
|
+
/** SMTP transport orchestrating adapter, MIME builder, and protocol logic. */
|
|
107
|
+
SMTPTransport, } from "./transports/smtp.js";
|
|
108
|
+
export type { EmailEvent } from "./webhooks.js";
|
|
109
|
+
export {
|
|
110
|
+
/** Parse a Brevo webhook payload into normalized email events. */
|
|
111
|
+
parseBrevoWebhook,
|
|
112
|
+
/** Parse a Mailgun webhook payload into normalized email events. */
|
|
113
|
+
parseMailgunWebhook,
|
|
114
|
+
/** Parse a Postmark webhook payload into normalized email events. */
|
|
115
|
+
parsePostmarkWebhook,
|
|
116
|
+
/** Parse a Resend webhook payload into normalized email events. */
|
|
117
|
+
parseResendWebhook,
|
|
118
|
+
/** Parse a SendGrid Event Webhook payload into normalized email events. */
|
|
119
|
+
parseSendGridWebhook,
|
|
120
|
+
/** Parse an AWS SES SNS webhook payload into normalized email events. */
|
|
121
|
+
parseSesWebhook,
|
|
122
|
+
/** Verify a Mailgun webhook using the nested signature object. */
|
|
123
|
+
verifyMailgunPayload,
|
|
124
|
+
/** Verify a Mailgun webhook HMAC signature. */
|
|
125
|
+
verifyMailgunSignature,
|
|
126
|
+
/** Verify a Resend webhook Svix-style signature. */
|
|
127
|
+
verifyResendSignature, } from "./webhooks.js";
|