sently 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/dist/chunk-0cdwxgck.js +344 -0
- package/dist/chunk-0cdwxgck.js.map +11 -0
- package/dist/{chunk-tch6s785.js → chunk-vmxqrew7.js} +6 -337
- package/dist/chunk-vmxqrew7.js.map +12 -0
- package/dist/src/adapters/bun.js +5 -3
- package/dist/src/adapters/bun.js.map +3 -3
- package/dist/src/adapters/node.js +5 -3
- package/dist/src/adapters/node.js.map +3 -3
- package/dist/src/index.js +8 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/pool/pool.js +5 -3
- package/dist/src/pool/pool.js.map +2 -2
- package/dist/src/transports/brevo.js +88 -0
- package/dist/src/transports/brevo.js.map +10 -0
- package/dist/src/transports/mailgun.js +95 -0
- package/dist/src/transports/mailgun.js.map +10 -0
- package/dist/src/transports/ses.js +214 -0
- package/dist/src/transports/ses.js.map +11 -0
- package/dist/src/transports/smtp.js +3 -2
- package/dist/src/transports/smtp.js.map +1 -1
- package/package.json +13 -1
- package/dist/chunk-tch6s785.js.map +0 -14
package/dist/src/index.js.map
CHANGED
package/dist/src/pool/pool.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
buildMIME,
|
|
3
2
|
closeSMTPSession,
|
|
4
3
|
deliverSMTPMessage,
|
|
5
4
|
openSMTPSession,
|
|
6
5
|
resolveSMTPConfig
|
|
7
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-vmxqrew7.js";
|
|
7
|
+
import {
|
|
8
|
+
buildMIME
|
|
9
|
+
} from "../../chunk-0cdwxgck.js";
|
|
8
10
|
import"../../chunk-vm70w4e3.js";
|
|
9
11
|
import {
|
|
10
12
|
resolveAttachments
|
|
@@ -260,4 +262,4 @@ export {
|
|
|
260
262
|
RateLimiter
|
|
261
263
|
};
|
|
262
264
|
|
|
263
|
-
//# debugId=
|
|
265
|
+
//# debugId=90F2338A3E37CF8464756E2164756E21
|
|
@@ -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 config: SMTPConfig;\n maxMessages: number;\n connectHost: string;\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
|
"import type {\n MailOptions,\n PoolConfig,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n} from \"../core/types.js\";\nimport { resolveSMTPConfig } from \"../transports/smtp.js\";\nimport { createPooledConnection, type PooledConnection } from \"./connection.js\";\n\n/** Options for {@link SMTPPool}. */\nexport interface SMTPPoolOptions {\n /** Factory for a new socket adapter per pooled connection. */\n createAdapter?: () => Promise<SocketAdapter> | SocketAdapter;\n /** Injectable clock for rate limiting (testing). */\n now?: () => number;\n}\n\ninterface QueueEntry {\n options: MailOptions;\n resolve: (result: SendResult) => void;\n reject: (error: unknown) => void;\n}\n\n/**\n * Token bucket rate limiter with lazy refill on acquire.\n */\nclass RateLimiter {\n private tokens: number;\n private lastRefill: number;\n private waiters: Array<() => void> = [];\n\n constructor(\n private readonly rateDelta: number,\n private readonly rateLimit: number,\n private readonly now: () => number = Date.now,\n ) {\n this.tokens = rateDelta;\n this.lastRefill = now();\n }\n\n /** Wait until a token is available, then consume one. */\n async acquire(): Promise<void> {\n for (;;) {\n this.refill();\n if (this.tokens > 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve);\n });\n }\n }\n\n /** Wake waiters after the clock advances (for testing). */\n notify(): void {\n this.refill();\n }\n\n private refill(): void {\n const t = this.now();\n const elapsed = t - this.lastRefill;\n if (elapsed >= this.rateLimit) {\n const periods = Math.floor(elapsed / this.rateLimit);\n this.tokens = Math.min(this.rateDelta, this.tokens + periods * this.rateDelta);\n this.lastRefill += periods * this.rateLimit;\n while (this.tokens > 0 && this.waiters.length > 0) {\n this.tokens -= 1;\n const next = this.waiters.shift();\n next?.();\n }\n }\n }\n}\n\n/**\n * SMTP connection pool with optional rate limiting.\n */\nexport class SMTPPool implements Transport {\n private readonly config: SMTPConfig & PoolConfig;\n private readonly maxConnections: number;\n private readonly maxMessages: number;\n private readonly createAdapterFn: () => Promise<SocketAdapter>;\n private readonly rateLimiter: RateLimiter | null;\n private readonly connections: PooledConnection[] = [];\n private readonly queue: QueueEntry[] = [];\n private draining = false;\n private closed = false;\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.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 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<boolean> {\n const conn = await this.spawnConnection();\n try {\n return true;\n } finally {\n await conn.close();\n this.removeConnection(conn);\n }\n }\n\n /** Drains the queue and closes all connections. */\n async close(): Promise<void> {\n this.closed = true;\n this.draining = true;\n await this.drainQueue();\n await Promise.all(this.connections.map((c) => c.close()));\n this.connections.length = 0;\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 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 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 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 private async drainQueue(): Promise<void> {\n while (this.queue.length > 0 || this.connections.some((c) => !c.idle)) {\n await this.processQueue();\n if (this.queue.length > 0) {\n await new Promise((r) => setTimeout(r, 10));\n }\n }\n }\n}\n\n/** @internal Exposed for deterministic rate limiter tests. */\nexport { RateLimiter };\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "90F2338A3E37CF8464756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractEmails,
|
|
3
|
+
parseAddresses,
|
|
4
|
+
resolveAttachments
|
|
5
|
+
} from "../../chunk-hdqpvsm8.js";
|
|
6
|
+
import {
|
|
7
|
+
encodeBase64
|
|
8
|
+
} from "../../chunk-794hc3m4.js";
|
|
9
|
+
import"../../chunk-v0bahtg2.js";
|
|
10
|
+
|
|
11
|
+
// src/transports/brevo.ts
|
|
12
|
+
class BrevoError extends Error {
|
|
13
|
+
statusCode;
|
|
14
|
+
code;
|
|
15
|
+
constructor(message, statusCode, code) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.statusCode = statusCode;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.name = "BrevoError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function toAddressObjects(input) {
|
|
23
|
+
return parseAddresses(input).map((addr) => ({
|
|
24
|
+
email: addr.address,
|
|
25
|
+
...addr.name ? { name: addr.name } : {}
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class BrevoTransport {
|
|
30
|
+
apiKey;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.apiKey = config.apiKey;
|
|
33
|
+
}
|
|
34
|
+
async send(options) {
|
|
35
|
+
const attachments = await resolveAttachments(options.attachments);
|
|
36
|
+
const from = parseAddresses(options.from)[0];
|
|
37
|
+
const body = {
|
|
38
|
+
sender: from ? { email: from.address, ...from.name ? { name: from.name } : {} } : { email: "" },
|
|
39
|
+
to: toAddressObjects(options.to),
|
|
40
|
+
subject: options.subject,
|
|
41
|
+
...options.cc ? { cc: toAddressObjects(options.cc) } : {},
|
|
42
|
+
...options.bcc ? { bcc: toAddressObjects(options.bcc) } : {},
|
|
43
|
+
...options.replyTo ? {
|
|
44
|
+
replyTo: (() => {
|
|
45
|
+
const reply = parseAddresses(options.replyTo)[0];
|
|
46
|
+
return reply ? { email: reply.address, ...reply.name ? { name: reply.name } : {} } : undefined;
|
|
47
|
+
})()
|
|
48
|
+
} : {},
|
|
49
|
+
...options.html ? { htmlContent: options.html } : {},
|
|
50
|
+
...options.text ? { textContent: options.text } : {},
|
|
51
|
+
...attachments.length > 0 ? {
|
|
52
|
+
attachment: attachments.map((att) => ({
|
|
53
|
+
name: att.filename,
|
|
54
|
+
content: att.content instanceof Uint8Array ? encodeBase64(att.content).replace(/\r\n/g, "") : att.content
|
|
55
|
+
}))
|
|
56
|
+
} : {}
|
|
57
|
+
};
|
|
58
|
+
const response = await fetch("https://api.brevo.com/v3/smtp/email", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: {
|
|
61
|
+
"api-key": this.apiKey,
|
|
62
|
+
"Content-Type": "application/json"
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body)
|
|
65
|
+
});
|
|
66
|
+
const payload = await response.json();
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new BrevoError(payload.message ?? "Brevo API error", response.status, payload.code ?? "");
|
|
69
|
+
}
|
|
70
|
+
const toEmails = extractEmails(options.to);
|
|
71
|
+
return {
|
|
72
|
+
messageId: payload.messageId ?? "",
|
|
73
|
+
accepted: toEmails,
|
|
74
|
+
rejected: [],
|
|
75
|
+
response: payload.messageId ?? "sent",
|
|
76
|
+
envelope: {
|
|
77
|
+
from: from?.address ?? "",
|
|
78
|
+
to: toEmails
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
BrevoTransport,
|
|
85
|
+
BrevoError
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//# debugId=FB124904BBBBECEB64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transports/brevo.ts"],
|
|
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 { BrevoConfig, MailOptions, SendResult, Transport } 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 Error {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly code: string,\n ) {\n super(message);\n this.name = \"BrevoError\";\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 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"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;AAoBO,MAAM,mBAAmB,MAAM;AAAA,EAIlB;AAAA,EACA;AAAA,EAHlB,WAAW,CACT,SACgB,YACA,MAChB;AAAA,IACA,MAAM,OAAO;AAAA,IAHG;AAAA,IACA;AAAA,IAGhB,KAAK,OAAO;AAAA;AAEhB;AAEA,SAAS,gBAAgB,CAAC,OAAmE;AAAA,EAC3F,OAAO,eAAe,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,IAC1C,OAAO,KAAK;AAAA,OACR,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,EACzC,EAAE;AAAA;AAAA;AAMG,MAAM,eAAoC;AAAA,EAC9B;AAAA,EAGjB,WAAW,CAAC,QAAqB;AAAA,IAC/B,KAAK,SAAS,OAAO;AAAA;AAAA,OAIjB,KAAI,CAAC,SAA2C;AAAA,IACpD,MAAM,cAAc,MAAM,mBAAmB,QAAQ,WAAW;AAAA,IAChE,MAAM,OAAO,eAAe,QAAQ,IAAI,EAAE;AAAA,IAE1C,MAAM,OAAgC;AAAA,MACpC,QAAQ,OACJ,EAAE,OAAO,KAAK,YAAa,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC,EAAG,IACjE,EAAE,OAAO,GAAG;AAAA,MAChB,IAAI,iBAAiB,QAAQ,EAAE;AAAA,MAC/B,SAAS,QAAQ;AAAA,SACb,QAAQ,KAAK,EAAE,IAAI,iBAAiB,QAAQ,EAAE,EAAE,IAAI,CAAC;AAAA,SACrD,QAAQ,MAAM,EAAE,KAAK,iBAAiB,QAAQ,GAAG,EAAE,IAAI,CAAC;AAAA,SACxD,QAAQ,UACR;AAAA,QACE,UAAU,MAAM;AAAA,UACd,MAAM,QAAQ,eAAe,QAAQ,OAA4B,EAAE;AAAA,UACnE,OAAO,QACH,EAAE,OAAO,MAAM,YAAa,MAAM,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC,EAAG,IACpE;AAAA,WACH;AAAA,MACL,IACA,CAAC;AAAA,SACD,QAAQ,OAAO,EAAE,aAAa,QAAQ,KAAK,IAAI,CAAC;AAAA,SAChD,QAAQ,OAAO,EAAE,aAAa,QAAQ,KAAK,IAAI,CAAC;AAAA,SAChD,YAAY,SAAS,IACrB;AAAA,QACE,YAAY,YAAY,IAAI,CAAC,SAAS;AAAA,UACpC,MAAM,IAAI;AAAA,UACV,SACE,IAAI,mBAAmB,aACnB,aAAa,IAAI,OAAO,EAAE,QAAQ,SAAS,EAAE,IAC7C,IAAI;AAAA,QACZ,EAAE;AAAA,MACJ,IACA,CAAC;AAAA,IACP;AAAA,IAEA,MAAM,WAAW,MAAM,MAAM,uCAAuC;AAAA,MAClE,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,IAED,MAAM,UAAW,MAAM,SAAS,KAAK;AAAA,IAMrC,IAAI,CAAC,SAAS,IAAI;AAAA,MAChB,MAAM,IAAI,WACR,QAAQ,WAAW,mBACnB,SAAS,QACT,QAAQ,QAAQ,EAClB;AAAA,IACF;AAAA,IAEA,MAAM,WAAW,cAAc,QAAQ,EAAE;AAAA,IACzC,OAAO;AAAA,MACL,WAAW,QAAQ,aAAa;AAAA,MAChC,UAAU;AAAA,MACV,UAAU,CAAC;AAAA,MACX,UAAU,QAAQ,aAAa;AAAA,MAC/B,UAAU;AAAA,QACR,MAAM,MAAM,WAAW;AAAA,QACvB,IAAI;AAAA,MACN;AAAA,IACF;AAAA;AAEJ;",
|
|
8
|
+
"debugId": "FB124904BBBBECEB64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractEmails,
|
|
3
|
+
parseAddresses,
|
|
4
|
+
resolveAttachments,
|
|
5
|
+
toMIMEHeader
|
|
6
|
+
} from "../../chunk-hdqpvsm8.js";
|
|
7
|
+
import {
|
|
8
|
+
encodeBase64
|
|
9
|
+
} from "../../chunk-794hc3m4.js";
|
|
10
|
+
import"../../chunk-v0bahtg2.js";
|
|
11
|
+
|
|
12
|
+
// src/transports/mailgun.ts
|
|
13
|
+
class MailgunError extends Error {
|
|
14
|
+
statusCode;
|
|
15
|
+
apiError;
|
|
16
|
+
constructor(message, statusCode, apiError) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.statusCode = statusCode;
|
|
19
|
+
this.apiError = apiError;
|
|
20
|
+
this.name = "MailgunError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class MailgunTransport {
|
|
25
|
+
apiKey;
|
|
26
|
+
domain;
|
|
27
|
+
baseUrl;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.apiKey = config.apiKey;
|
|
30
|
+
this.domain = config.domain;
|
|
31
|
+
this.baseUrl = config.region === "eu" ? "https://api.eu.mailgun.net" : "https://api.mailgun.net";
|
|
32
|
+
}
|
|
33
|
+
async send(options) {
|
|
34
|
+
const attachments = await resolveAttachments(options.attachments);
|
|
35
|
+
const from = parseAddresses(options.from)[0];
|
|
36
|
+
const form = new FormData;
|
|
37
|
+
form.append("from", from ? toMIMEHeader(from) : "");
|
|
38
|
+
form.append("to", parseAddresses(options.to).map(toMIMEHeader).join(", "));
|
|
39
|
+
if (options.cc) {
|
|
40
|
+
form.append("cc", parseAddresses(options.cc).map(toMIMEHeader).join(", "));
|
|
41
|
+
}
|
|
42
|
+
if (options.bcc) {
|
|
43
|
+
form.append("bcc", parseAddresses(options.bcc).map(toMIMEHeader).join(", "));
|
|
44
|
+
}
|
|
45
|
+
form.append("subject", options.subject);
|
|
46
|
+
if (options.text) {
|
|
47
|
+
form.append("text", options.text);
|
|
48
|
+
}
|
|
49
|
+
if (options.html) {
|
|
50
|
+
form.append("html", options.html);
|
|
51
|
+
}
|
|
52
|
+
if (options.replyTo) {
|
|
53
|
+
const replyTo = parseAddresses(options.replyTo)[0];
|
|
54
|
+
if (replyTo) {
|
|
55
|
+
form.append("h:Reply-To", toMIMEHeader(replyTo));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const attachment of attachments) {
|
|
59
|
+
const content = attachment.content instanceof Uint8Array ? attachment.content : new TextEncoder().encode(String(attachment.content ?? ""));
|
|
60
|
+
const blob = new Blob([new Uint8Array(content)], {
|
|
61
|
+
type: attachment.contentType ?? "application/octet-stream"
|
|
62
|
+
});
|
|
63
|
+
form.append("attachment", blob, attachment.filename);
|
|
64
|
+
}
|
|
65
|
+
const auth = encodeBase64(`api:${this.apiKey}`).replace(/\r\n/g, "");
|
|
66
|
+
const response = await fetch(`${this.baseUrl}/v3/${this.domain}/messages`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Basic ${auth}`
|
|
70
|
+
},
|
|
71
|
+
body: form
|
|
72
|
+
});
|
|
73
|
+
const payload = await response.json();
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new MailgunError(payload.message ?? "Mailgun API error", response.status, payload);
|
|
76
|
+
}
|
|
77
|
+
const toEmails = extractEmails(options.to);
|
|
78
|
+
return {
|
|
79
|
+
messageId: payload.id ?? "",
|
|
80
|
+
accepted: toEmails,
|
|
81
|
+
rejected: [],
|
|
82
|
+
response: payload.message ?? "queued",
|
|
83
|
+
envelope: {
|
|
84
|
+
from: from?.address ?? "",
|
|
85
|
+
to: toEmails
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
MailgunTransport,
|
|
92
|
+
MailgunError
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//# debugId=0BE9B4ECD763D6B564756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transports/mailgun.ts"],
|
|
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 { MailgunConfig, MailOptions, SendResult, Transport } 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 Error {\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);\n this.name = \"MailgunError\";\n }\n}\n\n/**\n * Mailgun HTTP API transport (multipart/form-data).\n */\nexport class MailgunTransport implements Transport {\n private readonly apiKey: string;\n private readonly domain: string;\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"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "0BE9B4ECD763D6B564756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMIME
|
|
3
|
+
} from "../../chunk-0cdwxgck.js";
|
|
4
|
+
import {
|
|
5
|
+
extractEmails,
|
|
6
|
+
parseAddresses,
|
|
7
|
+
resolveAttachments,
|
|
8
|
+
toMIMEHeader
|
|
9
|
+
} from "../../chunk-hdqpvsm8.js";
|
|
10
|
+
import {
|
|
11
|
+
encodeBase64
|
|
12
|
+
} from "../../chunk-794hc3m4.js";
|
|
13
|
+
import"../../chunk-v0bahtg2.js";
|
|
14
|
+
|
|
15
|
+
// src/core/sigv4.ts
|
|
16
|
+
var encoder = new TextEncoder;
|
|
17
|
+
async function sha256Hex(data) {
|
|
18
|
+
const hash = await crypto.subtle.digest("SHA-256", encoder.encode(data));
|
|
19
|
+
return bytesToHex(new Uint8Array(hash));
|
|
20
|
+
}
|
|
21
|
+
async function hmacSHA256(key, data) {
|
|
22
|
+
const keyBytes = typeof key === "string" ? encoder.encode(key) : new Uint8Array(key);
|
|
23
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
24
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(data));
|
|
25
|
+
return new Uint8Array(signature);
|
|
26
|
+
}
|
|
27
|
+
async function signRequest(request) {
|
|
28
|
+
const { method, url, body, credentials } = request;
|
|
29
|
+
const parsed = new URL(url);
|
|
30
|
+
const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, "").slice(0, 16)}Z`;
|
|
31
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
32
|
+
const headers = {
|
|
33
|
+
...Object.fromEntries(Object.entries(request.headers).map(([key, value]) => [key.toLowerCase(), value.trim()])),
|
|
34
|
+
host: parsed.host,
|
|
35
|
+
"x-amz-date": amzDate
|
|
36
|
+
};
|
|
37
|
+
if (credentials.sessionToken) {
|
|
38
|
+
headers["x-amz-security-token"] = credentials.sessionToken;
|
|
39
|
+
}
|
|
40
|
+
const signedHeaderNames = Object.keys(headers).map((name) => name.toLowerCase()).sort();
|
|
41
|
+
const signedHeaders = signedHeaderNames.join(";");
|
|
42
|
+
const canonicalHeaders = `${signedHeaderNames.map((name) => `${name}:${headers[name]}`).join(`
|
|
43
|
+
`)}
|
|
44
|
+
`;
|
|
45
|
+
const canonicalQuery = normalizeQuery(parsed.searchParams);
|
|
46
|
+
const payloadHash = await sha256Hex(body);
|
|
47
|
+
const canonicalRequest = [
|
|
48
|
+
method.toUpperCase(),
|
|
49
|
+
canonicalUri(parsed.pathname),
|
|
50
|
+
canonicalQuery,
|
|
51
|
+
canonicalHeaders,
|
|
52
|
+
signedHeaders,
|
|
53
|
+
payloadHash
|
|
54
|
+
].join(`
|
|
55
|
+
`);
|
|
56
|
+
const credentialScope = `${dateStamp}/${credentials.region}/${credentials.service}/aws4_request`;
|
|
57
|
+
const stringToSign = [
|
|
58
|
+
"AWS4-HMAC-SHA256",
|
|
59
|
+
amzDate,
|
|
60
|
+
credentialScope,
|
|
61
|
+
await sha256Hex(canonicalRequest)
|
|
62
|
+
].join(`
|
|
63
|
+
`);
|
|
64
|
+
const signingKey = await deriveSigningKey(credentials.secretAccessKey, dateStamp, credentials.region, credentials.service);
|
|
65
|
+
const signature = bytesToHex(await hmacSHA256(signingKey, stringToSign));
|
|
66
|
+
const authorization = [
|
|
67
|
+
`AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${credentialScope}`,
|
|
68
|
+
`SignedHeaders=${signedHeaders}`,
|
|
69
|
+
`Signature=${signature}`
|
|
70
|
+
].join(", ");
|
|
71
|
+
return {
|
|
72
|
+
headers: {
|
|
73
|
+
...headers,
|
|
74
|
+
Authorization: authorization
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function deriveSigningKey(secretAccessKey, dateStamp, region, service) {
|
|
79
|
+
const kDate = await hmacSHA256(`AWS4${secretAccessKey}`, dateStamp);
|
|
80
|
+
const kRegion = await hmacSHA256(kDate, region);
|
|
81
|
+
const kService = await hmacSHA256(kRegion, service);
|
|
82
|
+
return hmacSHA256(kService, "aws4_request");
|
|
83
|
+
}
|
|
84
|
+
function canonicalUri(pathname) {
|
|
85
|
+
if (!pathname || pathname === "/") {
|
|
86
|
+
return "/";
|
|
87
|
+
}
|
|
88
|
+
return pathname.split("/").map((segment) => encodeURIComponent(decodeURIComponent(segment))).join("/");
|
|
89
|
+
}
|
|
90
|
+
function normalizeQuery(searchParams) {
|
|
91
|
+
const pairs = [];
|
|
92
|
+
for (const [key, value] of searchParams.entries()) {
|
|
93
|
+
pairs.push(`${encodeRfc3986(key)}=${encodeRfc3986(value)}`);
|
|
94
|
+
}
|
|
95
|
+
pairs.sort();
|
|
96
|
+
return pairs.join("&");
|
|
97
|
+
}
|
|
98
|
+
function encodeRfc3986(value) {
|
|
99
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
100
|
+
}
|
|
101
|
+
function bytesToHex(bytes) {
|
|
102
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/transports/ses.ts
|
|
106
|
+
class SESError extends Error {
|
|
107
|
+
statusCode;
|
|
108
|
+
code;
|
|
109
|
+
requestId;
|
|
110
|
+
constructor(message, statusCode, code, requestId) {
|
|
111
|
+
super(message);
|
|
112
|
+
this.statusCode = statusCode;
|
|
113
|
+
this.code = code;
|
|
114
|
+
this.requestId = requestId;
|
|
115
|
+
this.name = "SESError";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class SESTransport {
|
|
120
|
+
accessKeyId;
|
|
121
|
+
secretAccessKey;
|
|
122
|
+
region;
|
|
123
|
+
sessionToken;
|
|
124
|
+
constructor(config) {
|
|
125
|
+
this.accessKeyId = config.accessKeyId;
|
|
126
|
+
this.secretAccessKey = config.secretAccessKey;
|
|
127
|
+
this.region = config.region ?? "us-east-1";
|
|
128
|
+
this.sessionToken = config.sessionToken;
|
|
129
|
+
}
|
|
130
|
+
async send(options) {
|
|
131
|
+
const attachments = await resolveAttachments(options.attachments);
|
|
132
|
+
const resolvedOptions = { ...options, attachments };
|
|
133
|
+
const from = parseAddresses(options.from)[0];
|
|
134
|
+
const fromEmail = from ? toMIMEHeader(from) : "";
|
|
135
|
+
const toEmails = extractEmails(options.to);
|
|
136
|
+
const ccEmails = options.cc ? extractEmails(options.cc) : [];
|
|
137
|
+
const bccEmails = options.bcc ? extractEmails(options.bcc) : [];
|
|
138
|
+
const destination = {
|
|
139
|
+
ToAddresses: toEmails,
|
|
140
|
+
CcAddresses: ccEmails,
|
|
141
|
+
BccAddresses: bccEmails
|
|
142
|
+
};
|
|
143
|
+
let requestBody;
|
|
144
|
+
if (attachments.length > 0) {
|
|
145
|
+
const mime = await buildMIME(resolvedOptions);
|
|
146
|
+
requestBody = {
|
|
147
|
+
FromEmailAddress: fromEmail,
|
|
148
|
+
Destination: destination,
|
|
149
|
+
Content: {
|
|
150
|
+
Raw: {
|
|
151
|
+
Data: encodeBase64(mime.raw).replace(/\r\n/g, "")
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
} else {
|
|
156
|
+
requestBody = {
|
|
157
|
+
FromEmailAddress: fromEmail,
|
|
158
|
+
Destination: destination,
|
|
159
|
+
Content: {
|
|
160
|
+
Simple: {
|
|
161
|
+
Subject: { Data: options.subject, Charset: "UTF-8" },
|
|
162
|
+
Body: {
|
|
163
|
+
...options.text ? { Text: { Data: options.text, Charset: "UTF-8" } } : {},
|
|
164
|
+
...options.html ? { Html: { Data: options.html, Charset: "UTF-8" } } : {}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const body = JSON.stringify(requestBody);
|
|
171
|
+
const url = `https://email.${this.region}.amazonaws.com/v2/email/outbound-emails`;
|
|
172
|
+
const signed = await signRequest({
|
|
173
|
+
method: "POST",
|
|
174
|
+
url,
|
|
175
|
+
headers: {
|
|
176
|
+
"content-type": "application/json"
|
|
177
|
+
},
|
|
178
|
+
body,
|
|
179
|
+
credentials: {
|
|
180
|
+
accessKeyId: this.accessKeyId,
|
|
181
|
+
secretAccessKey: this.secretAccessKey,
|
|
182
|
+
region: this.region,
|
|
183
|
+
service: "ses",
|
|
184
|
+
...this.sessionToken ? { sessionToken: this.sessionToken } : {}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const response = await fetch(url, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: signed.headers,
|
|
190
|
+
body
|
|
191
|
+
});
|
|
192
|
+
const payload = await response.json();
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
throw new SESError(payload.message ?? "SES API error", response.status, payload.Code ?? "", response.headers.get("x-amzn-requestid") ?? "");
|
|
195
|
+
}
|
|
196
|
+
const messageId = payload.MessageId ?? "";
|
|
197
|
+
return {
|
|
198
|
+
messageId,
|
|
199
|
+
accepted: [...toEmails, ...ccEmails, ...bccEmails],
|
|
200
|
+
rejected: [],
|
|
201
|
+
response: `MessageId: ${messageId}`,
|
|
202
|
+
envelope: {
|
|
203
|
+
from: from?.address ?? "",
|
|
204
|
+
to: toEmails
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export {
|
|
210
|
+
SESTransport,
|
|
211
|
+
SESError
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
//# debugId=FC953DAF4FBD627764756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/sigv4.ts", "../src/transports/ses.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * AWS Signature Version 4 signing using Web Crypto (HMAC-SHA256).\n * Works on Node.js, Bun, Deno, and Cloudflare Workers.\n * No external dependencies.\n *\n * @example\n * ```ts\n * import { signRequest } from \"sently/core/sigv4\";\n * const signed = await signRequest({\n * method: \"POST\",\n * url: \"https://email.us-east-1.amazonaws.com/v2/email/outbound-emails\",\n * headers: { \"content-type\": \"application/json\" },\n * body: '{\"...\"}',\n * credentials: { accessKeyId, secretAccessKey, region: \"us-east-1\", service: \"ses\" },\n * });\n * ```\n */\n\n/** AWS credentials and signing scope for SigV4. */\nexport interface SigV4Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n region: string;\n service: string;\n sessionToken?: string;\n}\n\n/** HTTP request to sign with AWS Signature Version 4. */\nexport interface SigV4Request {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n credentials: SigV4Credentials;\n /** Override datetime for testing. Full 'YYYYMMDDTHHMMSSZ' when provided. */\n _date?: string;\n}\n\n/** Signed request headers including Authorization. */\nexport interface SigV4Result {\n /** All headers including Authorization, x-amz-date, and x-amz-security-token */\n headers: Record<string, string>;\n}\n\nconst encoder = new TextEncoder();\n\n/**\n * Compute SHA-256 hash of a string using Web Crypto.\n * Returns lowercase hex string.\n * @internal\n */\nexport async function sha256Hex(data: string): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", encoder.encode(data));\n return bytesToHex(new Uint8Array(hash));\n}\n\n/**\n * Compute HMAC-SHA256 using Web Crypto.\n * @internal\n */\nexport async function hmacSHA256(key: Uint8Array | string, data: string): Promise<Uint8Array> {\n const keyBytes = typeof key === \"string\" ? encoder.encode(key) : new Uint8Array(key);\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyBytes,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, encoder.encode(data));\n return new Uint8Array(signature);\n}\n\n/**\n * Sign an HTTP request with AWS Signature Version 4.\n * Returns the complete set of headers to include in the request.\n */\nexport async function signRequest(request: SigV4Request): Promise<SigV4Result> {\n const { method, url, body, credentials } = request;\n const parsed = new URL(url);\n const amzDate = request._date ?? `${new Date().toISOString().replace(/[-:]/g, \"\").slice(0, 16)}Z`;\n const dateStamp = amzDate.slice(0, 8);\n\n const headers: Record<string, string> = {\n ...Object.fromEntries(\n Object.entries(request.headers).map(([key, value]) => [key.toLowerCase(), value.trim()]),\n ),\n host: parsed.host,\n \"x-amz-date\": amzDate,\n };\n\n if (credentials.sessionToken) {\n headers[\"x-amz-security-token\"] = credentials.sessionToken;\n }\n\n const signedHeaderNames = Object.keys(headers)\n .map((name) => name.toLowerCase())\n .sort();\n const signedHeaders = signedHeaderNames.join(\";\");\n const canonicalHeaders = `${signedHeaderNames.map((name) => `${name}:${headers[name]}`).join(\"\\n\")}\\n`;\n const canonicalQuery = normalizeQuery(parsed.searchParams);\n const payloadHash = await sha256Hex(body);\n const canonicalRequest = [\n method.toUpperCase(),\n canonicalUri(parsed.pathname),\n canonicalQuery,\n canonicalHeaders,\n signedHeaders,\n payloadHash,\n ].join(\"\\n\");\n\n const credentialScope = `${dateStamp}/${credentials.region}/${credentials.service}/aws4_request`;\n const stringToSign = [\n \"AWS4-HMAC-SHA256\",\n amzDate,\n credentialScope,\n await sha256Hex(canonicalRequest),\n ].join(\"\\n\");\n\n const signingKey = await deriveSigningKey(\n credentials.secretAccessKey,\n dateStamp,\n credentials.region,\n credentials.service,\n );\n const signature = bytesToHex(await hmacSHA256(signingKey, stringToSign));\n const authorization = [\n `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${credentialScope}`,\n `SignedHeaders=${signedHeaders}`,\n `Signature=${signature}`,\n ].join(\", \");\n\n return {\n headers: {\n ...headers,\n Authorization: authorization,\n },\n };\n}\n\nasync function deriveSigningKey(\n secretAccessKey: string,\n dateStamp: string,\n region: string,\n service: string,\n): Promise<Uint8Array> {\n const kDate = await hmacSHA256(`AWS4${secretAccessKey}`, dateStamp);\n const kRegion = await hmacSHA256(kDate, region);\n const kService = await hmacSHA256(kRegion, service);\n return hmacSHA256(kService, \"aws4_request\");\n}\n\nfunction canonicalUri(pathname: string): string {\n if (!pathname || pathname === \"/\") {\n return \"/\";\n }\n return pathname\n .split(\"/\")\n .map((segment) => encodeURIComponent(decodeURIComponent(segment)))\n .join(\"/\");\n}\n\nfunction normalizeQuery(searchParams: URLSearchParams): string {\n const pairs: string[] = [];\n for (const [key, value] of searchParams.entries()) {\n pairs.push(`${encodeRfc3986(key)}=${encodeRfc3986(value)}`);\n }\n pairs.sort();\n return pairs.join(\"&\");\n}\n\nfunction encodeRfc3986(value: string): string {\n return encodeURIComponent(value).replace(\n /[!'()*]/g,\n (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n",
|
|
6
|
+
"/**\n * @module\n * AWS SES v2 HTTP transport for sently.\n * Signs requests with AWS Signature Version 4 using Web Crypto.\n * Works on Node.js, Bun, Deno, and Cloudflare Workers.\n *\n * @example\n * ```ts\n * import { SESTransport } from \"sently/transports/ses\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SESTransport({\n * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,\n * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,\n * region: \"us-east-1\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { buildMIME } from \"../core/mime.js\";\nimport { signRequest } from \"../core/sigv4.js\";\nimport type { MailOptions, SESConfig, SendResult, Transport } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the AWS SES API returns a non-success response. */\nexport class SESError extends Error {\n /** Creates an AWS SES API error with status code, error code, and request ID. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly code: string,\n public readonly requestId: string,\n ) {\n super(message);\n this.name = \"SESError\";\n }\n}\n\n/**\n * AWS SES v2 HTTP API transport.\n */\nexport class SESTransport implements Transport {\n private readonly accessKeyId: string;\n private readonly secretAccessKey: string;\n private readonly region: string;\n private readonly sessionToken: string | undefined;\n\n /** Creates an SES transport with AWS credentials. */\n constructor(config: SESConfig) {\n this.accessKeyId = config.accessKeyId;\n this.secretAccessKey = config.secretAccessKey;\n this.region = config.region ?? \"us-east-1\";\n this.sessionToken = config.sessionToken;\n }\n\n /** Sends an email via the AWS SES v2 HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const resolvedOptions = { ...options, attachments };\n const from = parseAddresses(options.from)[0];\n const fromEmail = from ? toMIMEHeader(from) : \"\";\n const toEmails = extractEmails(options.to);\n const ccEmails = options.cc ? extractEmails(options.cc) : [];\n const bccEmails = options.bcc ? extractEmails(options.bcc) : [];\n\n const destination = {\n ToAddresses: toEmails,\n CcAddresses: ccEmails,\n BccAddresses: bccEmails,\n };\n\n let requestBody: Record<string, unknown>;\n\n if (attachments.length > 0) {\n const mime = await buildMIME(resolvedOptions);\n requestBody = {\n FromEmailAddress: fromEmail,\n Destination: destination,\n Content: {\n Raw: {\n Data: encodeBase64(mime.raw).replace(/\\r\\n/g, \"\"),\n },\n },\n };\n } else {\n requestBody = {\n FromEmailAddress: fromEmail,\n Destination: destination,\n Content: {\n Simple: {\n Subject: { Data: options.subject, Charset: \"UTF-8\" },\n Body: {\n ...(options.text ? { Text: { Data: options.text, Charset: \"UTF-8\" } } : {}),\n ...(options.html ? { Html: { Data: options.html, Charset: \"UTF-8\" } } : {}),\n },\n },\n },\n };\n }\n\n const body = JSON.stringify(requestBody);\n const url = `https://email.${this.region}.amazonaws.com/v2/email/outbound-emails`;\n const signed = await signRequest({\n method: \"POST\",\n url,\n headers: {\n \"content-type\": \"application/json\",\n },\n body,\n credentials: {\n accessKeyId: this.accessKeyId,\n secretAccessKey: this.secretAccessKey,\n region: this.region,\n service: \"ses\",\n ...(this.sessionToken ? { sessionToken: this.sessionToken } : {}),\n },\n });\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: signed.headers,\n 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 SESError(\n payload.message ?? \"SES API error\",\n response.status,\n payload.Code ?? \"\",\n response.headers.get(\"x-amzn-requestid\") ?? \"\",\n );\n }\n\n const messageId = payload.MessageId ?? \"\";\n return {\n messageId,\n accepted: [...toEmails, ...ccEmails, ...bccEmails],\n rejected: [],\n response: `MessageId: ${messageId}`,\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "FC953DAF4FBD627764756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
openSMTPSession,
|
|
7
7
|
readSMTPResponse,
|
|
8
8
|
resolveSMTPConfig
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-vmxqrew7.js";
|
|
10
|
+
import"../../chunk-0cdwxgck.js";
|
|
10
11
|
import"../../chunk-vm70w4e3.js";
|
|
11
12
|
import"../../chunk-hdqpvsm8.js";
|
|
12
13
|
import"../../chunk-794hc3m4.js";
|
|
@@ -21,4 +22,4 @@ export {
|
|
|
21
22
|
SMTPTransport
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
//# debugId=
|
|
25
|
+
//# debugId=CECFB2F9F29B813F64756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sently",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Runtime-agnostic email library for Node.js, Bun, Deno, and Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -93,6 +93,18 @@
|
|
|
93
93
|
"import": "./dist/transports/postmark.js",
|
|
94
94
|
"types": "./dist/transports/postmark.d.ts"
|
|
95
95
|
},
|
|
96
|
+
"./transports/mailgun": {
|
|
97
|
+
"import": "./dist/transports/mailgun.js",
|
|
98
|
+
"types": "./dist/transports/mailgun.d.ts"
|
|
99
|
+
},
|
|
100
|
+
"./transports/ses": {
|
|
101
|
+
"import": "./dist/transports/ses.js",
|
|
102
|
+
"types": "./dist/transports/ses.d.ts"
|
|
103
|
+
},
|
|
104
|
+
"./transports/brevo": {
|
|
105
|
+
"import": "./dist/transports/brevo.js",
|
|
106
|
+
"types": "./dist/transports/brevo.d.ts"
|
|
107
|
+
},
|
|
96
108
|
"./auth/oauth2": {
|
|
97
109
|
"import": "./dist/auth/oauth2.js",
|
|
98
110
|
"types": "./dist/auth/oauth2.d.ts"
|