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
|
@@ -1,120 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extractEmails,
|
|
3
|
-
parseAddresses,
|
|
4
|
-
resolveAttachments,
|
|
5
|
-
toMIMEHeader
|
|
6
|
-
} from "../chunk-f4c9ttmr.js";
|
|
7
|
-
import {
|
|
8
|
-
encodeBase64
|
|
9
|
-
} from "../chunk-794hc3m4.js";
|
|
10
|
-
import"../chunk-v0bahtg2.js";
|
|
1
|
+
import{l as Q}from"../chunk-cvmk9h1r.js";import{n as D,o as F,p as O}from"../chunk-kdqxhybf.js";import{G as L}from"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";class R extends Error{statusCode;apiError;constructor(q,C,G){super(q);this.statusCode=C;this.apiError=G;this.name="MailgunError"}}class X{apiKey;domain;baseUrl;constructor(q){this.apiKey=q.apiKey,this.domain=q.domain,this.baseUrl=q.region==="eu"?"https://api.eu.mailgun.net":"https://api.mailgun.net"}async send(q){let C=await Q(q.attachments),G=D(q.from)[0],w=new FormData;if(w.append("from",G?F(G):""),w.append("to",D(q.to).map(F).join(", ")),q.cc)w.append("cc",D(q.cc).map(F).join(", "));if(q.bcc)w.append("bcc",D(q.bcc).map(F).join(", "));if(w.append("subject",q.subject),q.text)w.append("text",q.text);if(q.html)w.append("html",q.html);if(q.replyTo){let z=D(q.replyTo)[0];if(z)w.append("h:Reply-To",F(z))}for(let z of C){let U=z.content instanceof Uint8Array?z.content:new TextEncoder().encode(String(z.content??"")),W=new Blob([new Uint8Array(U)],{type:z.contentType??"application/octet-stream"});w.append("attachment",W,z.filename)}let S=L(`api:${this.apiKey}`).replace(/\r\n/g,""),K=await fetch(`${this.baseUrl}/v3/${this.domain}/messages`,{method:"POST",headers:{Authorization:`Basic ${S}`},body:w}),J=await K.json();if(!K.ok)throw new R(J.message??"Mailgun API error",K.status,J);let N=O(q.to);return{messageId:J.id??"",accepted:N,rejected:[],response:J.message??"queued",envelope:{from:G?.address??"",to:N}}}async verify(){try{let q=L(`api:${this.apiKey}`).replace(/\r\n/g,""),C=await fetch(`${this.baseUrl}/v3/domains`,{headers:{Authorization:`Basic ${q}`}});if(!C.ok)return{ok:!1,provider:"mailgun",message:(await C.json().catch(()=>({}))).message??`HTTP ${C.status}`};return{ok:!0,provider:"mailgun",message:"API key is valid"}}catch(q){return{ok:!1,provider:"mailgun",message:q instanceof Error?q.message:String(q)}}}}export{X as MailgunTransport,R as MailgunError};
|
|
11
2
|
|
|
12
|
-
|
|
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
|
-
async verify() {
|
|
90
|
-
try {
|
|
91
|
-
const auth = encodeBase64(`api:${this.apiKey}`).replace(/\r\n/g, "");
|
|
92
|
-
const response = await fetch(`${this.baseUrl}/v3/domains`, {
|
|
93
|
-
headers: {
|
|
94
|
-
Authorization: `Basic ${auth}`
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
const payload = await response.json().catch(() => ({}));
|
|
99
|
-
return {
|
|
100
|
-
ok: false,
|
|
101
|
-
provider: "mailgun",
|
|
102
|
-
message: payload.message ?? `HTTP ${response.status}`
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
return { ok: true, provider: "mailgun", message: "API key is valid" };
|
|
106
|
-
} catch (err) {
|
|
107
|
-
return {
|
|
108
|
-
ok: false,
|
|
109
|
-
provider: "mailgun",
|
|
110
|
-
message: err instanceof Error ? err.message : String(err)
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
export {
|
|
116
|
-
MailgunTransport,
|
|
117
|
-
MailgunError
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
//# debugId=0BB5E6A11ACE98B264756E2164756E21
|
|
3
|
+
//# debugId=58D5A6ECB5B1262364756E2164756E21
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Mailgun HTTP transport for sently.\n * Uses the Mailgun Messages API v3 with multipart/form-data.\n *\n * @example\n * ```ts\n * import { MailgunTransport } from \"sently/transports/mailgun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new MailgunTransport({\n * apiKey: \"key-...\",\n * domain: \"mg.example.com\",\n * }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n MailgunConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Mailgun API returns a non-success response. */\nexport class MailgunError extends 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 /** Mailgun API key for basic authentication. */\n private readonly apiKey: string;\n /** Mailgun sending domain. */\n private readonly domain: string;\n /** Mailgun API base URL (US or EU region). */\n private readonly baseUrl: string;\n\n /** Creates a Mailgun transport with the given API key and domain. */\n constructor(config: MailgunConfig) {\n this.apiKey = config.apiKey;\n this.domain = config.domain;\n this.baseUrl =\n config.region === \"eu\" ? \"https://api.eu.mailgun.net\" : \"https://api.mailgun.net\";\n }\n\n /** Sends an email via the Mailgun Messages API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n const form = new FormData();\n\n form.append(\"from\", from ? toMIMEHeader(from) : \"\");\n form.append(\"to\", parseAddresses(options.to).map(toMIMEHeader).join(\", \"));\n\n if (options.cc) {\n form.append(\"cc\", parseAddresses(options.cc).map(toMIMEHeader).join(\", \"));\n }\n if (options.bcc) {\n form.append(\"bcc\", parseAddresses(options.bcc).map(toMIMEHeader).join(\", \"));\n }\n\n form.append(\"subject\", options.subject);\n\n if (options.text) {\n form.append(\"text\", options.text);\n }\n if (options.html) {\n form.append(\"html\", options.html);\n }\n if (options.replyTo) {\n const replyTo = parseAddresses(options.replyTo)[0];\n if (replyTo) {\n form.append(\"h:Reply-To\", toMIMEHeader(replyTo));\n }\n }\n\n for (const attachment of attachments) {\n const content =\n attachment.content instanceof Uint8Array\n ? attachment.content\n : new TextEncoder().encode(String(attachment.content ?? \"\"));\n const blob = new Blob([new Uint8Array(content)], {\n type: attachment.contentType ?? \"application/octet-stream\",\n });\n form.append(\"attachment\", blob, attachment.filename);\n }\n\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/${this.domain}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: `Basic ${auth}`,\n },\n body: form,\n });\n\n const payload = (await response.json()) as { id?: string; message?: string };\n\n if (!response.ok) {\n throw new MailgunError(payload.message ?? \"Mailgun API error\", response.status, payload);\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.id ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.message ?? \"queued\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Mailgun API key by listing domains. */\n async verify(): Promise<VerifyResult> {\n try {\n const auth = encodeBase64(`api:${this.apiKey}`).replace(/\\r\\n/g, \"\");\n const response = await fetch(`${this.baseUrl}/v3/domains`, {\n headers: {\n Authorization: `Basic ${auth}`,\n },\n });\n\n if (!response.ok) {\n const payload = (await response.json().catch(() => ({}))) as { message?: string };\n return {\n ok: false,\n provider: \"mailgun\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return { ok: true, provider: \"mailgun\", message: \"API key is valid\" };\n } catch (err) {\n return {\n ok: false,\n provider: \"mailgun\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "qLA8BO,CAAM,KAAqB,AAAM,CAIpB,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MAAM,CAAO,EAHG,kBACA,gBAGhB,KAAK,KAAO,eAEhB,CAKO,MAAM,CAAsC,CAEhC,OAEA,OAEA,QAGjB,WAAW,CAAC,EAAuB,CACjC,KAAK,OAAS,EAAO,OACrB,KAAK,OAAS,EAAO,OACrB,KAAK,QACH,EAAO,SAAW,KAAO,6BAA+B,+BAItD,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAO,IAAI,SAKjB,GAHA,EAAK,OAAO,OAAQ,EAAO,EAAa,CAAI,EAAI,EAAE,EAClD,EAAK,OAAO,KAAM,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAErE,EAAQ,GACV,EAAK,OAAO,KAAM,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAE3E,GAAI,EAAQ,IACV,EAAK,OAAO,MAAO,EAAe,EAAQ,GAAG,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAC,EAK7E,GAFA,EAAK,OAAO,UAAW,EAAQ,OAAO,EAElC,EAAQ,KACV,EAAK,OAAO,OAAQ,EAAQ,IAAI,EAElC,GAAI,EAAQ,KACV,EAAK,OAAO,OAAQ,EAAQ,IAAI,EAElC,GAAI,EAAQ,QAAS,CACnB,IAAM,EAAU,EAAe,EAAQ,OAAO,EAAE,GAChD,GAAI,EACF,EAAK,OAAO,aAAc,EAAa,CAAO,CAAC,EAInD,QAAW,KAAc,EAAa,CACpC,IAAM,EACJ,EAAW,mBAAmB,WAC1B,EAAW,QACX,IAAI,YAAY,EAAE,OAAO,OAAO,EAAW,SAAW,EAAE,CAAC,EACzD,EAAO,IAAI,KAAK,CAAC,IAAI,WAAW,CAAO,CAAC,EAAG,CAC/C,KAAM,EAAW,aAAe,0BAClC,CAAC,EACD,EAAK,OAAO,aAAc,EAAM,EAAW,QAAQ,EAGrD,IAAM,EAAO,EAAa,OAAO,KAAK,QAAQ,EAAE,QAAQ,QAAS,EAAE,EAC7D,EAAW,MAAM,MAAM,GAAG,KAAK,cAAc,KAAK,kBAAmB,CACzE,OAAQ,OACR,QAAS,CACP,cAAe,SAAS,GAC1B,EACA,KAAM,CACR,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EAAa,EAAQ,SAAW,oBAAqB,EAAS,OAAQ,CAAO,EAGzF,IAAM,EAAW,EAAc,EAAQ,EAAE,EACzC,MAAO,CACL,UAAW,EAAQ,IAAM,GACzB,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,SAAW,SAC7B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACN,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAO,EAAa,OAAO,KAAK,QAAQ,EAAE,QAAQ,QAAS,EAAE,EAC7D,EAAW,MAAM,MAAM,GAAG,KAAK,qBAAsB,CACzD,QAAS,CACP,cAAe,SAAS,GAC1B,CACF,CAAC,EAED,GAAI,CAAC,EAAS,GAEZ,MAAO,CACL,GAAI,GACJ,SAAU,UACV,SAJe,MAAM,EAAS,KAAK,EAAE,MAAM,KAAO,CAAC,EAAE,GAIpC,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CAAE,GAAI,GAAM,SAAU,UAAW,QAAS,kBAAmB,EACpE,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,UACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
|
|
8
|
+
"debugId": "58D5A6ECB5B1262364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,114 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extractEmails,
|
|
3
|
-
parseAddresses,
|
|
4
|
-
resolveAttachments,
|
|
5
|
-
toMIMEHeader
|
|
6
|
-
} from "../chunk-f4c9ttmr.js";
|
|
7
|
-
import {
|
|
8
|
-
encodeBase64
|
|
9
|
-
} from "../chunk-794hc3m4.js";
|
|
10
|
-
import"../chunk-v0bahtg2.js";
|
|
1
|
+
import{l as O}from"../chunk-cvmk9h1r.js";import{n as C,o as D,p as F}from"../chunk-kdqxhybf.js";import{G as L}from"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";class Q extends Error{statusCode;apiError;constructor(q,z,G){super(q);this.statusCode=z;this.apiError=G;this.name="PostmarkError"}}class T{serverToken;constructor(q){this.serverToken=q.serverToken}async send(q){let z=await O(q.attachments),G=C(q.from)[0],R={From:G?D(G):"",To:C(q.to).map(D).join(", "),Subject:q.subject,...q.cc?{Cc:C(q.cc).map(D).join(", ")}:{},...q.bcc?{Bcc:C(q.bcc).map(D).join(", ")}:{},...q.replyTo?{ReplyTo:C(q.replyTo).map(D).join(", ")}:{},...q.text?{TextBody:q.text}:{},...q.html?{HtmlBody:q.html}:{},...q.headers?{Headers:Object.entries(q.headers).map(([w,S])=>({Name:w,Value:S}))}:{},...z.length>0?{Attachments:z.map((w)=>({Name:w.filename,Content:w.content instanceof Uint8Array?L(w.content).replace(/\r\n/g,""):w.content,ContentType:w.contentType??"application/octet-stream",...w.contentId?{ContentID:w.contentId}:{}}))}:{}},K=await fetch("https://api.postmarkapp.com/email",{method:"POST",headers:{"X-Postmark-Server-Token":this.serverToken,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(R)}),J=await K.json();if(!K.ok)throw new Q(J.Message??"Postmark API error",K.status,J);return{messageId:J.MessageID??q.messageId??"",accepted:F(q.to),rejected:[],response:J.Message??"OK",envelope:{from:G?.address??"",to:[...F(q.to),...q.cc?F(q.cc):[],...q.bcc?F(q.bcc):[]]}}}async verify(){try{let q=await fetch("https://api.postmarkapp.com/server",{headers:{"X-Postmark-Server-Token":this.serverToken,Accept:"application/json"}}),z=await q.json();if(!q.ok)return{ok:!1,provider:"postmark",message:z.Message??`HTTP ${q.status}`};return{ok:!0,provider:"postmark",...z.Name?{message:z.Name}:{}}}catch(q){return{ok:!1,provider:"postmark",message:q instanceof Error?q.message:String(q)}}}}export{T as PostmarkTransport,Q as PostmarkError};
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
class PostmarkError 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 = "PostmarkError";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class PostmarkTransport {
|
|
25
|
-
serverToken;
|
|
26
|
-
constructor(config) {
|
|
27
|
-
this.serverToken = config.serverToken;
|
|
28
|
-
}
|
|
29
|
-
async send(options) {
|
|
30
|
-
const attachments = await resolveAttachments(options.attachments);
|
|
31
|
-
const from = parseAddresses(options.from)[0];
|
|
32
|
-
const body = {
|
|
33
|
-
From: from ? toMIMEHeader(from) : "",
|
|
34
|
-
To: parseAddresses(options.to).map(toMIMEHeader).join(", "),
|
|
35
|
-
Subject: options.subject,
|
|
36
|
-
...options.cc ? { Cc: parseAddresses(options.cc).map(toMIMEHeader).join(", ") } : {},
|
|
37
|
-
...options.bcc ? { Bcc: parseAddresses(options.bcc).map(toMIMEHeader).join(", ") } : {},
|
|
38
|
-
...options.replyTo ? { ReplyTo: parseAddresses(options.replyTo).map(toMIMEHeader).join(", ") } : {},
|
|
39
|
-
...options.text ? { TextBody: options.text } : {},
|
|
40
|
-
...options.html ? { HtmlBody: options.html } : {},
|
|
41
|
-
...options.headers ? { Headers: Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value })) } : {},
|
|
42
|
-
...attachments.length > 0 ? {
|
|
43
|
-
Attachments: attachments.map((att) => ({
|
|
44
|
-
Name: att.filename,
|
|
45
|
-
Content: att.content instanceof Uint8Array ? encodeBase64(att.content).replace(/\r\n/g, "") : att.content,
|
|
46
|
-
ContentType: att.contentType ?? "application/octet-stream",
|
|
47
|
-
...att.contentId ? { ContentID: att.contentId } : {}
|
|
48
|
-
}))
|
|
49
|
-
} : {}
|
|
50
|
-
};
|
|
51
|
-
const response = await fetch("https://api.postmarkapp.com/email", {
|
|
52
|
-
method: "POST",
|
|
53
|
-
headers: {
|
|
54
|
-
"X-Postmark-Server-Token": this.serverToken,
|
|
55
|
-
"Content-Type": "application/json",
|
|
56
|
-
Accept: "application/json"
|
|
57
|
-
},
|
|
58
|
-
body: JSON.stringify(body)
|
|
59
|
-
});
|
|
60
|
-
const payload = await response.json();
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new PostmarkError(payload.Message ?? "Postmark API error", response.status, payload);
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
messageId: payload.MessageID ?? options.messageId ?? "",
|
|
66
|
-
accepted: extractEmails(options.to),
|
|
67
|
-
rejected: [],
|
|
68
|
-
response: payload.Message ?? "OK",
|
|
69
|
-
envelope: {
|
|
70
|
-
from: from?.address ?? "",
|
|
71
|
-
to: [
|
|
72
|
-
...extractEmails(options.to),
|
|
73
|
-
...options.cc ? extractEmails(options.cc) : [],
|
|
74
|
-
...options.bcc ? extractEmails(options.bcc) : []
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async verify() {
|
|
80
|
-
try {
|
|
81
|
-
const response = await fetch("https://api.postmarkapp.com/server", {
|
|
82
|
-
headers: {
|
|
83
|
-
"X-Postmark-Server-Token": this.serverToken,
|
|
84
|
-
Accept: "application/json"
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
const payload = await response.json();
|
|
88
|
-
if (!response.ok) {
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
provider: "postmark",
|
|
92
|
-
message: payload.Message ?? `HTTP ${response.status}`
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
provider: "postmark",
|
|
98
|
-
...payload.Name ? { message: payload.Name } : {}
|
|
99
|
-
};
|
|
100
|
-
} catch (err) {
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
provider: "postmark",
|
|
104
|
-
message: err instanceof Error ? err.message : String(err)
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
export {
|
|
110
|
-
PostmarkTransport,
|
|
111
|
-
PostmarkError
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
//# debugId=650BF60942E0759C64756E2164756E21
|
|
3
|
+
//# debugId=F549E280C6C8948364756E2164756E21
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Postmark HTTP API transport for sending email via api.postmarkapp.com.\n *\n * @example\n * ```ts\n * import { PostmarkTransport } from \"sently/transports/postmark\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new PostmarkTransport({ serverToken: process.env.POSTMARK_TOKEN! }),\n * });\n *\n * await mailer.send({\n * from: \"sender@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Plain text body\",\n * });\n * ```\n */\nimport { extractEmails, parseAddresses, toMIMEHeader } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Postmark API configuration. */\nexport interface PostmarkConfig {\n /** Postmark server API token. */\n serverToken: string;\n}\n\n/** Error thrown when the Postmark API returns a non-success response. */\nexport class PostmarkError extends Error {\n /** Creates a Postmark API error with status code and response payload. */\n constructor(\n message: string,\n public readonly statusCode: number,\n public readonly apiError: unknown,\n ) {\n super(message);\n this.name = \"PostmarkError\";\n }\n}\n\n/**\n * Postmark HTTP API transport.\n */\nexport class PostmarkTransport implements Transport {\n /** Postmark server API token for the X-Postmark-Server-Token header. */\n private readonly serverToken: string;\n\n /** Creates a Postmark transport with the given server token. */\n constructor(config: PostmarkConfig) {\n this.serverToken = config.serverToken;\n }\n\n /** Sends an email via the Postmark HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body = {\n From: from ? toMIMEHeader(from) : \"\",\n To: parseAddresses(options.to).map(toMIMEHeader).join(\", \"),\n Subject: options.subject,\n ...(options.cc ? { Cc: parseAddresses(options.cc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.bcc ? { Bcc: parseAddresses(options.bcc).map(toMIMEHeader).join(\", \") } : {}),\n ...(options.replyTo\n ? { ReplyTo: parseAddresses(options.replyTo).map(toMIMEHeader).join(\", \") }\n : {}),\n ...(options.text ? { TextBody: options.text } : {}),\n ...(options.html ? { HtmlBody: options.html } : {}),\n ...(options.headers\n ? { Headers: Object.entries(options.headers).map(([Name, Value]) => ({ Name, Value })) }\n : {}),\n ...(attachments.length > 0\n ? {\n Attachments: attachments.map((att) => ({\n Name: att.filename,\n Content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n ContentType: att.contentType ?? \"application/octet-stream\",\n ...(att.contentId ? { ContentID: att.contentId } : {}),\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.postmarkapp.com/email\", {\n method: \"POST\",\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n MessageID?: string;\n Message?: string;\n ErrorCode?: number;\n };\n\n if (!response.ok) {\n throw new PostmarkError(payload.Message ?? \"Postmark API error\", response.status, payload);\n }\n\n return {\n messageId: payload.MessageID ?? options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: payload.Message ?? \"OK\",\n envelope: {\n from: from?.address ?? \"\",\n to: [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ],\n },\n };\n }\n\n /** Verifies the Postmark server token by fetching server info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.postmarkapp.com/server\", {\n headers: {\n \"X-Postmark-Server-Token\": this.serverToken,\n Accept: \"application/json\",\n },\n });\n\n const payload = (await response.json()) as { Name?: string; Message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"postmark\",\n message: payload.Message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"postmark\",\n ...(payload.Name ? { message: payload.Name } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"postmark\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "qLAiCO,CAAM,KAAsB,AAAM,CAIrB,WACA,SAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MAAM,CAAO,EAHG,kBACA,gBAGhB,KAAK,KAAO,gBAEhB,CAKO,MAAM,CAAuC,CAEjC,YAGjB,WAAW,CAAC,EAAwB,CAClC,KAAK,YAAc,EAAO,iBAItB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GAEpC,EAAO,CACX,KAAM,EAAO,EAAa,CAAI,EAAI,GAClC,GAAI,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,EAC1D,QAAS,EAAQ,WACb,EAAQ,GAAK,CAAE,GAAI,EAAe,EAAQ,EAAE,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAE,EAAI,CAAC,KAChF,EAAQ,IAAM,CAAE,IAAK,EAAe,EAAQ,GAAG,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAE,EAAI,CAAC,KACnF,EAAQ,QACR,CAAE,QAAS,EAAe,EAAQ,OAAO,EAAE,IAAI,CAAY,EAAE,KAAK,IAAI,CAAE,EACxE,CAAC,KACD,EAAQ,KAAO,CAAE,SAAU,EAAQ,IAAK,EAAI,CAAC,KAC7C,EAAQ,KAAO,CAAE,SAAU,EAAQ,IAAK,EAAI,CAAC,KAC7C,EAAQ,QACR,CAAE,QAAS,OAAO,QAAQ,EAAQ,OAAO,EAAE,IAAI,EAAE,EAAM,MAAY,CAAE,OAAM,OAAM,EAAE,CAAE,EACrF,CAAC,KACD,EAAY,OAAS,EACrB,CACE,YAAa,EAAY,IAAI,CAAC,KAAS,CACrC,KAAM,EAAI,SACV,QACE,EAAI,mBAAmB,WACnB,EAAa,EAAI,OAAO,EAAE,QAAQ,QAAS,EAAE,EAC7C,EAAI,QACV,YAAa,EAAI,aAAe,8BAC5B,EAAI,UAAY,CAAE,UAAW,EAAI,SAAU,EAAI,CAAC,CACtD,EAAE,CACJ,EACA,CAAC,CACP,EAEM,EAAW,MAAM,MAAM,oCAAqC,CAChE,OAAQ,OACR,QAAS,CACP,0BAA2B,KAAK,YAChC,eAAgB,mBAChB,OAAQ,kBACV,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAMrC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EAAc,EAAQ,SAAW,qBAAsB,EAAS,OAAQ,CAAO,EAG3F,MAAO,CACL,UAAW,EAAQ,WAAa,EAAQ,WAAa,GACrD,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,EAAQ,SAAW,KAC7B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACF,GAAG,EAAc,EAAQ,EAAE,EAC3B,GAAI,EAAQ,GAAK,EAAc,EAAQ,EAAE,EAAI,CAAC,EAC9C,GAAI,EAAQ,IAAM,EAAc,EAAQ,GAAG,EAAI,CAAC,CAClD,CACF,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,qCAAsC,CACjE,QAAS,CACP,0BAA2B,KAAK,YAChC,OAAQ,kBACV,CACF,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAO,CACL,GAAI,GACJ,SAAU,WACV,QAAS,EAAQ,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CACL,GAAI,GACJ,SAAU,cACN,EAAQ,KAAO,CAAE,QAAS,EAAQ,IAAK,EAAI,CAAC,CAClD,EACA,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,WACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
|
|
8
|
+
"debugId": "F549E280C6C8948364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,73 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildMIME
|
|
3
|
-
} from "../chunk-x3szga4k.js";
|
|
4
|
-
import {
|
|
5
|
-
extractEmails,
|
|
6
|
-
resolveAttachments
|
|
7
|
-
} from "../chunk-f4c9ttmr.js";
|
|
8
|
-
import"../chunk-794hc3m4.js";
|
|
9
|
-
import {
|
|
10
|
-
__require
|
|
11
|
-
} from "../chunk-v0bahtg2.js";
|
|
1
|
+
import{k as L}from"../chunk-nhec1319.js";import{l as N}from"../chunk-cvmk9h1r.js";import{p as K}from"../chunk-kdqxhybf.js";import"../chunk-32dj0v9g.js";import{K as J}from"../chunk-rhade81v.js";function T(k){return k.replace(/[^a-zA-Z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"no-subject"}function U(k){if(k==="darwin")return"open";if(k==="win32")return"start";return"xdg-open"}class V{outDir;open;format;constructor(k){this.outDir=k?.outDir??"./.emails",this.open=k?.open??!1,this.format=k?.format??"eml"}async send(k){let H=await N(k.attachments),P={...k,attachments:H},D=await L(P),F=await import("node:fs/promises");await F.mkdir(this.outDir,{recursive:!0});let Q=`${Date.now()}-${T(k.subject)}.${this.format}`,B=`${this.outDir}/${Q}`;if(this.format==="html"){let G=k.html??(k.text?`<pre>${k.text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`:"");await F.writeFile(B,G,"utf8")}else await F.writeFile(B,D.raw);if(console.log(`[sently preview] Written: ${B}`),this.open){let{spawn:G}=await import("node:child_process"),R=U(process.platform);G(R,[B],{detached:!0,stdio:"ignore"}).unref()}return{messageId:D.messageId,accepted:K(k.to),rejected:[],response:`preview: ${B}`,envelope:D.envelope}}async verify(){return{ok:!0,provider:"preview"}}}export{V as PreviewTransport};
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
function sanitizeSubject(subject) {
|
|
15
|
-
const sanitized = subject.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
16
|
-
return sanitized.slice(0, 40) || "no-subject";
|
|
17
|
-
}
|
|
18
|
-
function getOpenCommand(platform) {
|
|
19
|
-
if (platform === "darwin") {
|
|
20
|
-
return "open";
|
|
21
|
-
}
|
|
22
|
-
if (platform === "win32") {
|
|
23
|
-
return "start";
|
|
24
|
-
}
|
|
25
|
-
return "xdg-open";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class PreviewTransport {
|
|
29
|
-
outDir;
|
|
30
|
-
open;
|
|
31
|
-
format;
|
|
32
|
-
constructor(config) {
|
|
33
|
-
this.outDir = config?.outDir ?? "./.emails";
|
|
34
|
-
this.open = config?.open ?? false;
|
|
35
|
-
this.format = config?.format ?? "eml";
|
|
36
|
-
}
|
|
37
|
-
async send(options) {
|
|
38
|
-
const attachments = await resolveAttachments(options.attachments);
|
|
39
|
-
const resolvedOptions = { ...options, attachments };
|
|
40
|
-
const mime = await buildMIME(resolvedOptions);
|
|
41
|
-
const fs = await import("node:fs/promises");
|
|
42
|
-
await fs.mkdir(this.outDir, { recursive: true });
|
|
43
|
-
const filename = `${Date.now()}-${sanitizeSubject(options.subject)}.${this.format}`;
|
|
44
|
-
const filepath = `${this.outDir}/${filename}`;
|
|
45
|
-
if (this.format === "html") {
|
|
46
|
-
const html = options.html ?? (options.text ? `<pre>${options.text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}</pre>` : "");
|
|
47
|
-
await fs.writeFile(filepath, html, "utf8");
|
|
48
|
-
} else {
|
|
49
|
-
await fs.writeFile(filepath, mime.raw);
|
|
50
|
-
}
|
|
51
|
-
console.log(`[sently preview] Written: ${filepath}`);
|
|
52
|
-
if (this.open) {
|
|
53
|
-
const { spawn } = await import("node:child_process");
|
|
54
|
-
const command = getOpenCommand(process.platform);
|
|
55
|
-
spawn(command, [filepath], { detached: true, stdio: "ignore" }).unref();
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
messageId: mime.messageId,
|
|
59
|
-
accepted: extractEmails(options.to),
|
|
60
|
-
rejected: [],
|
|
61
|
-
response: `preview: ${filepath}`,
|
|
62
|
-
envelope: mime.envelope
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
async verify() {
|
|
66
|
-
return { ok: true, provider: "preview" };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
export {
|
|
70
|
-
PreviewTransport
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
//# debugId=A9DB64292AD1542264756E2164756E21
|
|
3
|
+
//# debugId=64A43FCD4659806964756E2164756E21
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * PreviewTransport — development transport that writes emails to disk\n * instead of sending them. Optionally opens in the default browser.\n * Use in place of a real transport during local development.\n *\n * @example\n * ```ts\n * import { PreviewTransport } from 'sently/transports/preview'\n * const transport = new PreviewTransport({ outDir: './.emails', open: true })\n * ```\n */\nimport { extractEmails } from \"../core/address.js\";\nimport { buildMIME } from \"../core/mime.js\";\nimport type {\n MailOptions,\n PreviewConfig,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\nfunction sanitizeSubject(subject: string): string {\n const sanitized = subject.replace(/[^a-zA-Z0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n return sanitized.slice(0, 40) || \"no-subject\";\n}\n\nfunction getOpenCommand(platform: string): string {\n if (platform === \"darwin\") {\n return \"open\";\n }\n if (platform === \"win32\") {\n return \"start\";\n }\n return \"xdg-open\";\n}\n\n/**\n * Development transport that writes emails to disk instead of sending them.\n */\nexport class PreviewTransport implements Transport {\n /** Directory where preview `.eml` or `.html` files are written. */\n private readonly outDir: string;\n /** Whether to open the written file in the default browser. */\n private readonly open: boolean;\n /** Output format: full MIME (`.eml`) or HTML body only. */\n private readonly format: \"eml\" | \"html\";\n\n /** Creates a preview transport with optional output directory and format. */\n constructor(config?: PreviewConfig) {\n this.outDir = config?.outDir ?? \"./.emails\";\n this.open = config?.open ?? false;\n this.format = config?.format ?? \"eml\";\n }\n\n /** Writes the message to disk and returns a synthetic SendResult. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const resolvedOptions = { ...options, attachments };\n const mime = await buildMIME(resolvedOptions);\n\n const fs = await import(\"node:fs/promises\");\n await fs.mkdir(this.outDir, { recursive: true });\n\n const filename = `${Date.now()}-${sanitizeSubject(options.subject)}.${this.format}`;\n const filepath = `${this.outDir}/${filename}`;\n\n if (this.format === \"html\") {\n const html =\n options.html ??\n (options.text\n ? `<pre>${options.text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\")}</pre>`\n : \"\");\n await fs.writeFile(filepath, html, \"utf8\");\n } else {\n await fs.writeFile(filepath, mime.raw);\n }\n\n console.log(`[sently preview] Written: ${filepath}`);\n\n if (this.open) {\n const { spawn } = await import(\"node:child_process\");\n const command = getOpenCommand(process.platform);\n spawn(command, [filepath], { detached: true, stdio: \"ignore\" }).unref();\n }\n\n return {\n messageId: mime.messageId,\n accepted: extractEmails(options.to),\n rejected: [],\n response: `preview: ${filepath}`,\n envelope: mime.envelope,\n };\n }\n\n /** Always succeeds — preview transport requires no external connectivity. */\n async verify(): Promise<VerifyResult> {\n return { ok: true, provider: \"preview\" };\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "qNAuBA,IAAS,JAAe,JAAC,HAAyB,CAEhD,OADkB,EAAQ,QAAQ,iBAAkB,GAAG,EAAE,QAAQ,SAAU,EAAE,EAC5D,MAAM,EAAG,EAAE,GAAK,aAGnC,SAAS,CAAc,CAAC,EAA0B,CAChD,GAAI,IAAa,SACf,MAAO,OAET,GAAI,IAAa,QACf,MAAO,QAET,MAAO,WAMF,MAAM,CAAsC,CAEhC,OAEA,KAEA,OAGjB,WAAW,CAAC,EAAwB,CAClC,KAAK,OAAS,GAAQ,QAAU,YAChC,KAAK,KAAO,GAAQ,MAAQ,GAC5B,KAAK,OAAS,GAAQ,QAAU,WAI5B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAkB,IAAK,EAAS,aAAY,EAC5C,EAAO,MAAM,EAAU,CAAe,EAEtC,EAAK,KAAa,4BACxB,MAAM,EAAG,MAAM,KAAK,OAAQ,CAAE,UAAW,EAAK,CAAC,EAE/C,IAAM,EAAW,GAAG,KAAK,IAAI,KAAK,EAAgB,EAAQ,OAAO,KAAK,KAAK,SACrE,EAAW,GAAG,KAAK,UAAU,IAEnC,GAAI,KAAK,SAAW,OAAQ,CAC1B,IAAM,EACJ,EAAQ,OACP,EAAQ,KACL,QAAQ,EAAQ,KAAK,QAAQ,KAAM,OAAO,EAAE,QAAQ,KAAM,MAAM,EAAE,QAAQ,KAAM,MAAM,UACtF,IACN,MAAM,EAAG,UAAU,EAAU,EAAM,MAAM,EAEzC,WAAM,EAAG,UAAU,EAAU,EAAK,GAAG,EAKvC,GAFA,QAAQ,IAAI,6BAA6B,GAAU,EAE/C,KAAK,KAAM,CACb,IAAQ,SAAU,KAAa,8BACzB,EAAU,EAAe,QAAQ,QAAQ,EAC/C,EAAM,EAAS,CAAC,CAAQ,EAAG,CAAE,SAAU,GAAM,MAAO,QAAS,CAAC,EAAE,MAAM,EAGxE,MAAO,CACL,UAAW,EAAK,UAChB,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,YAAY,IACtB,SAAU,EAAK,QACjB,OAII,OAAM,EAA0B,CACpC,MAAO,CAAE,GAAI,GAAM,SAAU,SAAU,EAE3C",
|
|
8
|
+
"debugId": "64A43FCD4659806964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -5,7 +5,13 @@ export interface ResendConfig {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
/** API base URL. Default: `https://api.resend.com`. */
|
|
7
7
|
baseUrl?: string;
|
|
8
|
+
/** Max batch HTTP requests per rate window. Default: `2` (Resend API limit). Set `0` to disable. */
|
|
9
|
+
rateDelta?: number;
|
|
10
|
+
/** Rate limit window in milliseconds. Default: `1000`. */
|
|
11
|
+
rateLimit?: number;
|
|
8
12
|
}
|
|
13
|
+
/** Maximum messages per Resend batch request (overridable per account). */
|
|
14
|
+
export declare const RESEND_BATCH_MAX = 100;
|
|
9
15
|
/** Error thrown when the Resend API returns a non-success response. */
|
|
10
16
|
export declare class ResendError extends Error {
|
|
11
17
|
readonly statusCode: number;
|
|
@@ -21,10 +27,20 @@ export declare class ResendTransport implements Transport {
|
|
|
21
27
|
private readonly apiKey;
|
|
22
28
|
/** Resend API base URL. */
|
|
23
29
|
private readonly baseUrl;
|
|
30
|
+
/** Optional rate limiter for multi-chunk batch sends. */
|
|
31
|
+
private readonly rateLimiter;
|
|
32
|
+
/** Maximum messages per batch HTTP request. */
|
|
33
|
+
readonly batchMax = 100;
|
|
24
34
|
/** Creates a Resend transport with the given API key. */
|
|
25
35
|
constructor(config: ResendConfig);
|
|
36
|
+
/** Build the JSON body for a single Resend email. */
|
|
37
|
+
private buildEmailBody;
|
|
38
|
+
/** Map a Resend API response id to a normalized SendResult. */
|
|
39
|
+
private toSendResult;
|
|
26
40
|
/** Sends an email via the Resend HTTP API. */
|
|
27
41
|
send(options: MailOptions): Promise<SendResult>;
|
|
42
|
+
/** Send up to {@link RESEND_BATCH_MAX} attachment-free messages per batch request. */
|
|
43
|
+
sendBatch(messages: MailOptions[]): Promise<SendResult[]>;
|
|
28
44
|
/** Verifies the Resend API key by listing domains. */
|
|
29
45
|
verify(): Promise<VerifyResult>;
|
|
30
46
|
}
|
|
@@ -1,110 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extractEmails,
|
|
3
|
-
parseAddresses,
|
|
4
|
-
resolveAttachments,
|
|
5
|
-
toMIMEHeader
|
|
6
|
-
} from "../chunk-f4c9ttmr.js";
|
|
7
|
-
import {
|
|
8
|
-
encodeBase64
|
|
9
|
-
} from "../chunk-794hc3m4.js";
|
|
10
|
-
import"../chunk-v0bahtg2.js";
|
|
1
|
+
import{j as K}from"../chunk-zwm5bfa4.js";import{l as I}from"../chunk-cvmk9h1r.js";import{n as $,o as C,p as G}from"../chunk-kdqxhybf.js";import{t as V}from"../chunk-ntqmn6pe.js";import{G as v}from"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";var Z=100;class Y extends Error{statusCode;apiError;constructor(q,w,z){super(q);this.statusCode=w;this.apiError=z;this.name="ResendError"}}class S{apiKey;baseUrl;rateLimiter;batchMax=Z;constructor(q){this.apiKey=q.apiKey,this.baseUrl=q.baseUrl??"https://api.resend.com";let w=q.rateDelta??2;if(w>0)this.rateLimiter=new V(w,q.rateLimit??1000);else this.rateLimiter=null}async buildEmailBody(q){let w=await I(q.attachments),z=$(q.from)[0];return{from:z?C(z):"",to:G(q.to),subject:q.subject,...q.cc?{cc:G(q.cc)}:{},...q.bcc?{bcc:G(q.bcc)}:{},...q.replyTo?{reply_to:G(q.replyTo)}:{},...q.text?{text:q.text}:{},...q.html?{html:q.html}:{},...q.headers?{headers:q.headers}:{},...w.length>0?{attachments:w.map((F)=>({filename:F.filename,content:F.content instanceof Uint8Array?v(F.content).replace(/\r\n/g,""):F.content,...F.contentType?{content_type:F.contentType}:{}}))}:{}}}toSendResult(q,w){let z=$(q.from)[0];return{messageId:w.id??q.messageId??"",accepted:G(q.to),rejected:[],response:w.message??"Email sent",envelope:{from:z?.address??"",to:[...G(q.to),...q.cc?G(q.cc):[],...q.bcc?G(q.bcc):[]]},...w.batchError!==void 0?{batchError:w.batchError}:{}}}async send(q){let w=await this.buildEmailBody(q),z=K(q),F=await fetch(`${this.baseUrl}/emails`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json",...z?{"Idempotency-Key":z}:{}},body:JSON.stringify(w)}),O=await F.json();if(!F.ok)throw new Y(O.message??"Resend API error",F.status,O);return this.toSendResult(q,O)}async sendBatch(q){if(q.length===0)return[];let w=[];for(let z=0;z<q.length;z+=Z){if(z>0&&this.rateLimiter)await this.rateLimiter.acquire();let F=q.slice(z,z+Z),O=await Promise.all(F.map((J)=>this.buildEmailBody(J))),U=await fetch(`${this.baseUrl}/emails/batch`,{method:"POST",headers:{Authorization:`Bearer ${this.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(O)}),Q=await U.json();if(!U.ok)throw new Y(Q.message??"Resend batch API error",U.status,Q);let N=Q.data??[];for(let J=0;J<F.length;J++){let L=F[J],W=N[J];if(W?.id!==void 0){w.push(this.toSendResult(L,{id:W.id,...Q.message!==void 0?{message:Q.message}:{}}));continue}let P=W?.error??"Batch item failed";w.push(this.toSendResult(L,{message:P,batchError:new Y(P,U.status,W??{})}))}}return w}async verify(){try{let q=await fetch(`${this.baseUrl}/domains`,{headers:{Authorization:`Bearer ${this.apiKey}`}});if(!q.ok)return{ok:!1,provider:"resend",message:(await q.json().catch(()=>({}))).message??`HTTP ${q.status}`};return{ok:!0,provider:"resend",message:"API key is valid"}}catch(q){return{ok:!1,provider:"resend",message:q instanceof Error?q.message:String(q)}}}}export{S as ResendTransport,Y as ResendError,Z as RESEND_BATCH_MAX};
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
class ResendError 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 = "ResendError";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class ResendTransport {
|
|
25
|
-
apiKey;
|
|
26
|
-
baseUrl;
|
|
27
|
-
constructor(config) {
|
|
28
|
-
this.apiKey = config.apiKey;
|
|
29
|
-
this.baseUrl = config.baseUrl ?? "https://api.resend.com";
|
|
30
|
-
}
|
|
31
|
-
async send(options) {
|
|
32
|
-
const attachments = await resolveAttachments(options.attachments);
|
|
33
|
-
const from = parseAddresses(options.from)[0];
|
|
34
|
-
const body = {
|
|
35
|
-
from: from ? toMIMEHeader(from) : "",
|
|
36
|
-
to: extractEmails(options.to),
|
|
37
|
-
subject: options.subject,
|
|
38
|
-
...options.cc ? { cc: extractEmails(options.cc) } : {},
|
|
39
|
-
...options.bcc ? { bcc: extractEmails(options.bcc) } : {},
|
|
40
|
-
...options.replyTo ? { reply_to: extractEmails(options.replyTo) } : {},
|
|
41
|
-
...options.text ? { text: options.text } : {},
|
|
42
|
-
...options.html ? { html: options.html } : {},
|
|
43
|
-
...options.headers ? { headers: options.headers } : {},
|
|
44
|
-
...attachments.length > 0 ? {
|
|
45
|
-
attachments: attachments.map((att) => ({
|
|
46
|
-
filename: att.filename,
|
|
47
|
-
content: att.content instanceof Uint8Array ? encodeBase64(att.content).replace(/\r\n/g, "") : att.content,
|
|
48
|
-
...att.contentType ? { content_type: att.contentType } : {}
|
|
49
|
-
}))
|
|
50
|
-
} : {}
|
|
51
|
-
};
|
|
52
|
-
const response = await fetch(`${this.baseUrl}/emails`, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: {
|
|
55
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
56
|
-
"Content-Type": "application/json"
|
|
57
|
-
},
|
|
58
|
-
body: JSON.stringify(body)
|
|
59
|
-
});
|
|
60
|
-
const payload = await response.json();
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new ResendError(payload.message ?? "Resend API error", response.status, payload);
|
|
63
|
-
}
|
|
64
|
-
const accepted = extractEmails(options.to);
|
|
65
|
-
return {
|
|
66
|
-
messageId: payload.id ?? options.messageId ?? "",
|
|
67
|
-
accepted,
|
|
68
|
-
rejected: [],
|
|
69
|
-
response: payload.message ?? "Email sent",
|
|
70
|
-
envelope: {
|
|
71
|
-
from: from?.address ?? "",
|
|
72
|
-
to: [
|
|
73
|
-
...extractEmails(options.to),
|
|
74
|
-
...options.cc ? extractEmails(options.cc) : [],
|
|
75
|
-
...options.bcc ? extractEmails(options.bcc) : []
|
|
76
|
-
]
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
async verify() {
|
|
81
|
-
try {
|
|
82
|
-
const response = await fetch(`${this.baseUrl}/domains`, {
|
|
83
|
-
headers: {
|
|
84
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
if (!response.ok) {
|
|
88
|
-
const payload = await response.json().catch(() => ({}));
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
provider: "resend",
|
|
92
|
-
message: payload.message ?? `HTTP ${response.status}`
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return { ok: true, provider: "resend", message: "API key is valid" };
|
|
96
|
-
} catch (err) {
|
|
97
|
-
return {
|
|
98
|
-
ok: false,
|
|
99
|
-
provider: "resend",
|
|
100
|
-
message: err instanceof Error ? err.message : String(err)
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
export {
|
|
106
|
-
ResendTransport,
|
|
107
|
-
ResendError
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
//# debugId=C97D4F9F26828E0A64756E2164756E21
|
|
3
|
+
//# debugId=8E2BC77BDE920DD964756E2164756E21
|