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.
Files changed (108) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +180 -43
  3. package/dist/adapters/bun.js +2 -183
  4. package/dist/adapters/bun.js.map +2 -2
  5. package/dist/adapters/cf.js +2 -77
  6. package/dist/adapters/cf.js.map +2 -2
  7. package/dist/adapters/deno.js +2 -72
  8. package/dist/adapters/deno.js.map +2 -2
  9. package/dist/adapters/node.js +2 -180
  10. package/dist/adapters/node.js.map +2 -2
  11. package/dist/auth/oauth2.js +2 -13
  12. package/dist/auth/oauth2.js.map +1 -1
  13. package/dist/chunk-32dj0v9g.js +5 -0
  14. package/dist/{chunk-794hc3m4.js.map → chunk-32dj0v9g.js.map} +2 -2
  15. package/dist/chunk-cvmk9h1r.js +4 -0
  16. package/dist/chunk-cvmk9h1r.js.map +10 -0
  17. package/dist/chunk-j5m37k1c.js +4 -0
  18. package/dist/{chunk-ym3zzv8b.js.map → chunk-j5m37k1c.js.map} +2 -2
  19. package/dist/chunk-kdqxhybf.js +4 -0
  20. package/dist/chunk-kdqxhybf.js.map +10 -0
  21. package/dist/chunk-m2njnxre.js +13 -0
  22. package/dist/{chunk-tymfm441.js.map → chunk-m2njnxre.js.map} +2 -2
  23. package/dist/chunk-nhec1319.js +5 -0
  24. package/dist/chunk-nhec1319.js.map +10 -0
  25. package/dist/chunk-nj3wmg69.js +3 -0
  26. package/dist/{chunk-7fqv71z1.js.map → chunk-nj3wmg69.js.map} +2 -2
  27. package/dist/chunk-ntqmn6pe.js +4 -0
  28. package/dist/chunk-ntqmn6pe.js.map +10 -0
  29. package/dist/chunk-pjzsvsm6.js +4 -0
  30. package/dist/chunk-pjzsvsm6.js.map +11 -0
  31. package/dist/chunk-rhade81v.js +4 -0
  32. package/dist/{chunk-v0bahtg2.js.map → chunk-rhade81v.js.map} +1 -1
  33. package/dist/chunk-zwm5bfa4.js +4 -0
  34. package/dist/chunk-zwm5bfa4.js.map +10 -0
  35. package/dist/core/idempotency-key.d.ts +6 -0
  36. package/dist/core/rate-limiter.d.ts +23 -0
  37. package/dist/core/smtp.js +2 -31
  38. package/dist/core/smtp.js.map +1 -1
  39. package/dist/core/types.d.ts +42 -0
  40. package/dist/detect.d.ts +2 -0
  41. package/dist/detect.js +2 -180
  42. package/dist/detect.js.map +4 -5
  43. package/dist/dkim.d.ts +21 -0
  44. package/dist/dkim.js +9 -0
  45. package/dist/dkim.js.map +10 -0
  46. package/dist/idempotency.d.ts +46 -0
  47. package/dist/idempotency.js +3 -0
  48. package/dist/idempotency.js.map +10 -0
  49. package/dist/index.d.ts +106 -16
  50. package/dist/index.js +115 -14
  51. package/dist/mailer.d.ts +17 -0
  52. package/dist/mailer.js +3 -0
  53. package/dist/mailer.js.map +9 -0
  54. package/dist/plugins/react.d.ts +35 -0
  55. package/dist/plugins/react.js +3 -0
  56. package/dist/plugins/react.js.map +10 -0
  57. package/dist/plugins/template.js +2 -28
  58. package/dist/plugins/template.js.map +2 -2
  59. package/dist/pool/pool.d.ts +1 -23
  60. package/dist/pool/pool.js +2 -16
  61. package/dist/pool/pool.js.map +5 -3
  62. package/dist/react.d.ts +20 -0
  63. package/dist/react.js +3 -0
  64. package/dist/react.js.map +9 -0
  65. package/dist/transports/brevo.js +2 -115
  66. package/dist/transports/brevo.js.map +2 -2
  67. package/dist/transports/mailgun.js +2 -119
  68. package/dist/transports/mailgun.js.map +2 -2
  69. package/dist/transports/postmark.js +2 -113
  70. package/dist/transports/postmark.js.map +2 -2
  71. package/dist/transports/preview.js +2 -72
  72. package/dist/transports/preview.js.map +2 -2
  73. package/dist/transports/resend.d.ts +16 -0
  74. package/dist/transports/resend.js +2 -109
  75. package/dist/transports/resend.js.map +3 -3
  76. package/dist/transports/retry.d.ts +2 -0
  77. package/dist/transports/retry.js +2 -78
  78. package/dist/transports/retry.js.map +3 -3
  79. package/dist/transports/sendgrid.d.ts +8 -0
  80. package/dist/transports/sendgrid.js +2 -132
  81. package/dist/transports/sendgrid.js.map +3 -3
  82. package/dist/transports/ses.js +5 -251
  83. package/dist/transports/ses.js.map +2 -2
  84. package/dist/transports/smtp.js +2 -26
  85. package/dist/transports/smtp.js.map +1 -1
  86. package/dist/webhooks/brevo.d.ts +3 -0
  87. package/dist/webhooks/mailgun.d.ts +13 -0
  88. package/dist/webhooks/postmark.d.ts +3 -0
  89. package/dist/webhooks/resend.d.ts +12 -0
  90. package/dist/webhooks/sendgrid.d.ts +3 -0
  91. package/dist/webhooks/ses.d.ts +6 -0
  92. package/dist/webhooks/timing-safe-equal.d.ts +9 -0
  93. package/dist/webhooks/types.d.ts +32 -0
  94. package/dist/webhooks.d.ts +26 -0
  95. package/dist/webhooks.js +3 -0
  96. package/dist/webhooks.js.map +9 -0
  97. package/package.json +45 -6
  98. package/dist/chunk-794hc3m4.js +0 -105
  99. package/dist/chunk-7fqv71z1.js +0 -251
  100. package/dist/chunk-f4c9ttmr.js +0 -154
  101. package/dist/chunk-f4c9ttmr.js.map +0 -11
  102. package/dist/chunk-mp5c9bfd.js +0 -270
  103. package/dist/chunk-mp5c9bfd.js.map +0 -11
  104. package/dist/chunk-tymfm441.js +0 -405
  105. package/dist/chunk-v0bahtg2.js +0 -6
  106. package/dist/chunk-x3szga4k.js +0 -367
  107. package/dist/chunk-x3szga4k.js.map +0 -11
  108. package/dist/chunk-ym3zzv8b.js +0 -74
