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/transports/ses.js
CHANGED
|
@@ -1,253 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildMIME
|
|
3
|
-
} from "../chunk-x3szga4k.js";
|
|
4
|
-
import {
|
|
5
|
-
extractEmails,
|
|
6
|
-
parseAddresses,
|
|
7
|
-
resolveAttachments,
|
|
8
|
-
toMIMEHeader
|
|
9
|
-
} from "../chunk-f4c9ttmr.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, 15)}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(`
|
|
1
|
+
import{k as A}from"../chunk-nhec1319.js";import{l as k}from"../chunk-cvmk9h1r.js";import{n as x,o as R,p as P}from"../chunk-kdqxhybf.js";import{G as T}from"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";var M=new TextEncoder;async function z(C){let F=await crypto.subtle.digest("SHA-256",M.encode(C));return K(new Uint8Array(F))}async function W(C,F){let J=typeof C==="string"?M.encode(C):new Uint8Array(C),L=await crypto.subtle.importKey("raw",J,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),G=await crypto.subtle.sign("HMAC",L,M.encode(F));return new Uint8Array(G)}async function j(C){let{method:F,url:J,body:L,credentials:G}=C,X=new URL(J),Y=C._date??`${new Date().toISOString().replace(/[-:]/g,"").slice(0,15)}Z`,U=Y.slice(0,8),Z={...Object.fromEntries(Object.entries(C.headers).map(([O,E])=>[O.toLowerCase(),E.trim()])),host:X.host,"x-amz-date":Y};if(G.sessionToken)Z["x-amz-security-token"]=G.sessionToken;let $=Object.keys(Z).map((O)=>O.toLowerCase()).sort(),N=$.join(";"),_=`${$.map((O)=>`${O}:${Z[O]}`).join(`
|
|
43
2
|
`)}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
dkim;
|
|
125
|
-
constructor(config) {
|
|
126
|
-
this.accessKeyId = config.accessKeyId;
|
|
127
|
-
this.secretAccessKey = config.secretAccessKey;
|
|
128
|
-
this.region = config.region ?? "us-east-1";
|
|
129
|
-
this.sessionToken = config.sessionToken;
|
|
130
|
-
this.dkim = config.dkim;
|
|
131
|
-
}
|
|
132
|
-
async send(options) {
|
|
133
|
-
const attachments = await resolveAttachments(options.attachments);
|
|
134
|
-
const resolvedOptions = { ...options, attachments };
|
|
135
|
-
const from = parseAddresses(options.from)[0];
|
|
136
|
-
const fromEmail = from ? toMIMEHeader(from) : "";
|
|
137
|
-
const toEmails = extractEmails(options.to);
|
|
138
|
-
const ccEmails = options.cc ? extractEmails(options.cc) : [];
|
|
139
|
-
const bccEmails = options.bcc ? extractEmails(options.bcc) : [];
|
|
140
|
-
const destination = {
|
|
141
|
-
ToAddresses: toEmails,
|
|
142
|
-
CcAddresses: ccEmails,
|
|
143
|
-
BccAddresses: bccEmails
|
|
144
|
-
};
|
|
145
|
-
let requestBody;
|
|
146
|
-
if (attachments.length > 0) {
|
|
147
|
-
const mime = await buildMIME(resolvedOptions, this.dkim);
|
|
148
|
-
requestBody = {
|
|
149
|
-
FromEmailAddress: fromEmail,
|
|
150
|
-
Destination: destination,
|
|
151
|
-
Content: {
|
|
152
|
-
Raw: {
|
|
153
|
-
Data: encodeBase64(mime.raw).replace(/\r\n/g, "")
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
} else {
|
|
158
|
-
requestBody = {
|
|
159
|
-
FromEmailAddress: fromEmail,
|
|
160
|
-
Destination: destination,
|
|
161
|
-
Content: {
|
|
162
|
-
Simple: {
|
|
163
|
-
Subject: { Data: options.subject, Charset: "UTF-8" },
|
|
164
|
-
Body: {
|
|
165
|
-
...options.text ? { Text: { Data: options.text, Charset: "UTF-8" } } : {},
|
|
166
|
-
...options.html ? { Html: { Data: options.html, Charset: "UTF-8" } } : {}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const body = JSON.stringify(requestBody);
|
|
173
|
-
const url = `https://email.${this.region}.amazonaws.com/v2/email/outbound-emails`;
|
|
174
|
-
const signed = await signRequest({
|
|
175
|
-
method: "POST",
|
|
176
|
-
url,
|
|
177
|
-
headers: {
|
|
178
|
-
"content-type": "application/json"
|
|
179
|
-
},
|
|
180
|
-
body,
|
|
181
|
-
credentials: {
|
|
182
|
-
accessKeyId: this.accessKeyId,
|
|
183
|
-
secretAccessKey: this.secretAccessKey,
|
|
184
|
-
region: this.region,
|
|
185
|
-
service: "ses",
|
|
186
|
-
...this.sessionToken ? { sessionToken: this.sessionToken } : {}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
const response = await fetch(url, {
|
|
190
|
-
method: "POST",
|
|
191
|
-
headers: signed.headers,
|
|
192
|
-
body
|
|
193
|
-
});
|
|
194
|
-
const payload = await response.json();
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
throw new SESError(payload.message ?? "SES API error", response.status, payload.Code ?? "", response.headers.get("x-amzn-requestid") ?? "");
|
|
197
|
-
}
|
|
198
|
-
const messageId = payload.MessageId ?? "";
|
|
199
|
-
return {
|
|
200
|
-
messageId,
|
|
201
|
-
accepted: [...toEmails, ...ccEmails, ...bccEmails],
|
|
202
|
-
rejected: [],
|
|
203
|
-
response: `MessageId: ${messageId}`,
|
|
204
|
-
envelope: {
|
|
205
|
-
from: from?.address ?? "",
|
|
206
|
-
to: toEmails
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
async verify() {
|
|
211
|
-
try {
|
|
212
|
-
const url = `https://email.${this.region}.amazonaws.com/v2/email/configuration-sets`;
|
|
213
|
-
const signed = await signRequest({
|
|
214
|
-
method: "GET",
|
|
215
|
-
url,
|
|
216
|
-
headers: {},
|
|
217
|
-
body: "",
|
|
218
|
-
credentials: {
|
|
219
|
-
accessKeyId: this.accessKeyId,
|
|
220
|
-
secretAccessKey: this.secretAccessKey,
|
|
221
|
-
region: this.region,
|
|
222
|
-
service: "ses",
|
|
223
|
-
...this.sessionToken ? { sessionToken: this.sessionToken } : {}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
const response = await fetch(url, {
|
|
227
|
-
method: "GET",
|
|
228
|
-
headers: signed.headers
|
|
229
|
-
});
|
|
230
|
-
const payload = await response.json().catch(() => ({}));
|
|
231
|
-
if (!response.ok) {
|
|
232
|
-
return {
|
|
233
|
-
ok: false,
|
|
234
|
-
provider: "ses",
|
|
235
|
-
message: payload.message ?? `HTTP ${response.status}`
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
return { ok: true, provider: "ses", message: "Credentials are valid" };
|
|
239
|
-
} catch (err) {
|
|
240
|
-
return {
|
|
241
|
-
ok: false,
|
|
242
|
-
provider: "ses",
|
|
243
|
-
message: err instanceof Error ? err.message : String(err)
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
export {
|
|
249
|
-
SESTransport,
|
|
250
|
-
SESError
|
|
251
|
-
};
|
|
3
|
+
`,D=h(X.searchParams),w=await z(L),Q=[F.toUpperCase(),S(X.pathname),D,_,N,w].join(`
|
|
4
|
+
`),V=`${U}/${G.region}/${G.service}/aws4_request`,I=["AWS4-HMAC-SHA256",Y,V,await z(Q)].join(`
|
|
5
|
+
`),b=await f(G.secretAccessKey,U,G.region,G.service),q=K(await W(b,I)),H=[`AWS4-HMAC-SHA256 Credential=${G.accessKeyId}/${V}`,`SignedHeaders=${N}`,`Signature=${q}`].join(", ");return{headers:{...Z,Authorization:H}}}async function f(C,F,J,L){let G=await W(`AWS4${C}`,F),X=await W(G,J),Y=await W(X,L);return W(Y,"aws4_request")}function S(C){if(!C||C==="/")return"/";return C.split("/").map((F)=>encodeURIComponent(decodeURIComponent(F))).join("/")}function h(C){let F=[];for(let[J,L]of C.entries())F.push(`${B(J)}=${B(L)}`);return F.sort(),F.join("&")}function B(C){return encodeURIComponent(C).replace(/[!'()*]/g,(F)=>`%${F.charCodeAt(0).toString(16).toUpperCase()}`)}function K(C){return Array.from(C,(F)=>F.toString(16).padStart(2,"0")).join("")}class v extends Error{statusCode;code;requestId;constructor(C,F,J,L){super(C);this.statusCode=F;this.code=J;this.requestId=L;this.name="SESError"}}class u{accessKeyId;secretAccessKey;region;sessionToken;dkim;constructor(C){this.accessKeyId=C.accessKeyId,this.secretAccessKey=C.secretAccessKey,this.region=C.region??"us-east-1",this.sessionToken=C.sessionToken,this.dkim=C.dkim}async send(C){let F=await k(C.attachments),J={...C,attachments:F},L=x(C.from)[0],G=L?R(L):"",X=P(C.to),Y=C.cc?P(C.cc):[],U=C.bcc?P(C.bcc):[],Z={ToAddresses:X,CcAddresses:Y,BccAddresses:U},$;if(F.length>0){let I=await A(J,this.dkim);$={FromEmailAddress:G,Destination:Z,Content:{Raw:{Data:T(I.raw).replace(/\r\n/g,"")}}}}else $={FromEmailAddress:G,Destination:Z,Content:{Simple:{Subject:{Data:C.subject,Charset:"UTF-8"},Body:{...C.text?{Text:{Data:C.text,Charset:"UTF-8"}}:{},...C.html?{Html:{Data:C.html,Charset:"UTF-8"}}:{}}}}};let N=JSON.stringify($),_=`https://email.${this.region}.amazonaws.com/v2/email/outbound-emails`,D=await j({method:"POST",url:_,headers:{"content-type":"application/json"},body:N,credentials:{accessKeyId:this.accessKeyId,secretAccessKey:this.secretAccessKey,region:this.region,service:"ses",...this.sessionToken?{sessionToken:this.sessionToken}:{}}}),w=await fetch(_,{method:"POST",headers:D.headers,body:N}),Q=await w.json();if(!w.ok)throw new v(Q.message??"SES API error",w.status,Q.Code??"",w.headers.get("x-amzn-requestid")??"");let V=Q.MessageId??"";return{messageId:V,accepted:[...X,...Y,...U],rejected:[],response:`MessageId: ${V}`,envelope:{from:L?.address??"",to:X}}}async verify(){try{let C=`https://email.${this.region}.amazonaws.com/v2/email/configuration-sets`,F=await j({method:"GET",url:C,headers:{},body:"",credentials:{accessKeyId:this.accessKeyId,secretAccessKey:this.secretAccessKey,region:this.region,service:"ses",...this.sessionToken?{sessionToken:this.sessionToken}:{}}}),J=await fetch(C,{method:"GET",headers:F.headers}),L=await J.json().catch(()=>({}));if(!J.ok)return{ok:!1,provider:"ses",message:L.message??`HTTP ${J.status}`};return{ok:!0,provider:"ses",message:"Credentials are valid"}}catch(C){return{ok:!1,provider:"ses",message:C instanceof Error?C.message:String(C)}}}}export{u as SESTransport,v as SESError};
|
|
252
6
|
|
|
253
|
-
//# debugId=
|
|
7
|
+
//# debugId=40DB8C49111C4E7964756E2164756E21
|
|
@@ -5,7 +5,7 @@
|
|
|
5
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 /** AWS access key ID. */\n accessKeyId: string;\n /** AWS secret access key. */\n secretAccessKey: string;\n /** AWS region (e.g. `us-east-1`). */\n region: string;\n /** AWS service name (e.g. `ses`, `s3`). */\n service: string;\n /** Optional STS session token for temporary credentials. */\n sessionToken?: string;\n}\n\n/** HTTP request to sign with AWS Signature Version 4. */\nexport interface SigV4Request {\n /** HTTP method (e.g. `POST`). */\n method: string;\n /** Full request URL including path and query. */\n url: string;\n /** Request headers to include in the signature. */\n headers: Record<string, string>;\n /** Request body as a string (empty for GET). */\n body: string;\n /** AWS credentials and signing scope. */\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, 15)}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
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, VerifyResult } 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 /** AWS access key ID for SigV4 signing. */\n private readonly accessKeyId: string;\n /** AWS secret access key for SigV4 signing. */\n private readonly secretAccessKey: string;\n /** AWS region for the SES endpoint. */\n private readonly region: string;\n /** Optional STS session token for temporary credentials. */\n private readonly sessionToken: string | undefined;\n /** Optional DKIM config for raw MIME attachment sends. */\n private readonly dkim: SESConfig[\"dkim\"];\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 this.dkim = config.dkim;\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, this.dkim);\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 /** Verifies AWS credentials by listing SES configuration sets. */\n async verify(): Promise<VerifyResult> {\n try {\n const url = `https://email.${this.region}.amazonaws.com/v2/email/configuration-sets`;\n const signed = await signRequest({\n method: \"GET\",\n url,\n headers: {},\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: \"GET\",\n headers: signed.headers,\n });\n\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"ses\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"ses\", message: \"Credentials are valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"ses\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "40DB8C49111C4E7964756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/transports/smtp.js
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SMTPTransport,
|
|
3
|
-
closeSMTPSession,
|
|
4
|
-
deliverSMTPMessage,
|
|
5
|
-
openSMTPSession,
|
|
6
|
-
readSMTPResponse,
|
|
7
|
-
resolveSMTPConfig
|
|
8
|
-
} from "../chunk-7fqv71z1.js";
|
|
9
|
-
import"../chunk-ym3zzv8b.js";
|
|
10
|
-
import"../chunk-x3szga4k.js";
|
|
11
|
-
import {
|
|
12
|
-
encodeLine
|
|
13
|
-
} from "../chunk-tymfm441.js";
|
|
14
|
-
import"../chunk-f4c9ttmr.js";
|
|
15
|
-
import"../chunk-794hc3m4.js";
|
|
16
|
-
import"../chunk-v0bahtg2.js";
|
|
17
|
-
export {
|
|
18
|
-
resolveSMTPConfig,
|
|
19
|
-
readSMTPResponse,
|
|
20
|
-
openSMTPSession,
|
|
21
|
-
encodeLine,
|
|
22
|
-
deliverSMTPMessage,
|
|
23
|
-
closeSMTPSession,
|
|
24
|
-
SMTPTransport
|
|
25
|
-
};
|
|
1
|
+
import{a as b,b as c,c as d,d as e,e as f,f as g}from"../chunk-nj3wmg69.js";import"../chunk-j5m37k1c.js";import"../chunk-nhec1319.js";import"../chunk-cvmk9h1r.js";import"../chunk-kdqxhybf.js";import{F as a}from"../chunk-m2njnxre.js";import"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";export{c as resolveSMTPConfig,g as readSMTPResponse,d as openSMTPSession,a as encodeLine,e as deliverSMTPMessage,f as closeSMTPSession,b as SMTPTransport};
|
|
26
2
|
|
|
27
|
-
//# debugId=
|
|
3
|
+
//# debugId=6A679298EBA4014164756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EmailEvent } from "./types.js";
|
|
2
|
+
/** Parse a Mailgun webhook payload into normalized {@link EmailEvent} records. */
|
|
3
|
+
export declare function parse(payload: unknown): EmailEvent[];
|
|
4
|
+
/**
|
|
5
|
+
* Verify a Mailgun webhook signature using HMAC-SHA256 (Web Crypto).
|
|
6
|
+
* signingKey is the Mailgun webhook signing key.
|
|
7
|
+
*/
|
|
8
|
+
export declare function verifySignature(timestamp: string, token: string, signature: string, signingKey: string): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* Verify a Mailgun webhook payload using the nested `signature` object.
|
|
11
|
+
* Returns false when the payload shape is invalid.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyPayload(payload: unknown, signingKey: string): Promise<boolean>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EmailEvent } from "./types.js";
|
|
2
|
+
/** Parse a Resend webhook payload into normalized {@link EmailEvent} records. */
|
|
3
|
+
export declare function parse(payload: unknown): EmailEvent[];
|
|
4
|
+
/**
|
|
5
|
+
* Verify a Resend webhook signature (Svix-style) using Web Crypto.
|
|
6
|
+
* Returns false when verification cannot be performed or the signature is invalid.
|
|
7
|
+
*/
|
|
8
|
+
export declare function verifySignature(payload: string, headers: {
|
|
9
|
+
"svix-id"?: string;
|
|
10
|
+
"svix-timestamp"?: string;
|
|
11
|
+
"svix-signature"?: string;
|
|
12
|
+
}, secret: string): Promise<boolean>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EmailEvent } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse an AWS SES webhook payload (SNS envelope) into normalized {@link EmailEvent} records.
|
|
4
|
+
* Handles SubscriptionConfirmation (returns `[]`) and double-encoded Notification messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parse(payload: unknown): EmailEvent[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constant-time byte comparison for cryptographic signatures.
|
|
3
|
+
* Never short-circuits the XOR loop; length mismatch is folded into `diff`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function timingSafeEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
6
|
+
/** Decode a base64 string to bytes; returns null on invalid input. */
|
|
7
|
+
export declare function decodeBase64Bytes(value: string): Uint8Array | null;
|
|
8
|
+
/** Decode a lowercase hex string to bytes; returns null on invalid input. */
|
|
9
|
+
export declare function decodeHexBytes(value: string): Uint8Array | null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Normalized email lifecycle event from any provider webhook. */
|
|
2
|
+
export interface EmailEvent {
|
|
3
|
+
/** Provider identifier (e.g. `"resend"`, `"sendgrid"`). */
|
|
4
|
+
provider: string;
|
|
5
|
+
/** Normalized event type. */
|
|
6
|
+
type: "delivered" | "bounced" | "complained" | "opened" | "clicked" | "deferred" | "unknown";
|
|
7
|
+
/** Provider message identifier when available. */
|
|
8
|
+
messageId?: string;
|
|
9
|
+
/** Recipient email address when available. */
|
|
10
|
+
recipient?: string;
|
|
11
|
+
/** Event timestamp when available. */
|
|
12
|
+
timestamp?: Date;
|
|
13
|
+
/** Original provider payload for debugging or custom handling. */
|
|
14
|
+
raw: unknown;
|
|
15
|
+
}
|
|
16
|
+
/** Map a provider-specific event string to a normalized {@link EmailEvent.type}. */
|
|
17
|
+
export type EventTypeMapper = (event: string) => EmailEvent["type"];
|
|
18
|
+
/** Default mapper — returns `"unknown"` for unrecognized events. */
|
|
19
|
+
export declare function mapEventType(event: string, mapping: Record<string, EmailEvent["type"]>): EmailEvent["type"];
|
|
20
|
+
/** Parse an ISO or Unix timestamp into a Date, or undefined when invalid. */
|
|
21
|
+
export declare function parseTimestamp(value: unknown): Date | undefined;
|
|
22
|
+
/** Ensure payload is an array (for providers that batch events). */
|
|
23
|
+
export declare function asArray(payload: unknown): unknown[];
|
|
24
|
+
/** Build an {@link EmailEvent} respecting exact optional property types. */
|
|
25
|
+
export declare function emailEvent(input: {
|
|
26
|
+
provider: string;
|
|
27
|
+
type: EmailEvent["type"];
|
|
28
|
+
raw: unknown;
|
|
29
|
+
messageId?: string | undefined;
|
|
30
|
+
recipient?: string | undefined;
|
|
31
|
+
timestamp?: Date | undefined;
|
|
32
|
+
}): EmailEvent;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Provider webhook parsers — normalize delivery events into {@link EmailEvent}.
|
|
4
|
+
*
|
|
5
|
+
* Pure parsing only; no server framework required. Signature verification
|
|
6
|
+
* helpers are optional and never required to call `parse`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { parseResendWebhook } from "sently/webhooks";
|
|
11
|
+
*
|
|
12
|
+
* const events = parseResendWebhook(await request.json());
|
|
13
|
+
* for (const event of events) {
|
|
14
|
+
* if (event.type === "bounced") {
|
|
15
|
+
* // handle bounce
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export { parse as parseBrevoWebhook } from "./webhooks/brevo.js";
|
|
21
|
+
export { parse as parseMailgunWebhook, verifyPayload as verifyMailgunPayload, verifySignature as verifyMailgunSignature, } from "./webhooks/mailgun.js";
|
|
22
|
+
export { parse as parsePostmarkWebhook } from "./webhooks/postmark.js";
|
|
23
|
+
export { parse as parseResendWebhook, verifySignature as verifyResendSignature, } from "./webhooks/resend.js";
|
|
24
|
+
export { parse as parseSendGridWebhook } from "./webhooks/sendgrid.js";
|
|
25
|
+
export { parse as parseSesWebhook } from "./webhooks/ses.js";
|
|
26
|
+
export type { EmailEvent } from "./webhooks/types.js";
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import"./chunk-rhade81v.js";export{v as verifyResendSignature,t as verifyMailgunSignature,n as verifyMailgunPayload,k as parseSesWebhook,m as parseSendGridWebhook,l as parseResendWebhook,p as parsePostmarkWebhook,s as parseMailgunWebhook,o as parseBrevoWebhook};
|
|
2
|
+
|
|
3
|
+
//# debugId=0ADA81CFDCA8B16A64756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sently",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Runtime-agnostic email library for Node.js, Bun, Deno, and Cloudflare Workers. ESM-native Nodemailer alternative with zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,13 +33,20 @@
|
|
|
33
33
|
"publish:jsr": "npx jsr publish",
|
|
34
34
|
"publish:all-d": "bun run publish:npm-d && bun run publish:jsr-d",
|
|
35
35
|
"publish:all": "bun run publish:npm && bun run publish:jsr",
|
|
36
|
-
"verify:publish": "bun run pack:npm"
|
|
36
|
+
"verify:publish": "bun run pack:npm",
|
|
37
|
+
"measure:size": "bun tools/measure-bundle-size.ts",
|
|
38
|
+
"measure:size:md": "bun tools/measure-bundle-size.ts --markdown",
|
|
39
|
+
"check:size": "bun tools/measure-bundle-size.ts --check"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
|
-
"@biomejs/biome": "
|
|
40
|
-
"@modelcontextprotocol/sdk": "
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
42
|
+
"@biomejs/biome": "2.4.16",
|
|
43
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
44
|
+
"@react-email/render": "1.1.2",
|
|
45
|
+
"@types/node": "25.9.1",
|
|
46
|
+
"@types/react": "19.2.15",
|
|
47
|
+
"react": "19.2.6",
|
|
48
|
+
"react-dom": "19.2.6",
|
|
49
|
+
"typescript": "6.0.3"
|
|
43
50
|
},
|
|
44
51
|
"keywords": [
|
|
45
52
|
"email",
|
|
@@ -146,6 +153,38 @@
|
|
|
146
153
|
"./pool": {
|
|
147
154
|
"import": "./dist/pool/pool.js",
|
|
148
155
|
"types": "./dist/pool/pool.d.ts"
|
|
156
|
+
},
|
|
157
|
+
"./mailer": {
|
|
158
|
+
"import": "./dist/mailer.js",
|
|
159
|
+
"types": "./dist/mailer.d.ts"
|
|
160
|
+
},
|
|
161
|
+
"./dkim": {
|
|
162
|
+
"import": "./dist/dkim.js",
|
|
163
|
+
"types": "./dist/dkim.d.ts"
|
|
164
|
+
},
|
|
165
|
+
"./react": {
|
|
166
|
+
"import": "./dist/react.js",
|
|
167
|
+
"types": "./dist/react.d.ts"
|
|
168
|
+
},
|
|
169
|
+
"./idempotency": {
|
|
170
|
+
"import": "./dist/idempotency.js",
|
|
171
|
+
"types": "./dist/idempotency.d.ts"
|
|
172
|
+
},
|
|
173
|
+
"./webhooks": {
|
|
174
|
+
"import": "./dist/webhooks.js",
|
|
175
|
+
"types": "./dist/webhooks.d.ts"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"peerDependencies": {
|
|
179
|
+
"@react-email/render": ">=0.0.9",
|
|
180
|
+
"react": ">=18.0.0"
|
|
181
|
+
},
|
|
182
|
+
"peerDependenciesMeta": {
|
|
183
|
+
"@react-email/render": {
|
|
184
|
+
"optional": true
|
|
185
|
+
},
|
|
186
|
+
"react": {
|
|
187
|
+
"optional": true
|
|
149
188
|
}
|
|
150
189
|
}
|
|
151
190
|
}
|