package/dist/index.js CHANGED
@@ -1,14 +1,115 @@
1
- export { GOOGLE_TOKEN_URL, MICROSOFT_TOKEN_URL, OAuth2Client } from "./auth/oauth2.js";
2
- export { SMTPError } from "./core/smtp.js";
3
- export { createMailer, detectRuntime } from "./detect.js";
4
- export { simpleEngine, templatePlugin } from "./plugins/template.js";
5
- export { SMTPPool } from "./pool/pool.js";
6
- export { BrevoError, BrevoTransport } from "./transports/brevo.js";
7
- export { MailgunError, MailgunTransport } from "./transports/mailgun.js";
8
- export { PostmarkError, PostmarkTransport } from "./transports/postmark.js";
9
- export { PreviewTransport } from "./transports/preview.js";
10
- export { ResendError, ResendTransport } from "./transports/resend.js";
11
- export { RetryTransport } from "./transports/retry.js";
12
- export { SendGridError, SendGridTransport } from "./transports/sendgrid.js";
13
- export { SESError, SESTransport } from "./transports/ses.js";
14
- export { SMTPTransport } from "./transports/smtp.js";
1
+ export {
2
+
3
+ GOOGLE_TOKEN_URL,
4
+
5
+ MICROSOFT_TOKEN_URL,
6
+
7
+ OAuth2Client,
8
+ } from "./auth/oauth2.js";
9
+ export {
10
+
11
+ SMTPError,
12
+ } from "./core/smtp.js";
13
+
14
+ export {
15
+
16
+ createMailer,
17
+
18
+ detectRuntime,
19
+ } from "./detect.js";
20
+
21
+ export {
22
+
23
+ importPrivateKey,
24
+
25
+ signDKIM,
26
+ } from "./dkim.js";
27
+
28
+ export {
29
+
30
+ IdempotencyTransport,
31
+
32
+ MemoryIdempotencyStore,
33
+ } from "./idempotency.js";
34
+
35
+ export {
36
+
37
+ simpleEngine,
38
+
39
+ templatePlugin,
40
+ } from "./plugins/template.js";
41
+ export {
42
+
43
+ SMTPPool,
44
+ } from "./pool/pool.js";
45
+ export {
46
+
47
+ BrevoError,
48
+
49
+ BrevoTransport,
50
+ } from "./transports/brevo.js";
51
+ export {
52
+
53
+ MailgunError,
54
+
55
+ MailgunTransport,
56
+ } from "./transports/mailgun.js";
57
+ export {
58
+
59
+ PostmarkError,
60
+
61
+ PostmarkTransport,
62
+ } from "./transports/postmark.js";
63
+ export {
64
+
65
+ PreviewTransport,
66
+ } from "./transports/preview.js";
67
+ export {
68
+
69
+ RESEND_BATCH_MAX,
70
+
71
+ ResendError,
72
+
73
+ ResendTransport,
74
+ } from "./transports/resend.js";
75
+ export {
76
+
77
+ RetryTransport,
78
+ } from "./transports/retry.js";
79
+ export {
80
+
81
+ SendGridError,
82
+
83
+ SendGridTransport,
84
+ } from "./transports/sendgrid.js";
85
+ export {
86
+
87
+ SESError,
88
+
89
+ SESTransport,
90
+ } from "./transports/ses.js";
91
+ export {
92
+
93
+ SMTPTransport,
94
+ } from "./transports/smtp.js";
95
+
96
+ export {
97
+
98
+ parseBrevoWebhook,
99
+
100
+ parseMailgunWebhook,
101
+
102
+ parsePostmarkWebhook,
103
+
104
+ parseResendWebhook,
105
+
106
+ parseSendGridWebhook,
107
+
108
+ parseSesWebhook,
109
+
110
+ verifyMailgunPayload,
111
+
112
+ verifyMailgunSignature,
113
+
114
+ verifyResendSignature,
115
+ } from "./webhooks.js";
@@ -0,0 +1,17 @@
1
+ import type { BulkSendOptions, BulkSendResult, Mailer, MailOptions, MailPlugin, SendResult, Transport, TransportMailerOptions, VerifyResult } from "./core/types.js";
2
+ export type { TransportMailerOptions };
3
+ /**
4
+ * Create a mailer that wraps a custom {@link Transport} (HTTP API, preview, retry, etc.).
5
+ */
6
+ export declare function createMailer(options: TransportMailerOptions): Promise<Mailer>;
7
+ /** Internal mailer implementation shared with the full `sently` entry. */
8
+ export declare class MailerImpl implements Mailer {
9
+ private readonly transport;
10
+ private readonly plugins;
11
+ constructor(transport: Transport, plugins?: MailPlugin[]);
12
+ send(options: MailOptions): Promise<SendResult>;
13
+ private processMessage;
14
+ sendBulk(messages: MailOptions[], options?: BulkSendOptions): Promise<BulkSendResult>;
15
+ verify(): Promise<VerifyResult>;
16
+ close(): Promise<void>;
17
+ }
package/dist/mailer.js ADDED
@@ -0,0 +1,3 @@
1
+ import{r as a,s as b}from"./chunk-pjzsvsm6.js";import"./chunk-ntqmn6pe.js";import"./chunk-rhade81v.js";export{a as createMailer,b as MailerImpl};
2
+
3
+ //# debugId=87404C28F00AC40D64756E2164756E21
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "87404C28F00AC40D64756E2164756E21",
8
+ "names": []
9
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @module
3
+ * React Email plugin for sently.
4
+ * Renders a React element to HTML and plain text via `@react-email/render`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { reactPlugin } from "sently/react";
9
+ * import { WelcomeEmail } from "./emails/welcome";
10
+ *
11
+ * const mailer = await createMailer({
12
+ * transport: new ResendTransport({ apiKey }),
13
+ * plugins: [reactPlugin()],
14
+ * });
15
+ *
16
+ * await mailer.send({
17
+ * from: "...",
18
+ * to: "...",
19
+ * subject: "Welcome!",
20
+ * react: WelcomeEmail({ name: "Ali" }),
21
+ * });
22
+ * ```
23
+ */
24
+ import type { MailOptions, MailPlugin } from "../core/types.js";
25
+ /** Mail options with a React element body (requires `react` peer). */
26
+ export interface ReactMailOptions extends Omit<MailOptions, "react"> {
27
+ /** React element rendered to html + text by the react plugin. */
28
+ react?: import("react").ReactElement;
29
+ }
30
+ /**
31
+ * Create a plugin that renders `options.react` to HTML and plain text.
32
+ * Explicit `html` / `text` values always win. The `react` field is stripped
33
+ * before options reach the transport.
34
+ */
35
+ export declare function reactPlugin(): MailPlugin;
@@ -0,0 +1,3 @@
1
+ import{K as B}from"../chunk-rhade81v.js";function F(){return async(j)=>{if(j.react===void 0||j.react===null)return j;let{render:k}=await import("@react-email/render"),q=j.html??await k(j.react,{pretty:!1}),z=j.text??await k(j.react,{plainText:!0}),{react:D,...A}=j;return{...A,html:q,text:z}}}export{F as reactPlugin};
2
+
3
+ //# debugId=06063038813DDE2664756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/plugins/react.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * @module\n * React Email plugin for sently.\n * Renders a React element to HTML and plain text via `@react-email/render`.\n *\n * @example\n * ```ts\n * import { reactPlugin } from \"sently/react\";\n * import { WelcomeEmail } from \"./emails/welcome\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [reactPlugin()],\n * });\n *\n * await mailer.send({\n * from: \"...\",\n * to: \"...\",\n * subject: \"Welcome!\",\n * react: WelcomeEmail({ name: \"Ali\" }),\n * });\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Mail options with a React element body (requires `react` peer). */\nexport interface ReactMailOptions extends Omit<MailOptions, \"react\"> {\n /** React element rendered to html + text by the react plugin. */\n react?: import(\"react\").ReactElement;\n}\n\n/**\n * Create a plugin that renders `options.react` to HTML and plain text.\n * Explicit `html` / `text` values always win. The `react` field is stripped\n * before options reach the transport.\n */\nexport function reactPlugin(): MailPlugin {\n return async (options: MailOptions): Promise<MailOptions> => {\n if (options.react === undefined || options.react === null) {\n return options;\n }\n\n const { render } = await import(\"@react-email/render\");\n\n const html =\n options.html ??\n (await render(options.react as Parameters<typeof render>[0], { pretty: false }));\n\n const text =\n options.text ??\n (await render(options.react as Parameters<typeof render>[0], { plainText: true }));\n\n const { react: _react, ...rest } = options;\n return { ...rest, html, text };\n };\n}\n"
6
+ ],
7
+ "mappings": "yCAoCO,SAAS,CAAW,EAAe,CACxC,MAAO,OAAO,IAA+C,CAC3D,GAAI,EAAQ,QAAU,QAAa,EAAQ,QAAU,KACnD,OAAO,EAGT,IAAQ,UAAW,KAAa,+BAE1B,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,OAAQ,EAAM,CAAC,EAE1E,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,UAAW,EAAK,CAAC,GAE1E,MAAO,KAAW,GAAS,EACnC,MAAO,IAAK,EAAM,OAAM,MAAK",
8
+ "debugId": "06063038813DDE2664756E2164756E21",
9
+ "names": []
10
+ }
@@ -1,29 +1,3 @@
1
- import"../chunk-v0bahtg2.js";
1
+ import"../chunk-rhade81v.js";function E(q,j){return q.replace(/\{\{(\w+)\}\}/g,(z,A)=>{let w=j[A];return w!==void 0&&w!==null?String(w):""})}function F(q){return(j)=>{if(!j.template)return j;let z=q.templates[j.template];if(!z)throw Error(`sently: template "${j.template}" not found`);let A=q.engine(z,j.data??{}),{template:w,data:D,...B}=j;return{...B,html:A}}}export{F as templatePlugin,E as simpleEngine};
2
2
 
3
- // src/plugins/template.ts
4
- function simpleEngine(template, data) {
5
- return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
6
- const val = data[key];
7
- return val !== undefined && val !== null ? String(val) : "";
8
- });
9
- }
10
- function templatePlugin(config) {
11
- return (options) => {
12
- if (!options.template) {
13
- return options;
14
- }
15
- const tmpl = config.templates[options.template];
16
- if (!tmpl) {
17
- throw new Error(`sently: template "${options.template}" not found`);
18
- }
19
- const html = config.engine(tmpl, options.data ?? {});
20
- const { template: _template, data: _data, ...rest } = options;
21
- return { ...rest, html };
22
- };
23
- }
24
- export {
25
- templatePlugin,
26
- simpleEngine
27
- };
28
-
29
- //# debugId=BF4AF728A3AA093064756E2164756E21
3
+ //# debugId=094B3D1AC9B011AB64756E2164756E21
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * @module\n * Template plugin for sently.\n * Renders HTML email templates using a pluggable engine.\n * Built-in: simple {{variable}} interpolation (zero dependencies).\n * Bring your own engine: pass any render function.\n *\n * @example\n * ```ts\n * import { templatePlugin, simpleEngine } from 'sently/plugins/template'\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [\n * templatePlugin({\n * engine: simpleEngine,\n * templates: {\n * welcome: '<h1>Hello, {{name}}!</h1>',\n * reset: '<p>Reset link: {{link}}</p>',\n * },\n * }),\n * ],\n * })\n *\n * await mailer.send({\n * from: '...',\n * to: '...',\n * subject: 'Welcome!',\n * template: 'welcome',\n * data: { name: 'Ali' },\n * })\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Renders a template string with the given data object. */\nexport type TemplateEngine = (template: string, data: Record<string, unknown>) => string;\n\n/** Configuration for the template plugin. */\nexport interface TemplatePluginConfig {\n /**\n * Rendering function. Use `simpleEngine` for zero-dep {{var}} interpolation,\n * or pass any function: (template, data) => string.\n */\n engine: TemplateEngine;\n /**\n * Map of template names to HTML strings.\n * Templates are loaded once at plugin creation time.\n */\n templates: Record<string, string>;\n}\n\n/**\n * Built-in zero-dependency template engine.\n * Replaces {{variable}} with the matching value from data.\n * Unknown variables are replaced with an empty string.\n */\nexport function simpleEngine(template: string, data: Record<string, unknown>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key: string) => {\n const val = data[key];\n return val !== undefined && val !== null ? String(val) : \"\";\n });\n}\n\n/**\n * Create a template plugin from the given config.\n * The plugin reads options.template and options.data, renders the HTML,\n * sets options.html, and removes template/data before passing to the next plugin.\n */\nexport function templatePlugin(config: TemplatePluginConfig): MailPlugin {\n return (options: MailOptions): MailOptions => {\n if (!options.template) {\n return options;\n }\n\n const tmpl = config.templates[options.template];\n if (!tmpl) {\n throw new Error(`sently: template \"${options.template}\" not found`);\n }\n\n const html = config.engine(tmpl, options.data ?? {});\n const { template: _template, data: _data, ...rest } = options;\n return { ...rest, html };\n };\n}\n"
6
6
  ],
7
- "mappings": ";;;AAyDO,SAAS,YAAY,CAAC,UAAkB,MAAuC;AAAA,EACpF,OAAO,SAAS,QAAQ,kBAAkB,CAAC,GAAG,QAAgB;AAAA,IAC5D,MAAM,MAAM,KAAK;AAAA,IACjB,OAAO,QAAQ,aAAa,QAAQ,OAAO,OAAO,GAAG,IAAI;AAAA,GAC1D;AAAA;AAQI,SAAS,cAAc,CAAC,QAA0C;AAAA,EACvE,OAAO,CAAC,YAAsC;AAAA,IAC5C,IAAI,CAAC,QAAQ,UAAU;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,IAEA,MAAM,OAAO,OAAO,UAAU,QAAQ;AAAA,IACtC,IAAI,CAAC,MAAM;AAAA,MACT,MAAM,IAAI,MAAM,qBAAqB,QAAQ,qBAAqB;AAAA,IACpE;AAAA,IAEA,MAAM,OAAO,OAAO,OAAO,MAAM,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACnD,QAAQ,UAAU,WAAW,MAAM,UAAU,SAAS;AAAA,IACtD,OAAO,KAAK,MAAM,KAAK;AAAA;AAAA;",
8
- "debugId": "BF4AF728A3AA093064756E2164756E21",
7
+ "mappings": "6BAyDO,SAAS,CAAY,CAAC,EAAkB,EAAuC,CACpF,OAAO,EAAS,QAAQ,iBAAkB,CAAC,EAAG,IAAgB,CAC5D,IAAM,EAAM,EAAK,GACjB,OAAO,IAAQ,QAAa,IAAQ,KAAO,OAAO,CAAG,EAAI,GAC1D,EAQI,SAAS,CAAc,CAAC,EAA0C,CACvE,MAAO,CAAC,IAAsC,CAC5C,GAAI,CAAC,EAAQ,SACX,OAAO,EAGT,IAAM,EAAO,EAAO,UAAU,EAAQ,UACtC,GAAI,CAAC,EACH,MAAU,MAAM,qBAAqB,EAAQ,qBAAqB,EAGpE,IAAM,EAAO,EAAO,OAAO,EAAM,EAAQ,MAAQ,CAAC,CAAC,GAC3C,SAAU,EAAW,KAAM,KAAU,GAAS,EACtD,MAAO,IAAK,EAAM,MAAK",
8
+ "debugId": "094B3D1AC9B011AB64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -31,28 +31,6 @@ export interface SMTPPoolOptions {
31
31
  /** Injectable clock for rate limiting (testing). */
32
32
  now?: () => number;
33
33
  }
34
- /**
35
- * Token bucket rate limiter with lazy refill on acquire.
36
- */
37
- declare class RateLimiter {
38
- private readonly rateDelta;
39
- private readonly rateLimit;
40
- private readonly now;
41
- /** Remaining tokens in the current rate-limit window. */
42
- private tokens;
43
- /** Timestamp (ms) of the last token refill. */
44
- private lastRefill;
45
- /** Resolvers waiting for a token when the bucket is empty. */
46
- private waiters;
47
- /** Creates a rate limiter with the given burst size and window duration. */
48
- constructor(rateDelta: number, rateLimit: number, now?: () => number);
49
- /** Wait until a token is available, then consume one. */
50
- acquire(): Promise<void>;
51
- /** Wake waiters after the clock advances (for testing). */
52
- notify(): void;
53
- /** Refills tokens based on elapsed time and wakes waiting acquirers. */
54
- private refill;
55
- }
56
34
  /**
57
35
  * SMTP connection pool with optional rate limiting.
58
36
  */
@@ -101,4 +79,4 @@ export declare class SMTPPool implements Transport {
101
79
  private drainQueue;
102
80
  }
103
81
  /** @internal Exposed for deterministic rate limiter tests. */
104
- export { RateLimiter };
82
+ export { RateLimiter } from "../core/rate-limiter.js";
package/dist/pool/pool.js CHANGED
@@ -1,17 +1,3 @@
1
- import {
2
- RateLimiter,
3
- SMTPPool
4
- } from "../chunk-mp5c9bfd.js";
5
- import"../chunk-7fqv71z1.js";
6
- import"../chunk-ym3zzv8b.js";
7
- import"../chunk-x3szga4k.js";
8
- import"../chunk-tymfm441.js";
9
- import"../chunk-f4c9ttmr.js";
10
- import"../chunk-794hc3m4.js";
11
- import"../chunk-v0bahtg2.js";
12
- export {
13
- SMTPPool,
14
- RateLimiter
15
- };
1
+ import{b as B,c as Q,d as U,e as V}from"../chunk-nj3wmg69.js";import"../chunk-j5m37k1c.js";import{k as K}from"../chunk-nhec1319.js";import{l as N}from"../chunk-cvmk9h1r.js";import"../chunk-kdqxhybf.js";import{t as G}from"../chunk-ntqmn6pe.js";import"../chunk-m2njnxre.js";import"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";async function W(k){let q=B(k.config),w=await k.createAdapter();await w.connect(k.connectHost,q.port),await Q(w,q);let z=0,D=!0,F=Promise.resolve(),X=k.maxMessages;return{get idle(){return D},get messageCount(){return z},get usable(){return z<X},async send(H){let Y=async()=>{D=!1;try{let Z={...H,attachments:await N(H.attachments)},_=await K(Z,q.dkim),$=await U(w,_);return z+=1,$}finally{D=!0}},J=F.then(Y);return F=J.then(()=>{return},()=>{return}),J},async close(){await F,await V(w)}}}class b{config;maxConnections;maxMessages;createAdapterFn;rateLimiter;connections=[];queue=[];draining=!1;closed=!1;processChain=Promise.resolve();constructor(k,q){if(this.config=k,this.maxConnections=k.maxConnections??5,this.maxMessages=k.maxMessages??100,q?.createAdapter){let w=q.createAdapter;this.createAdapterFn=async()=>w()}else if(k.adapter)this.createAdapterFn=async()=>k.adapter;else throw Error("SMTPPool requires config.adapter or options.createAdapter");if(k.rateDelta!==void 0&&k.rateDelta>0)this.rateLimiter=new G(k.rateDelta,k.rateLimit??1000,q?.now);else this.rateLimiter=null}async send(k){if(this.draining)throw Error("SMTPPool is closing — no new messages accepted");if(this.closed)throw Error("SMTPPool is closed");if(this.rateLimiter)await this.rateLimiter.acquire();return new Promise((q,w)=>{this.queue.push({options:k,resolve:q,reject:w}),this.scheduleProcess()})}scheduleProcess(){this.processChain=this.processChain.then(()=>this.processQueue()).catch(()=>{return})}async verify(){try{let k=await this.spawnConnection();try{return{ok:!0,provider:"smtp-pool"}}finally{await k.close(),this.removeConnection(k)}}catch(k){return{ok:!1,provider:"smtp-pool",message:k instanceof Error?k.message:String(k)}}}async close(){this.draining=!0,await this.drainQueue(),await Promise.allSettled(this.connections.map((k)=>k.close())),this.connections.length=0,this.closed=!0}get connectionCount(){return this.connections.length}get queueSize(){return this.queue.length}async processQueue(){if(this.draining)return;while(this.queue.length>0){let k=this.connections.find((q)=>q.idle&&q.usable);if(k){let q=this.queue.shift();if(!q)break;try{let w=await k.send(q.options);if(q.resolve(w),!k.usable)await k.close(),this.removeConnection(k)}catch(w){q.reject(w),await k.close().catch(()=>{return}),this.removeConnection(k)}continue}if(this.connections.length<this.maxConnections){let q=this.queue.shift();if(!q)break;let w=await this.spawnConnection();try{let z=await w.send(q.options);if(q.resolve(z),!w.usable)await w.close(),this.removeConnection(w)}catch(z){q.reject(z),await w.close().catch(()=>{return}),this.removeConnection(w)}continue}break}}async spawnConnection(){let k=B(this.config),q=await W({config:this.config,maxMessages:this.maxMessages,connectHost:k.host,createAdapter:this.createAdapterFn});return this.connections.push(q),q}removeConnection(k){let q=this.connections.indexOf(k);if(q>=0)this.connections.splice(q,1)}async drainQueue(){while(this.queue.length>0||this.connections.some((k)=>!k.idle))if(await this.processQueue(),this.queue.length>0)await new Promise((k)=>setTimeout(k,10))}}export{b as SMTPPool,G as RateLimiter};
16
2
 
17
- //# debugId=C06866950F09705E64756E2164756E21
3
+ //# debugId=1FB3D48CB667401164756E2164756E21
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": [],
3
+ "sources": ["../src/pool/connection.ts", "../src/pool/pool.ts"],
4
4
  "sourcesContent": [
5
+ "import { buildMIME } from \"../core/mime.js\";\nimport type { MailOptions, SendResult, SMTPConfig } from \"../core/types.js\";\nimport { resolveAttachments } from \"../transports/resolve-attachments.js\";\nimport {\n closeSMTPSession,\n deliverSMTPMessage,\n openSMTPSession,\n resolveSMTPConfig,\n} from \"../transports/smtp.js\";\n\n/** A single pooled SMTP connection with a persistent session. */\nexport interface PooledConnection {\n /** Send one message over this connection. */\n send(options: MailOptions): Promise<SendResult>;\n /** Whether this connection is idle and available for work. */\n readonly idle: boolean;\n /** Number of messages sent on this connection. */\n readonly messageCount: number;\n /** Whether this connection can accept more messages before recycle. */\n readonly usable: boolean;\n /** Close the connection and end the SMTP session. */\n close(): Promise<void>;\n}\n\n/** Options for creating a pooled connection. */\nexport interface PooledConnectionOptions {\n /** SMTP configuration for the pooled session. */\n config: SMTPConfig;\n /** Maximum messages before this connection is recycled. */\n maxMessages: number;\n /** Hostname to connect to (may differ from config.host for direct MX). */\n connectHost: string;\n /** Factory that creates the socket adapter for this connection. */\n createAdapter: () => Promise<import(\"../core/types.js\").SocketAdapter>;\n}\n\n/**\n * Create a pooled SMTP connection with an open authenticated session.\n */\nexport async function createPooledConnection(\n options: PooledConnectionOptions,\n): Promise<PooledConnection> {\n const config = resolveSMTPConfig(options.config);\n const adapter = await options.createAdapter();\n await adapter.connect(options.connectHost, config.port);\n await openSMTPSession(adapter, config);\n\n let messageCount = 0;\n let idle = true;\n let sendChain: Promise<void> = Promise.resolve();\n\n const maxMessages = options.maxMessages;\n\n return {\n get idle(): boolean {\n return idle;\n },\n get messageCount(): number {\n return messageCount;\n },\n get usable(): boolean {\n return messageCount < maxMessages;\n },\n\n async send(mailOptions: MailOptions): Promise<SendResult> {\n const run = async (): Promise<SendResult> => {\n idle = false;\n try {\n const resolvedOptions = {\n ...mailOptions,\n attachments: await resolveAttachments(mailOptions.attachments),\n };\n const mime = await buildMIME(resolvedOptions, config.dkim);\n const result = await deliverSMTPMessage(adapter, mime);\n messageCount += 1;\n return result;\n } finally {\n idle = true;\n }\n };\n\n const resultPromise = sendChain.then(run);\n sendChain = resultPromise.then(\n () => undefined,\n () => undefined,\n );\n return resultPromise;\n },\n\n async close(): Promise<void> {\n await sendChain;\n await closeSMTPSession(adapter);\n },\n };\n}\n",
6
+ "/**\n * @module\n * SMTP connection pool with optional rate limiting.\n *\n * @example\n * ```ts\n * import { SMTPPool } from \"sently/pool\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n *\n * const pool = new SMTPPool({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * pool: true,\n * maxConnections: 5,\n * });\n *\n * await pool.send({\n * from: \"you@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Pooled send\",\n * });\n * ```\n */\n\nimport { RateLimiter } from \"../core/rate-limiter.js\";\nimport type {\n MailOptions,\n PoolConfig,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveSMTPConfig } from \"../transports/smtp.js\";\nimport { createPooledConnection, type PooledConnection } from \"./connection.js\";\n\n/** Options for {@link SMTPPool}. */\nexport interface SMTPPoolOptions {\n /** Factory for a new socket adapter per pooled connection. */\n createAdapter?: () => Promise<SocketAdapter> | SocketAdapter;\n /** Injectable clock for rate limiting (testing). */\n now?: () => number;\n}\n\ninterface QueueEntry {\n options: MailOptions;\n resolve: (result: SendResult) => void;\n reject: (error: unknown) => void;\n}\n\n/**\n * SMTP connection pool with optional rate limiting.\n */\nexport class SMTPPool implements Transport {\n /** Resolved SMTP and pool configuration. */\n private readonly config: SMTPConfig & PoolConfig;\n /** Maximum simultaneous pooled connections. */\n private readonly maxConnections: number;\n /** Maximum messages per connection before recycle. */\n private readonly maxMessages: number;\n /** Factory that creates a socket adapter for each new connection. */\n private readonly createAdapterFn: () => Promise<SocketAdapter>;\n /** Optional token-bucket rate limiter, or null when disabled. */\n private readonly rateLimiter: RateLimiter | null;\n /** Active pooled SMTP connections. */\n private readonly connections: PooledConnection[] = [];\n /** Pending send operations waiting for a connection. */\n private readonly queue: QueueEntry[] = [];\n /** True while {@link close} is draining; rejects new sends. */\n private draining = false;\n /** True after the pool has fully closed. */\n private closed = false;\n /** Serializes queue processing to avoid concurrent drain races. */\n private processChain: Promise<void> = Promise.resolve();\n\n /** Creates an SMTP connection pool. */\n constructor(config: SMTPConfig & PoolConfig, options?: SMTPPoolOptions) {\n this.config = config;\n this.maxConnections = config.maxConnections ?? 5;\n this.maxMessages = config.maxMessages ?? 100;\n\n if (options?.createAdapter) {\n const factory = options.createAdapter;\n this.createAdapterFn = async () => factory();\n } else if (config.adapter) {\n this.createAdapterFn = async () => config.adapter as SocketAdapter;\n } else {\n throw new Error(\"SMTPPool requires config.adapter or options.createAdapter\");\n }\n\n if (config.rateDelta !== undefined && config.rateDelta > 0) {\n this.rateLimiter = new RateLimiter(config.rateDelta, config.rateLimit ?? 1000, options?.now);\n } else {\n this.rateLimiter = null;\n }\n }\n\n /** Sends a message through the pool. */\n async send(options: MailOptions): Promise<SendResult> {\n if (this.draining) {\n throw new Error(\"SMTPPool is closing — no new messages accepted\");\n }\n if (this.closed) {\n throw new Error(\"SMTPPool is closed\");\n }\n if (this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n return new Promise<SendResult>((resolve, reject) => {\n this.queue.push({ options, resolve, reject });\n this.scheduleProcess();\n });\n }\n\n /** Schedules asynchronous processing of the send queue. */\n private scheduleProcess(): void {\n this.processChain = this.processChain.then(() => this.processQueue()).catch(() => undefined);\n }\n\n /** Verifies connectivity using a temporary connection. */\n async verify(): Promise<VerifyResult> {\n try {\n const conn = await this.spawnConnection();\n try {\n return { ok: true, provider: \"smtp-pool\" };\n } finally {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp-pool\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Drains the queue and closes all connections. */\n async close(): Promise<void> {\n this.draining = true;\n await this.drainQueue();\n await Promise.allSettled(this.connections.map((c) => c.close()));\n this.connections.length = 0;\n this.closed = true;\n }\n\n /** Current number of open pooled connections. */\n get connectionCount(): number {\n return this.connections.length;\n }\n\n /** Number of messages waiting in the send queue. */\n get queueSize(): number {\n return this.queue.length;\n }\n\n /** Dispatches queued messages to idle or newly spawned connections. */\n private async processQueue(): Promise<void> {\n if (this.draining) {\n return;\n }\n\n while (this.queue.length > 0) {\n const idleConn = this.connections.find((c) => c.idle && c.usable);\n if (idleConn) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n try {\n const result = await idleConn.send(entry.options);\n entry.resolve(result);\n if (!idleConn.usable) {\n await idleConn.close();\n this.removeConnection(idleConn);\n }\n } catch (err) {\n entry.reject(err);\n await idleConn.close().catch(() => undefined);\n this.removeConnection(idleConn);\n }\n continue;\n }\n\n if (this.connections.length < this.maxConnections) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n const conn = await this.spawnConnection();\n try {\n const result = await conn.send(entry.options);\n entry.resolve(result);\n if (!conn.usable) {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n entry.reject(err);\n await conn.close().catch(() => undefined);\n this.removeConnection(conn);\n }\n continue;\n }\n\n break;\n }\n }\n\n /** Opens a new authenticated pooled SMTP connection. */\n private async spawnConnection(): Promise<PooledConnection> {\n const resolved = resolveSMTPConfig(this.config);\n const conn = await createPooledConnection({\n config: this.config,\n maxMessages: this.maxMessages,\n connectHost: resolved.host,\n createAdapter: this.createAdapterFn,\n });\n this.connections.push(conn);\n return conn;\n }\n\n /** Removes a connection from the active pool list. */\n private removeConnection(conn: PooledConnection): void {\n const index = this.connections.indexOf(conn);\n if (index >= 0) {\n this.connections.splice(index, 1);\n }\n }\n\n /** Waits until the send queue and in-flight work are fully drained. */\n private async drainQueue(): Promise<void> {\n while (this.queue.length > 0 || this.connections.some((c) => !c.idle)) {\n await this.processQueue();\n if (this.queue.length > 0) {\n await new Promise((r) => setTimeout(r, 10));\n }\n }\n }\n}\n\n/** @internal Exposed for deterministic rate limiter tests. */\nexport { RateLimiter } from \"../core/rate-limiter.js\";\n"
5
7
  ],
6
- "mappings": "",
7
- "debugId": "C06866950F09705E64756E2164756E21",
8
+ "mappings": "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",
9
+ "debugId": "1FB3D48CB667401164756E2164756E21",
8
10
  "names": []
9
11
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @module
3
+ * React Email integration for sently — optional peer dependency.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { reactPlugin } from "sently/react";
8
+ * import { createMailer } from "sently/mailer";
9
+ * import { ResendTransport } from "sently/transports/resend";
10
+ *
11
+ * const mailer = await createMailer({
12
+ * transport: new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
13
+ * plugins: [reactPlugin()],
14
+ * });
15
+ * ```
16
+ */
17
+ export type { ReactMailOptions } from "./plugins/react.js";
18
+ export {
19
+ /** Plugin that renders `options.react` to HTML and plain text via `@react-email/render`. */
20
+ reactPlugin, } from "./plugins/react.js";
package/dist/react.js ADDED
@@ -0,0 +1,3 @@
1
+ import"./chunk-rhade81v.js";export{k as reactPlugin};
2
+
3
+ //# debugId=AB3248C3DD5B9EFC64756E2164756E21
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "AB3248C3DD5B9EFC64756E2164756E21",
8
+ "names": []
9
+ }
@@ -1,116 +1,3 @@
1
- import {
2
- extractEmails,
3
- parseAddresses,
4
- resolveAttachments
5
- } from "../chunk-f4c9ttmr.js";
6
- import {
7
- encodeBase64
8
- } from "../chunk-794hc3m4.js";
9
- import"../chunk-v0bahtg2.js";
1
+ import{l as M}from"../chunk-cvmk9h1r.js";import{n as F,p as L}from"../chunk-kdqxhybf.js";import{G as K}from"../chunk-32dj0v9g.js";import"../chunk-rhade81v.js";class N extends Error{statusCode;code;constructor(q,w,C){super(q);this.statusCode=w;this.code=C;this.name="BrevoError"}}function H(q){return F(q).map((w)=>({email:w.address,...w.name?{name:w.name}:{}}))}class R{apiKey;constructor(q){this.apiKey=q.apiKey}async send(q){let w=await M(q.attachments),C=F(q.from)[0],Q={sender:C?{email:C.address,...C.name?{name:C.name}:{}}:{email:""},to:H(q.to),subject:q.subject,...q.cc?{cc:H(q.cc)}:{},...q.bcc?{bcc:H(q.bcc)}:{},...q.replyTo?{replyTo:(()=>{let z=F(q.replyTo)[0];return z?{email:z.address,...z.name?{name:z.name}:{}}:void 0})()}:{},...q.html?{htmlContent:q.html}:{},...q.text?{textContent:q.text}:{},...w.length>0?{attachment:w.map((z)=>({name:z.filename,content:z.content instanceof Uint8Array?K(z.content).replace(/\r\n/g,""):z.content}))}:{}},G=await fetch("https://api.brevo.com/v3/smtp/email",{method:"POST",headers:{"api-key":this.apiKey,"Content-Type":"application/json"},body:JSON.stringify(Q)}),D=await G.json();if(!G.ok)throw new N(D.message??"Brevo API error",G.status,D.code??"");let J=L(q.to);return{messageId:D.messageId??"",accepted:J,rejected:[],response:D.messageId??"sent",envelope:{from:C?.address??"",to:J}}}async verify(){try{let q=await fetch("https://api.brevo.com/v3/account",{headers:{"api-key":this.apiKey}}),w=await q.json();if(!q.ok)return{ok:!1,provider:"brevo",message:w.message??`HTTP ${q.status}`};return{ok:!0,provider:"brevo",...w.companyName?{message:w.companyName}:{}}}catch(q){return{ok:!1,provider:"brevo",message:q instanceof Error?q.message:String(q)}}}}export{R as BrevoTransport,N as BrevoError};
10
2
 
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
- async verify() {
83
- try {
84
- const response = await fetch("https://api.brevo.com/v3/account", {
85
- headers: {
86
- "api-key": this.apiKey
87
- }
88
- });
89
- const payload = await response.json();
90
- if (!response.ok) {
91
- return {
92
- ok: false,
93
- provider: "brevo",
94
- message: payload.message ?? `HTTP ${response.status}`
95
- };
96
- }
97
- return {
98
- ok: true,
99
- provider: "brevo",
100
- ...payload.companyName ? { message: payload.companyName } : {}
101
- };
102
- } catch (err) {
103
- return {
104
- ok: false,
105
- provider: "brevo",
106
- message: err instanceof Error ? err.message : String(err)
107
- };
108
- }
109
- }
110
- }
111
- export {
112
- BrevoTransport,
113
- BrevoError
114
- };
115
-
116
- //# debugId=C5824397C8CE442364756E2164756E21
3
+ //# debugId=3E1FA1A055C8575B64756E2164756E21
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends 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 /** Brevo API key used for Authorization. */\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: attachments.map((att) => ({\n name: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n messageId?: string;\n message?: string;\n code?: string;\n };\n\n if (!response.ok) {\n throw new BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "C5824397C8CE442364756E2164756E21",
7
+ "mappings": "8KA0BO,CAAM,KAAmB,AAAM,CAIlB,WACA,KAHlB,WAAW,CACT,EACgB,EACA,EAChB,CACA,MAAM,CAAO,EAHG,kBACA,YAGhB,KAAK,KAAO,aAEhB,CAEA,SAAS,CAAgB,CAAC,EAAmE,CAC3F,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC,KAAU,CAC1C,MAAO,EAAK,WACR,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CACzC,EAAE,EAMG,MAAM,CAAoC,CAE9B,OAGjB,WAAW,CAAC,EAAqB,CAC/B,KAAK,OAAS,EAAO,YAIjB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GAEpC,EAAgC,CACpC,OAAQ,EACJ,CAAE,MAAO,EAAK,WAAa,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CAAG,EACjE,CAAE,MAAO,EAAG,EAChB,GAAI,EAAiB,EAAQ,EAAE,EAC/B,QAAS,EAAQ,WACb,EAAQ,GAAK,CAAE,GAAI,EAAiB,EAAQ,EAAE,CAAE,EAAI,CAAC,KACrD,EAAQ,IAAM,CAAE,IAAK,EAAiB,EAAQ,GAAG,CAAE,EAAI,CAAC,KACxD,EAAQ,QACR,CACE,SAAU,IAAM,CACd,IAAM,EAAQ,EAAe,EAAQ,OAA4B,EAAE,GACnE,OAAO,EACH,CAAE,MAAO,EAAM,WAAa,EAAM,KAAO,CAAE,KAAM,EAAM,IAAK,EAAI,CAAC,CAAG,EACpE,SACH,CACL,EACA,CAAC,KACD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAY,OAAS,EACrB,CACE,WAAY,EAAY,IAAI,CAAC,KAAS,CACpC,KAAM,EAAI,SACV,QACE,EAAI,mBAAmB,WACnB,EAAa,EAAI,OAAO,EAAE,QAAQ,QAAS,EAAE,EAC7C,EAAI,OACZ,EAAE,CACJ,EACA,CAAC,CACP,EAEM,EAAW,MAAM,MAAM,sCAAuC,CAClE,OAAQ,OACR,QAAS,CACP,UAAW,KAAK,OAChB,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAMrC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EACR,EAAQ,SAAW,kBACnB,EAAS,OACT,EAAQ,MAAQ,EAClB,EAGF,IAAM,EAAW,EAAc,EAAQ,EAAE,EACzC,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,WAAa,OAC/B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACN,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,mCAAoC,CAC/D,QAAS,CACP,UAAW,KAAK,MAClB,CACF,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,EAAQ,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CACL,GAAI,GACJ,SAAU,WACN,EAAQ,YAAc,CAAE,QAAS,EAAQ,WAAY,EAAI,CAAC,CAChE,EACA,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
8
+ "debugId": "3E1FA1A055C8575B64756E2164756E21",
9
9
  "names": []
10
10
  }