@geekmidas/emailkit 10.0.0-alpha.141 → 10.0.0-alpha.142

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.
@@ -0,0 +1,48 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_async_hooks = require("node:async_hooks");
3
+ //#region src/autoSubmitted.ts
4
+ /**
5
+ * RFC 3834's `Auto-Submitted` header: what stops two automatic responders
6
+ * answering each other forever.
7
+ *
8
+ * Mail an app sends while it is handling mail it received is marked
9
+ * `auto-replied`, and mail that arrives already marked is not handled. Both
10
+ * sides are here, so the sender and the receiver read the header the same way.
11
+ *
12
+ * Which sends are replies is a property of where they happen, not of the call,
13
+ * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,
14
+ * however deep, sends marked mail. The storage lives on `globalThis` so a copy
15
+ * of this module loaded as CJS and another loaded as ESM share it.
16
+ */
17
+ const AUTO_SUBMITTED_HEADER = "Auto-Submitted";
18
+ const STORE = Symbol.for("@geekmidas/emailkit/autoSubmitted");
19
+ function storage() {
20
+ const scope = globalThis;
21
+ scope[STORE] ??= new node_async_hooks.AsyncLocalStorage();
22
+ return scope[STORE];
23
+ }
24
+ /** Runs `fn` so that every message it sends is marked `auto-replied`. */
25
+ function autoReplying(fn) {
26
+ return storage().run("auto-replied", fn);
27
+ }
28
+ /** What mail sent from here should be marked, if anything. */
29
+ function currentAutoSubmitted() {
30
+ return storage().getStore();
31
+ }
32
+ /**
33
+ * Whether a message was sent automatically: its `Auto-Submitted` header is
34
+ * present and anything but `no`. Parameters after `;` are ignored, and so is
35
+ * case, as the RFC says.
36
+ *
37
+ * `headers` is keyed by lower-case name, as `parseEmail` returns them.
38
+ */
39
+ function isAutoSubmitted(headers) {
40
+ return (headers["Auto-Submitted".toLowerCase()] ?? []).some((value) => value.split(";")[0].trim().toLowerCase() !== "no");
41
+ }
42
+ //#endregion
43
+ exports.AUTO_SUBMITTED_HEADER = AUTO_SUBMITTED_HEADER;
44
+ exports.autoReplying = autoReplying;
45
+ exports.currentAutoSubmitted = currentAutoSubmitted;
46
+ exports.isAutoSubmitted = isAutoSubmitted;
47
+
48
+ //# sourceMappingURL=autoSubmitted.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoSubmitted.cjs","names":["AsyncLocalStorage"],"sources":["../src/autoSubmitted.ts"],"sourcesContent":["/**\n * RFC 3834's `Auto-Submitted` header: what stops two automatic responders\n * answering each other forever.\n *\n * Mail an app sends while it is handling mail it received is marked\n * `auto-replied`, and mail that arrives already marked is not handled. Both\n * sides are here, so the sender and the receiver read the header the same way.\n *\n * Which sends are replies is a property of where they happen, not of the call,\n * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,\n * however deep, sends marked mail. The storage lives on `globalThis` so a copy\n * of this module loaded as CJS and another loaded as ESM share it.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport const AUTO_SUBMITTED_HEADER = 'Auto-Submitted';\n\n/** The values RFC 3834 defines; `no` is the only one meaning \"a person sent it\". */\nexport type AutoSubmitted = 'no' | 'auto-generated' | 'auto-replied';\n\nconst STORE = Symbol.for('@geekmidas/emailkit/autoSubmitted');\n\nfunction storage(): AsyncLocalStorage<AutoSubmitted> {\n\tconst scope = globalThis as unknown as Record<\n\t\tsymbol,\n\t\tAsyncLocalStorage<AutoSubmitted> | undefined\n\t>;\n\tscope[STORE] ??= new AsyncLocalStorage<AutoSubmitted>();\n\treturn scope[STORE];\n}\n\n/** Runs `fn` so that every message it sends is marked `auto-replied`. */\nexport function autoReplying<T>(fn: () => T): T {\n\treturn storage().run('auto-replied', fn);\n}\n\n/** What mail sent from here should be marked, if anything. */\nexport function currentAutoSubmitted(): AutoSubmitted | undefined {\n\treturn storage().getStore();\n}\n\n/**\n * Whether a message was sent automatically: its `Auto-Submitted` header is\n * present and anything but `no`. Parameters after `;` are ignored, and so is\n * case, as the RFC says.\n *\n * `headers` is keyed by lower-case name, as `parseEmail` returns them.\n */\nexport function isAutoSubmitted(\n\theaders: Readonly<Record<string, readonly string[] | undefined>>,\n): boolean {\n\treturn (headers[AUTO_SUBMITTED_HEADER.toLowerCase()] ?? []).some(\n\t\t(value) => value.split(';')[0]!.trim().toLowerCase() !== 'no',\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,MAAa,wBAAwB;AAKrC,MAAM,QAAQ,OAAO,IAAI,mCAAmC;AAE5D,SAAS,UAA4C;CACpD,MAAM,QAAQ;CAId,MAAM,WAAW,IAAIA,iBAAAA,kBAAiC;CACtD,OAAO,MAAM;AACd;;AAGA,SAAgB,aAAgB,IAAgB;CAC/C,OAAO,QAAQ,CAAC,CAAC,IAAI,gBAAgB,EAAE;AACxC;;AAGA,SAAgB,uBAAkD;CACjE,OAAO,QAAQ,CAAC,CAAC,SAAS;AAC3B;;;;;;;;AASA,SAAgB,gBACf,SACU;CACV,QAAQ,QAAA,iBAA8B,YAAY,MAAM,CAAC,EAAA,CAAG,MAC1D,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,CAAC,CAAC,YAAY,MAAM,IAC1D;AACD"}
@@ -0,0 +1,31 @@
1
+ //#region src/autoSubmitted.d.ts
2
+ /**
3
+ * RFC 3834's `Auto-Submitted` header: what stops two automatic responders
4
+ * answering each other forever.
5
+ *
6
+ * Mail an app sends while it is handling mail it received is marked
7
+ * `auto-replied`, and mail that arrives already marked is not handled. Both
8
+ * sides are here, so the sender and the receiver read the header the same way.
9
+ *
10
+ * Which sends are replies is a property of where they happen, not of the call,
11
+ * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,
12
+ * however deep, sends marked mail. The storage lives on `globalThis` so a copy
13
+ * of this module loaded as CJS and another loaded as ESM share it.
14
+ */
15
+ export declare const AUTO_SUBMITTED_HEADER = "Auto-Submitted";
16
+ /** The values RFC 3834 defines; `no` is the only one meaning "a person sent it". */
17
+ export type AutoSubmitted = 'no' | 'auto-generated' | 'auto-replied';
18
+ /** Runs `fn` so that every message it sends is marked `auto-replied`. */
19
+ export declare function autoReplying<T>(fn: () => T): T;
20
+ /** What mail sent from here should be marked, if anything. */
21
+ export declare function currentAutoSubmitted(): AutoSubmitted | undefined;
22
+ /**
23
+ * Whether a message was sent automatically: its `Auto-Submitted` header is
24
+ * present and anything but `no`. Parameters after `;` are ignored, and so is
25
+ * case, as the RFC says.
26
+ *
27
+ * `headers` is keyed by lower-case name, as `parseEmail` returns them.
28
+ */
29
+ export declare function isAutoSubmitted(headers: Readonly<Record<string, readonly string[] | undefined>>): boolean;
30
+ //#endregion
31
+ //# sourceMappingURL=autoSubmitted.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoSubmitted.d.cts","names":[],"sources":["../src/autoSubmitted.ts"],"mappings":";;;;;;;;;;;;;;qBAgBa;;YAGD;;wBAcI,aAAa,GAAG,UAAU,IAAI;;wBAK9B,wBAAwB;;;;;;;;wBAWxB,gBACf,SAAS,SAAS"}
@@ -0,0 +1,31 @@
1
+ //#region src/autoSubmitted.d.ts
2
+ /**
3
+ * RFC 3834's `Auto-Submitted` header: what stops two automatic responders
4
+ * answering each other forever.
5
+ *
6
+ * Mail an app sends while it is handling mail it received is marked
7
+ * `auto-replied`, and mail that arrives already marked is not handled. Both
8
+ * sides are here, so the sender and the receiver read the header the same way.
9
+ *
10
+ * Which sends are replies is a property of where they happen, not of the call,
11
+ * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,
12
+ * however deep, sends marked mail. The storage lives on `globalThis` so a copy
13
+ * of this module loaded as CJS and another loaded as ESM share it.
14
+ */
15
+ export declare const AUTO_SUBMITTED_HEADER = "Auto-Submitted";
16
+ /** The values RFC 3834 defines; `no` is the only one meaning "a person sent it". */
17
+ export type AutoSubmitted = 'no' | 'auto-generated' | 'auto-replied';
18
+ /** Runs `fn` so that every message it sends is marked `auto-replied`. */
19
+ export declare function autoReplying<T>(fn: () => T): T;
20
+ /** What mail sent from here should be marked, if anything. */
21
+ export declare function currentAutoSubmitted(): AutoSubmitted | undefined;
22
+ /**
23
+ * Whether a message was sent automatically: its `Auto-Submitted` header is
24
+ * present and anything but `no`. Parameters after `;` are ignored, and so is
25
+ * case, as the RFC says.
26
+ *
27
+ * `headers` is keyed by lower-case name, as `parseEmail` returns them.
28
+ */
29
+ export declare function isAutoSubmitted(headers: Readonly<Record<string, readonly string[] | undefined>>): boolean;
30
+ //#endregion
31
+ //# sourceMappingURL=autoSubmitted.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoSubmitted.d.mts","names":[],"sources":["../src/autoSubmitted.ts"],"mappings":";;;;;;;;;;;;;;qBAgBa;;YAGD;;wBAcI,aAAa,GAAG,UAAU,IAAI;;wBAK9B,wBAAwB;;;;;;;;wBAWxB,gBACf,SAAS,SAAS"}
@@ -0,0 +1,44 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/autoSubmitted.ts
3
+ /**
4
+ * RFC 3834's `Auto-Submitted` header: what stops two automatic responders
5
+ * answering each other forever.
6
+ *
7
+ * Mail an app sends while it is handling mail it received is marked
8
+ * `auto-replied`, and mail that arrives already marked is not handled. Both
9
+ * sides are here, so the sender and the receiver read the header the same way.
10
+ *
11
+ * Which sends are replies is a property of where they happen, not of the call,
12
+ * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,
13
+ * however deep, sends marked mail. The storage lives on `globalThis` so a copy
14
+ * of this module loaded as CJS and another loaded as ESM share it.
15
+ */
16
+ const AUTO_SUBMITTED_HEADER = "Auto-Submitted";
17
+ const STORE = Symbol.for("@geekmidas/emailkit/autoSubmitted");
18
+ function storage() {
19
+ const scope = globalThis;
20
+ scope[STORE] ??= new AsyncLocalStorage();
21
+ return scope[STORE];
22
+ }
23
+ /** Runs `fn` so that every message it sends is marked `auto-replied`. */
24
+ function autoReplying(fn) {
25
+ return storage().run("auto-replied", fn);
26
+ }
27
+ /** What mail sent from here should be marked, if anything. */
28
+ function currentAutoSubmitted() {
29
+ return storage().getStore();
30
+ }
31
+ /**
32
+ * Whether a message was sent automatically: its `Auto-Submitted` header is
33
+ * present and anything but `no`. Parameters after `;` are ignored, and so is
34
+ * case, as the RFC says.
35
+ *
36
+ * `headers` is keyed by lower-case name, as `parseEmail` returns them.
37
+ */
38
+ function isAutoSubmitted(headers) {
39
+ return (headers["Auto-Submitted".toLowerCase()] ?? []).some((value) => value.split(";")[0].trim().toLowerCase() !== "no");
40
+ }
41
+ //#endregion
42
+ export { AUTO_SUBMITTED_HEADER, autoReplying, currentAutoSubmitted, isAutoSubmitted };
43
+
44
+ //# sourceMappingURL=autoSubmitted.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoSubmitted.mjs","names":[],"sources":["../src/autoSubmitted.ts"],"sourcesContent":["/**\n * RFC 3834's `Auto-Submitted` header: what stops two automatic responders\n * answering each other forever.\n *\n * Mail an app sends while it is handling mail it received is marked\n * `auto-replied`, and mail that arrives already marked is not handled. Both\n * sides are here, so the sender and the receiver read the header the same way.\n *\n * Which sends are replies is a property of where they happen, not of the call,\n * so it is carried by an `AsyncLocalStorage`: everything a handler awaits,\n * however deep, sends marked mail. The storage lives on `globalThis` so a copy\n * of this module loaded as CJS and another loaded as ESM share it.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nexport const AUTO_SUBMITTED_HEADER = 'Auto-Submitted';\n\n/** The values RFC 3834 defines; `no` is the only one meaning \"a person sent it\". */\nexport type AutoSubmitted = 'no' | 'auto-generated' | 'auto-replied';\n\nconst STORE = Symbol.for('@geekmidas/emailkit/autoSubmitted');\n\nfunction storage(): AsyncLocalStorage<AutoSubmitted> {\n\tconst scope = globalThis as unknown as Record<\n\t\tsymbol,\n\t\tAsyncLocalStorage<AutoSubmitted> | undefined\n\t>;\n\tscope[STORE] ??= new AsyncLocalStorage<AutoSubmitted>();\n\treturn scope[STORE];\n}\n\n/** Runs `fn` so that every message it sends is marked `auto-replied`. */\nexport function autoReplying<T>(fn: () => T): T {\n\treturn storage().run('auto-replied', fn);\n}\n\n/** What mail sent from here should be marked, if anything. */\nexport function currentAutoSubmitted(): AutoSubmitted | undefined {\n\treturn storage().getStore();\n}\n\n/**\n * Whether a message was sent automatically: its `Auto-Submitted` header is\n * present and anything but `no`. Parameters after `;` are ignored, and so is\n * case, as the RFC says.\n *\n * `headers` is keyed by lower-case name, as `parseEmail` returns them.\n */\nexport function isAutoSubmitted(\n\theaders: Readonly<Record<string, readonly string[] | undefined>>,\n): boolean {\n\treturn (headers[AUTO_SUBMITTED_HEADER.toLowerCase()] ?? []).some(\n\t\t(value) => value.split(';')[0]!.trim().toLowerCase() !== 'no',\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,MAAa,wBAAwB;AAKrC,MAAM,QAAQ,OAAO,IAAI,mCAAmC;AAE5D,SAAS,UAA4C;CACpD,MAAM,QAAQ;CAId,MAAM,WAAW,IAAI,kBAAiC;CACtD,OAAO,MAAM;AACd;;AAGA,SAAgB,aAAgB,IAAgB;CAC/C,OAAO,QAAQ,CAAC,CAAC,IAAI,gBAAgB,EAAE;AACxC;;AAGA,SAAgB,uBAAkD;CACjE,OAAO,QAAQ,CAAC,CAAC,SAAS;AAC3B;;;;;;;;AASA,SAAgB,gBACf,SACU;CACV,QAAQ,QAAA,iBAA8B,YAAY,MAAM,CAAC,EAAA,CAAG,MAC1D,UAAU,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,CAAE,KAAK,CAAC,CAAC,YAAY,MAAM,IAC1D;AACD"}
package/dist/client.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
+ const require_autoSubmitted = require("./autoSubmitted.cjs");
3
4
  let _opentelemetry_api = require("@opentelemetry/api");
4
5
  let _react_email_components = require("@react-email/components");
5
6
  let nodemailer = require("nodemailer");
@@ -21,9 +22,11 @@ var SMTPClient = class {
21
22
  return traceSend({ "email.recipients": recipients(options) }, () => this.deliver(options));
22
23
  }
23
24
  async deliver(options) {
25
+ const marked = require_autoSubmitted.currentAutoSubmitted();
24
26
  const mailOptions = {
25
27
  ...this.config.defaults,
26
- ...options
28
+ ...options,
29
+ ...marked ? { headers: { [require_autoSubmitted.AUTO_SUBMITTED_HEADER]: marked } } : {}
27
30
  };
28
31
  if (!mailOptions.from) throw new Error("The \"from\" field is required in email options or defaults");
29
32
  if (!mailOptions.text && !mailOptions.html) throw new Error("Either text or html content must be provided");
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["render","trace","SpanKind","SpanStatusCode"],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\t/**\n\t * Send one message. An `email.send` span covers it, through the global\n\t * OpenTelemetry tracer: the transport and how many were accepted or\n\t * rejected, never an address, subject or body.\n\t */\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\treturn traceSend({ 'email.recipients': recipients(options) }, () =>\n\t\t\tthis.deliver(options),\n\t\t);\n\t}\n\n\tprivate async deliver(options: SendOptions): Promise<SendResult> {\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn traceSend(\n\t\t\t{\n\t\t\t\t'email.template': String(template),\n\t\t\t\t'email.recipients': recipients(options),\n\t\t\t},\n\t\t\t() => this.deliver({ ...options, html }),\n\t\t);\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n\n/** How many addresses a message is for: to, cc and bcc. */\nfunction recipients(options: {\n\tto?: unknown;\n\tcc?: unknown;\n\tbcc?: unknown;\n}): number {\n\treturn count(options.to) + count(options.cc) + count(options.bcc);\n}\n\nfunction count(value: unknown): number {\n\tif (!value) return 0;\n\treturn Array.isArray(value) ? value.length : 1;\n}\n\nasync function traceSend(\n\tattributes: Attributes,\n\tsend: () => Promise<SendResult>,\n): Promise<SendResult> {\n\tconst span = trace.getTracer('@geekmidas/emailkit').startSpan('email.send', {\n\t\tkind: SpanKind.CLIENT,\n\t\tattributes: { 'email.transport': 'smtp', ...attributes },\n\t});\n\ttry {\n\t\tconst result = await send();\n\t\t// What the server said, when the transport reports it.\n\t\tif (result.accepted.length || result.rejected.length) {\n\t\t\tspan.setAttributes({\n\t\t\t\t'email.accepted': count(result.accepted),\n\t\t\t\t'email.rejected': count(result.rejected),\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t} catch (error) {\n\t\tif (error instanceof Error) span.recordException(error);\n\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,aAAb,MAA4E;CAC3E;CACA;CAEA,YAAY,QAA8B;EACzC,KAAK,SAAS;EACd,KAAK,cAAc,WAAA,QAAW,gBAAgB,OAAO,IAAW;CACjE;;;;;;CAOA,MAAM,KAAK,SAA2C;EACrD,OAAO,UAAU,EAAE,oBAAoB,WAAW,OAAO,EAAE,SAC1D,KAAK,QAAQ,OAAO,CACrB;CACD;CAEA,MAAc,QAAQ,SAA2C;EAChE,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;EACJ;EAEA,IAAI,CAAC,YAAY,MAChB,MAAM,IAAI,MACT,6DACD;EAGD,IAAI,CAAC,YAAY,QAAQ,CAAC,YAAY,MACrC,MAAM,IAAI,MAAM,8CAA8C;EAG/D,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,WAAW;EAExD,OAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK;EAChB;CACD;CAEA,MAAM,aACL,UACA,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;EACxC,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,EAAE,YAAY;EAG3D,MAAM,UAAU,UAAU,QAAQ,KAAK;EACvC,MAAM,OAAO,OAAA,GAAMA,wBAAAA,OAAAA,CAAO,OAAO;EAEjC,OAAO,UACN;GACC,kBAAkB,OAAO,QAAQ;GACjC,oBAAoB,WAAW,OAAO;EACvC,SACM,KAAK,QAAQ;GAAE,GAAG;GAAS;EAAK,CAAC,CACxC;CACD;CAEA,MAAM,SAA2B;EAChC,IAAI;GACH,MAAM,KAAK,YAAY,OAAO;GAC9B,OAAO;EACR,QAAQ;GACP,OAAO;EACR;CACD;CAEA,MAAM,QAAuB;EAC5B,KAAK,YAAY,MAAM;CACxB;CAEA,mBAAuC;EACtC,OAAO,OAAO,KAAK,KAAK,OAAO,SAAS;CACzC;AACD;AAEA,SAAgB,kBACf,QACgB;CAChB,OAAO,IAAI,WAAW,MAAM;AAC7B;;AAGA,SAAS,WAAW,SAIT;CACV,OAAO,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,GAAG;AACjE;AAEA,SAAS,MAAM,OAAwB;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;AAC9C;AAEA,eAAe,UACd,YACA,MACsB;CACtB,MAAM,OAAOC,mBAAAA,MAAM,UAAU,qBAAqB,CAAC,CAAC,UAAU,cAAc;EAC3E,MAAMC,mBAAAA,SAAS;EACf,YAAY;GAAE,mBAAmB;GAAQ,GAAG;EAAW;CACxD,CAAC;CACD,IAAI;EACH,MAAM,SAAS,MAAM,KAAK;EAE1B,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,QAC7C,KAAK,cAAc;GAClB,kBAAkB,MAAM,OAAO,QAAQ;GACvC,kBAAkB,MAAM,OAAO,QAAQ;EACxC,CAAC;EAEF,OAAO;CACR,SAAS,OAAO;EACf,IAAI,iBAAiB,OAAO,KAAK,gBAAgB,KAAK;EACtD,KAAK,UAAU,EAAE,MAAMC,mBAAAA,eAAe,MAAM,CAAC;EAC7C,MAAM;CACP,UAAU;EACT,KAAK,IAAI;CACV;AACD"}
1
+ {"version":3,"file":"client.cjs","names":["currentAutoSubmitted","AUTO_SUBMITTED_HEADER","render","trace","SpanKind","SpanStatusCode"],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport { AUTO_SUBMITTED_HEADER, currentAutoSubmitted } from './autoSubmitted';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\t/**\n\t * Send one message. An `email.send` span covers it, through the global\n\t * OpenTelemetry tracer: the transport and how many were accepted or\n\t * rejected, never an address, subject or body.\n\t */\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\treturn traceSend({ 'email.recipients': recipients(options) }, () =>\n\t\t\tthis.deliver(options),\n\t\t);\n\t}\n\n\tprivate async deliver(options: SendOptions): Promise<SendResult> {\n\t\tconst marked = currentAutoSubmitted();\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t\t// A send inside an inbox handler is a reply to mail, whatever it says.\n\t\t\t...(marked ? { headers: { [AUTO_SUBMITTED_HEADER]: marked } } : {}),\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn traceSend(\n\t\t\t{\n\t\t\t\t'email.template': String(template),\n\t\t\t\t'email.recipients': recipients(options),\n\t\t\t},\n\t\t\t() => this.deliver({ ...options, html }),\n\t\t);\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n\n/** How many addresses a message is for: to, cc and bcc. */\nfunction recipients(options: {\n\tto?: unknown;\n\tcc?: unknown;\n\tbcc?: unknown;\n}): number {\n\treturn count(options.to) + count(options.cc) + count(options.bcc);\n}\n\nfunction count(value: unknown): number {\n\tif (!value) return 0;\n\treturn Array.isArray(value) ? value.length : 1;\n}\n\nasync function traceSend(\n\tattributes: Attributes,\n\tsend: () => Promise<SendResult>,\n): Promise<SendResult> {\n\tconst span = trace.getTracer('@geekmidas/emailkit').startSpan('email.send', {\n\t\tkind: SpanKind.CLIENT,\n\t\tattributes: { 'email.transport': 'smtp', ...attributes },\n\t});\n\ttry {\n\t\tconst result = await send();\n\t\t// What the server said, when the transport reports it.\n\t\tif (result.accepted.length || result.rejected.length) {\n\t\t\tspan.setAttributes({\n\t\t\t\t'email.accepted': count(result.accepted),\n\t\t\t\t'email.rejected': count(result.rejected),\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t} catch (error) {\n\t\tif (error instanceof Error) span.recordException(error);\n\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n"],"mappings":";;;;;;;;AAmBA,IAAa,aAAb,MAA4E;CAC3E;CACA;CAEA,YAAY,QAA8B;EACzC,KAAK,SAAS;EACd,KAAK,cAAc,WAAA,QAAW,gBAAgB,OAAO,IAAW;CACjE;;;;;;CAOA,MAAM,KAAK,SAA2C;EACrD,OAAO,UAAU,EAAE,oBAAoB,WAAW,OAAO,EAAE,SAC1D,KAAK,QAAQ,OAAO,CACrB;CACD;CAEA,MAAc,QAAQ,SAA2C;EAChE,MAAM,SAASA,sBAAAA,qBAAqB;EACpC,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;GAEH,GAAI,SAAS,EAAE,SAAS,GAAGC,sBAAAA,wBAAwB,OAAO,EAAE,IAAI,CAAC;EAClE;EAEA,IAAI,CAAC,YAAY,MAChB,MAAM,IAAI,MACT,6DACD;EAGD,IAAI,CAAC,YAAY,QAAQ,CAAC,YAAY,MACrC,MAAM,IAAI,MAAM,8CAA8C;EAG/D,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,WAAW;EAExD,OAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK;EAChB;CACD;CAEA,MAAM,aACL,UACA,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;EACxC,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,EAAE,YAAY;EAG3D,MAAM,UAAU,UAAU,QAAQ,KAAK;EACvC,MAAM,OAAO,OAAA,GAAMC,wBAAAA,OAAAA,CAAO,OAAO;EAEjC,OAAO,UACN;GACC,kBAAkB,OAAO,QAAQ;GACjC,oBAAoB,WAAW,OAAO;EACvC,SACM,KAAK,QAAQ;GAAE,GAAG;GAAS;EAAK,CAAC,CACxC;CACD;CAEA,MAAM,SAA2B;EAChC,IAAI;GACH,MAAM,KAAK,YAAY,OAAO;GAC9B,OAAO;EACR,QAAQ;GACP,OAAO;EACR;CACD;CAEA,MAAM,QAAuB;EAC5B,KAAK,YAAY,MAAM;CACxB;CAEA,mBAAuC;EACtC,OAAO,OAAO,KAAK,KAAK,OAAO,SAAS;CACzC;AACD;AAEA,SAAgB,kBACf,QACgB;CAChB,OAAO,IAAI,WAAW,MAAM;AAC7B;;AAGA,SAAS,WAAW,SAIT;CACV,OAAO,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,GAAG;AACjE;AAEA,SAAS,MAAM,OAAwB;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;AAC9C;AAEA,eAAe,UACd,YACA,MACsB;CACtB,MAAM,OAAOC,mBAAAA,MAAM,UAAU,qBAAqB,CAAC,CAAC,UAAU,cAAc;EAC3E,MAAMC,mBAAAA,SAAS;EACf,YAAY;GAAE,mBAAmB;GAAQ,GAAG;EAAW;CACxD,CAAC;CACD,IAAI;EACH,MAAM,SAAS,MAAM,KAAK;EAE1B,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,QAC7C,KAAK,cAAc;GAClB,kBAAkB,MAAM,OAAO,QAAQ;GACvC,kBAAkB,MAAM,OAAO,QAAQ;EACxC,CAAC;EAEF,OAAO;CACR,SAAS,OAAO;EACf,IAAI,iBAAiB,OAAO,KAAK,gBAAgB,KAAK;EACtD,KAAK,UAAU,EAAE,MAAMC,mBAAAA,eAAe,MAAM,CAAC;EAC7C,MAAM;CACP,UAAU;EACT,KAAK,IAAI;CACV;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;qBAkBa,WAAW,UAAU,2BAA2B,YAAY;UAChE;UACA;EAER,YAAY,QAAQ,kBAAkB;;;;;;EAUhC,KAAK,SAAS,cAAc,QAAQ;UAM5B;EA0BR,aAAa,UAAU,cAAc,IAC1C,UAAU,GACV,SAAS,KAAK;IACb,OAAO,iBAAiB,GAAG;MAE1B,QAAQ;EAkBL,UAAU;EASV,SAAS;EAIf,oBAAoB,cAAc;;wBAKnB,kBAAkB,UAAU,gBAC3C,QAAQ,kBAAkB,KACxB,WAAW"}
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;qBAmBa,WAAW,UAAU,2BAA2B,YAAY;UAChE;UACA;EAER,YAAY,QAAQ,kBAAkB;;;;;;EAUhC,KAAK,SAAS,cAAc,QAAQ;UAM5B;EA6BR,aAAa,UAAU,cAAc,IAC1C,UAAU,GACV,SAAS,KAAK;IACb,OAAO,iBAAiB,GAAG;MAE1B,QAAQ;EAkBL,UAAU;EASV,SAAS;EAIf,oBAAoB,cAAc;;wBAKnB,kBAAkB,UAAU,gBAC3C,QAAQ,kBAAkB,KACxB,WAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;qBAkBa,WAAW,UAAU,2BAA2B,YAAY;UAChE;UACA;EAER,YAAY,QAAQ,kBAAkB;;;;;;EAUhC,KAAK,SAAS,cAAc,QAAQ;UAM5B;EA0BR,aAAa,UAAU,cAAc,IAC1C,UAAU,GACV,SAAS,KAAK;IACb,OAAO,iBAAiB,GAAG;MAE1B,QAAQ;EAkBL,UAAU;EASV,SAAS;EAIf,oBAAoB,cAAc;;wBAKnB,kBAAkB,UAAU,gBAC3C,QAAQ,kBAAkB,KACxB,WAAW"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;qBAmBa,WAAW,UAAU,2BAA2B,YAAY;UAChE;UACA;EAER,YAAY,QAAQ,kBAAkB;;;;;;EAUhC,KAAK,SAAS,cAAc,QAAQ;UAM5B;EA6BR,aAAa,UAAU,cAAc,IAC1C,UAAU,GACV,SAAS,KAAK;IACb,OAAO,iBAAiB,GAAG;MAE1B,QAAQ;EAkBL,UAAU;EASV,SAAS;EAIf,oBAAoB,cAAc;;wBAKnB,kBAAkB,UAAU,gBAC3C,QAAQ,kBAAkB,KACxB,WAAW"}
package/dist/client.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { AUTO_SUBMITTED_HEADER, currentAutoSubmitted } from "./autoSubmitted.mjs";
1
2
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
2
3
  import { render } from "@react-email/components";
3
4
  import nodemailer from "nodemailer";
@@ -18,9 +19,11 @@ var SMTPClient = class {
18
19
  return traceSend({ "email.recipients": recipients(options) }, () => this.deliver(options));
19
20
  }
20
21
  async deliver(options) {
22
+ const marked = currentAutoSubmitted();
21
23
  const mailOptions = {
22
24
  ...this.config.defaults,
23
- ...options
25
+ ...options,
26
+ ...marked ? { headers: { [AUTO_SUBMITTED_HEADER]: marked } } : {}
24
27
  };
25
28
  if (!mailOptions.from) throw new Error("The \"from\" field is required in email options or defaults");
26
29
  if (!mailOptions.text && !mailOptions.html) throw new Error("Either text or html content must be provided");
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\t/**\n\t * Send one message. An `email.send` span covers it, through the global\n\t * OpenTelemetry tracer: the transport and how many were accepted or\n\t * rejected, never an address, subject or body.\n\t */\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\treturn traceSend({ 'email.recipients': recipients(options) }, () =>\n\t\t\tthis.deliver(options),\n\t\t);\n\t}\n\n\tprivate async deliver(options: SendOptions): Promise<SendResult> {\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn traceSend(\n\t\t\t{\n\t\t\t\t'email.template': String(template),\n\t\t\t\t'email.recipients': recipients(options),\n\t\t\t},\n\t\t\t() => this.deliver({ ...options, html }),\n\t\t);\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n\n/** How many addresses a message is for: to, cc and bcc. */\nfunction recipients(options: {\n\tto?: unknown;\n\tcc?: unknown;\n\tbcc?: unknown;\n}): number {\n\treturn count(options.to) + count(options.cc) + count(options.bcc);\n}\n\nfunction count(value: unknown): number {\n\tif (!value) return 0;\n\treturn Array.isArray(value) ? value.length : 1;\n}\n\nasync function traceSend(\n\tattributes: Attributes,\n\tsend: () => Promise<SendResult>,\n): Promise<SendResult> {\n\tconst span = trace.getTracer('@geekmidas/emailkit').startSpan('email.send', {\n\t\tkind: SpanKind.CLIENT,\n\t\tattributes: { 'email.transport': 'smtp', ...attributes },\n\t});\n\ttry {\n\t\tconst result = await send();\n\t\t// What the server said, when the transport reports it.\n\t\tif (result.accepted.length || result.rejected.length) {\n\t\t\tspan.setAttributes({\n\t\t\t\t'email.accepted': count(result.accepted),\n\t\t\t\t'email.rejected': count(result.rejected),\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t} catch (error) {\n\t\tif (error instanceof Error) span.recordException(error);\n\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n"],"mappings":";;;;AAkBA,IAAa,aAAb,MAA4E;CAC3E;CACA;CAEA,YAAY,QAA8B;EACzC,KAAK,SAAS;EACd,KAAK,cAAc,WAAW,gBAAgB,OAAO,IAAW;CACjE;;;;;;CAOA,MAAM,KAAK,SAA2C;EACrD,OAAO,UAAU,EAAE,oBAAoB,WAAW,OAAO,EAAE,SAC1D,KAAK,QAAQ,OAAO,CACrB;CACD;CAEA,MAAc,QAAQ,SAA2C;EAChE,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;EACJ;EAEA,IAAI,CAAC,YAAY,MAChB,MAAM,IAAI,MACT,6DACD;EAGD,IAAI,CAAC,YAAY,QAAQ,CAAC,YAAY,MACrC,MAAM,IAAI,MAAM,8CAA8C;EAG/D,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,WAAW;EAExD,OAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK;EAChB;CACD;CAEA,MAAM,aACL,UACA,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;EACxC,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,EAAE,YAAY;EAG3D,MAAM,UAAU,UAAU,QAAQ,KAAK;EACvC,MAAM,OAAO,MAAM,OAAO,OAAO;EAEjC,OAAO,UACN;GACC,kBAAkB,OAAO,QAAQ;GACjC,oBAAoB,WAAW,OAAO;EACvC,SACM,KAAK,QAAQ;GAAE,GAAG;GAAS;EAAK,CAAC,CACxC;CACD;CAEA,MAAM,SAA2B;EAChC,IAAI;GACH,MAAM,KAAK,YAAY,OAAO;GAC9B,OAAO;EACR,QAAQ;GACP,OAAO;EACR;CACD;CAEA,MAAM,QAAuB;EAC5B,KAAK,YAAY,MAAM;CACxB;CAEA,mBAAuC;EACtC,OAAO,OAAO,KAAK,KAAK,OAAO,SAAS;CACzC;AACD;AAEA,SAAgB,kBACf,QACgB;CAChB,OAAO,IAAI,WAAW,MAAM;AAC7B;;AAGA,SAAS,WAAW,SAIT;CACV,OAAO,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,GAAG;AACjE;AAEA,SAAS,MAAM,OAAwB;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;AAC9C;AAEA,eAAe,UACd,YACA,MACsB;CACtB,MAAM,OAAO,MAAM,UAAU,qBAAqB,CAAC,CAAC,UAAU,cAAc;EAC3E,MAAM,SAAS;EACf,YAAY;GAAE,mBAAmB;GAAQ,GAAG;EAAW;CACxD,CAAC;CACD,IAAI;EACH,MAAM,SAAS,MAAM,KAAK;EAE1B,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,QAC7C,KAAK,cAAc;GAClB,kBAAkB,MAAM,OAAO,QAAQ;GACvC,kBAAkB,MAAM,OAAO,QAAQ;EACxC,CAAC;EAEF,OAAO;CACR,SAAS,OAAO;EACf,IAAI,iBAAiB,OAAO,KAAK,gBAAgB,KAAK;EACtD,KAAK,UAAU,EAAE,MAAM,eAAe,MAAM,CAAC;EAC7C,MAAM;CACP,UAAU;EACT,KAAK,IAAI;CACV;AACD"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n\ttype Attributes,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\nimport { render } from '@react-email/components';\nimport nodemailer, { type Transporter } from 'nodemailer';\nimport { AUTO_SUBMITTED_HEADER, currentAutoSubmitted } from './autoSubmitted';\nimport type {\n\tEmailClient,\n\tEmailClientConfig,\n\tSendOptions,\n\tSendResult,\n\tTemplateNames,\n\tTemplatePropsFor,\n\tTemplateRecord,\n} from './types';\n\nexport class SMTPClient<T extends TemplateRecord> implements EmailClient<T> {\n\tprivate transporter: Transporter;\n\tprivate config: EmailClientConfig<T>;\n\n\tconstructor(config: EmailClientConfig<T>) {\n\t\tthis.config = config;\n\t\tthis.transporter = nodemailer.createTransport(config.smtp as any);\n\t}\n\n\t/**\n\t * Send one message. An `email.send` span covers it, through the global\n\t * OpenTelemetry tracer: the transport and how many were accepted or\n\t * rejected, never an address, subject or body.\n\t */\n\tasync send(options: SendOptions): Promise<SendResult> {\n\t\treturn traceSend({ 'email.recipients': recipients(options) }, () =>\n\t\t\tthis.deliver(options),\n\t\t);\n\t}\n\n\tprivate async deliver(options: SendOptions): Promise<SendResult> {\n\t\tconst marked = currentAutoSubmitted();\n\t\tconst mailOptions = {\n\t\t\t...this.config.defaults,\n\t\t\t...options,\n\t\t\t// A send inside an inbox handler is a reply to mail, whatever it says.\n\t\t\t...(marked ? { headers: { [AUTO_SUBMITTED_HEADER]: marked } } : {}),\n\t\t};\n\n\t\tif (!mailOptions.from) {\n\t\t\tthrow new Error(\n\t\t\t\t'The \"from\" field is required in email options or defaults',\n\t\t\t);\n\t\t}\n\n\t\tif (!mailOptions.text && !mailOptions.html) {\n\t\t\tthrow new Error('Either text or html content must be provided');\n\t\t}\n\n\t\tconst info = await this.transporter.sendMail(mailOptions);\n\n\t\treturn {\n\t\t\tmessageId: info.messageId,\n\t\t\taccepted: info.accepted || [],\n\t\t\trejected: info.rejected || [],\n\t\t\tresponse: info.response,\n\t\t};\n\t}\n\n\tasync sendTemplate<K extends TemplateNames<T>>(\n\t\ttemplate: K,\n\t\toptions: Omit<SendOptions, 'template'> & {\n\t\t\tprops: TemplatePropsFor<T, K>;\n\t\t},\n\t): Promise<SendResult> {\n\t\tconst Component = this.config.templates[template];\n\t\tif (!Component) {\n\t\t\tthrow new Error(`Template \"${String(template)}\" not found`);\n\t\t}\n\n\t\tconst element = Component(options.props);\n\t\tconst html = await render(element);\n\n\t\treturn traceSend(\n\t\t\t{\n\t\t\t\t'email.template': String(template),\n\t\t\t\t'email.recipients': recipients(options),\n\t\t\t},\n\t\t\t() => this.deliver({ ...options, html }),\n\t\t);\n\t}\n\n\tasync verify(): Promise<boolean> {\n\t\ttry {\n\t\t\tawait this.transporter.verify();\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.transporter.close();\n\t}\n\n\tgetTemplateNames(): TemplateNames<T>[] {\n\t\treturn Object.keys(this.config.templates) as TemplateNames<T>[];\n\t}\n}\n\nexport function createEmailClient<T extends TemplateRecord>(\n\tconfig: EmailClientConfig<T>,\n): SMTPClient<T> {\n\treturn new SMTPClient(config);\n}\n\n/** How many addresses a message is for: to, cc and bcc. */\nfunction recipients(options: {\n\tto?: unknown;\n\tcc?: unknown;\n\tbcc?: unknown;\n}): number {\n\treturn count(options.to) + count(options.cc) + count(options.bcc);\n}\n\nfunction count(value: unknown): number {\n\tif (!value) return 0;\n\treturn Array.isArray(value) ? value.length : 1;\n}\n\nasync function traceSend(\n\tattributes: Attributes,\n\tsend: () => Promise<SendResult>,\n): Promise<SendResult> {\n\tconst span = trace.getTracer('@geekmidas/emailkit').startSpan('email.send', {\n\t\tkind: SpanKind.CLIENT,\n\t\tattributes: { 'email.transport': 'smtp', ...attributes },\n\t});\n\ttry {\n\t\tconst result = await send();\n\t\t// What the server said, when the transport reports it.\n\t\tif (result.accepted.length || result.rejected.length) {\n\t\t\tspan.setAttributes({\n\t\t\t\t'email.accepted': count(result.accepted),\n\t\t\t\t'email.rejected': count(result.rejected),\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t} catch (error) {\n\t\tif (error instanceof Error) span.recordException(error);\n\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n"],"mappings":";;;;;AAmBA,IAAa,aAAb,MAA4E;CAC3E;CACA;CAEA,YAAY,QAA8B;EACzC,KAAK,SAAS;EACd,KAAK,cAAc,WAAW,gBAAgB,OAAO,IAAW;CACjE;;;;;;CAOA,MAAM,KAAK,SAA2C;EACrD,OAAO,UAAU,EAAE,oBAAoB,WAAW,OAAO,EAAE,SAC1D,KAAK,QAAQ,OAAO,CACrB;CACD;CAEA,MAAc,QAAQ,SAA2C;EAChE,MAAM,SAAS,qBAAqB;EACpC,MAAM,cAAc;GACnB,GAAG,KAAK,OAAO;GACf,GAAG;GAEH,GAAI,SAAS,EAAE,SAAS,GAAG,wBAAwB,OAAO,EAAE,IAAI,CAAC;EAClE;EAEA,IAAI,CAAC,YAAY,MAChB,MAAM,IAAI,MACT,6DACD;EAGD,IAAI,CAAC,YAAY,QAAQ,CAAC,YAAY,MACrC,MAAM,IAAI,MAAM,8CAA8C;EAG/D,MAAM,OAAO,MAAM,KAAK,YAAY,SAAS,WAAW;EAExD,OAAO;GACN,WAAW,KAAK;GAChB,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK,YAAY,CAAC;GAC5B,UAAU,KAAK;EAChB;CACD;CAEA,MAAM,aACL,UACA,SAGsB;EACtB,MAAM,YAAY,KAAK,OAAO,UAAU;EACxC,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,aAAa,OAAO,QAAQ,EAAE,YAAY;EAG3D,MAAM,UAAU,UAAU,QAAQ,KAAK;EACvC,MAAM,OAAO,MAAM,OAAO,OAAO;EAEjC,OAAO,UACN;GACC,kBAAkB,OAAO,QAAQ;GACjC,oBAAoB,WAAW,OAAO;EACvC,SACM,KAAK,QAAQ;GAAE,GAAG;GAAS;EAAK,CAAC,CACxC;CACD;CAEA,MAAM,SAA2B;EAChC,IAAI;GACH,MAAM,KAAK,YAAY,OAAO;GAC9B,OAAO;EACR,QAAQ;GACP,OAAO;EACR;CACD;CAEA,MAAM,QAAuB;EAC5B,KAAK,YAAY,MAAM;CACxB;CAEA,mBAAuC;EACtC,OAAO,OAAO,KAAK,KAAK,OAAO,SAAS;CACzC;AACD;AAEA,SAAgB,kBACf,QACgB;CAChB,OAAO,IAAI,WAAW,MAAM;AAC7B;;AAGA,SAAS,WAAW,SAIT;CACV,OAAO,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,GAAG;AACjE;AAEA,SAAS,MAAM,OAAwB;CACtC,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS;AAC9C;AAEA,eAAe,UACd,YACA,MACsB;CACtB,MAAM,OAAO,MAAM,UAAU,qBAAqB,CAAC,CAAC,UAAU,cAAc;EAC3E,MAAM,SAAS;EACf,YAAY;GAAE,mBAAmB;GAAQ,GAAG;EAAW;CACxD,CAAC;CACD,IAAI;EACH,MAAM,SAAS,MAAM,KAAK;EAE1B,IAAI,OAAO,SAAS,UAAU,OAAO,SAAS,QAC7C,KAAK,cAAc;GAClB,kBAAkB,MAAM,OAAO,QAAQ;GACvC,kBAAkB,MAAM,OAAO,QAAQ;EACxC,CAAC;EAEF,OAAO;CACR,SAAS,OAAO;EACf,IAAI,iBAAiB,OAAO,KAAK,gBAAgB,KAAK;EACtD,KAAK,UAAU,EAAE,MAAM,eAAe,MAAM,CAAC;EAC7C,MAAM;CACP,UAAU;EACT,KAAK,IAAI;CACV;AACD"}
package/dist/index.cjs CHANGED
@@ -1,10 +1,15 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_autoSubmitted = require("./autoSubmitted.cjs");
2
3
  const require_client = require("./client.cjs");
3
4
  const require_errors = require("./errors.cjs");
4
5
  const require_url = require("./url.cjs");
6
+ exports.AUTO_SUBMITTED_HEADER = require_autoSubmitted.AUTO_SUBMITTED_HEADER;
5
7
  exports.EmailUrlError = require_errors.EmailUrlError;
6
8
  exports.MalformedEmailUrl = require_errors.MalformedEmailUrl;
7
9
  exports.SMTPClient = require_client.SMTPClient;
8
10
  exports.UnsupportedEmailScheme = require_errors.UnsupportedEmailScheme;
11
+ exports.autoReplying = require_autoSubmitted.autoReplying;
9
12
  exports.createEmailClient = require_client.createEmailClient;
13
+ exports.currentAutoSubmitted = require_autoSubmitted.currentAutoSubmitted;
14
+ exports.isAutoSubmitted = require_autoSubmitted.isAutoSubmitted;
10
15
  exports.parseEmailUrl = require_url.parseEmailUrl;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
+ import { AUTO_SUBMITTED_HEADER, AutoSubmitted, autoReplying, currentAutoSubmitted, isAutoSubmitted } from "./autoSubmitted.cjs";
1
2
  import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types.cjs";
2
3
  import { SMTPClient, createEmailClient } from "./client.cjs";
3
4
  import { EmailUrlError, MalformedEmailUrl, UnsupportedEmailScheme } from "./errors.cjs";
4
5
  import { parseEmailUrl } from "./url.cjs";
5
- export { type Attachment, type EmailClient, type EmailClientConfig, type EmailOptions, type EmailTemplate, EmailUrlError, MalformedEmailUrl, type PlainEmailOptions, SMTPClient, type SMTPConfig, type SendOptions, type SendResult, type TemplateNames, type TemplatePropsFor, type TemplateRecord, UnsupportedEmailScheme, createEmailClient, parseEmailUrl };
6
+ export { AUTO_SUBMITTED_HEADER, type Attachment, type AutoSubmitted, type EmailClient, type EmailClientConfig, type EmailOptions, type EmailTemplate, EmailUrlError, MalformedEmailUrl, type PlainEmailOptions, SMTPClient, type SMTPConfig, type SendOptions, type SendResult, type TemplateNames, type TemplatePropsFor, type TemplateRecord, UnsupportedEmailScheme, autoReplying, createEmailClient, currentAutoSubmitted, isAutoSubmitted, parseEmailUrl };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
+ import { AUTO_SUBMITTED_HEADER, AutoSubmitted, autoReplying, currentAutoSubmitted, isAutoSubmitted } from "./autoSubmitted.mjs";
1
2
  import { Attachment, EmailClient, EmailClientConfig, EmailOptions, EmailTemplate, PlainEmailOptions, SMTPConfig, SendOptions, SendResult, TemplateNames, TemplatePropsFor, TemplateRecord } from "./types.mjs";
2
3
  import { SMTPClient, createEmailClient } from "./client.mjs";
3
4
  import { EmailUrlError, MalformedEmailUrl, UnsupportedEmailScheme } from "./errors.mjs";
4
5
  import { parseEmailUrl } from "./url.mjs";
5
- export { type Attachment, type EmailClient, type EmailClientConfig, type EmailOptions, type EmailTemplate, EmailUrlError, MalformedEmailUrl, type PlainEmailOptions, SMTPClient, type SMTPConfig, type SendOptions, type SendResult, type TemplateNames, type TemplatePropsFor, type TemplateRecord, UnsupportedEmailScheme, createEmailClient, parseEmailUrl };
6
+ export { AUTO_SUBMITTED_HEADER, type Attachment, type AutoSubmitted, type EmailClient, type EmailClientConfig, type EmailOptions, type EmailTemplate, EmailUrlError, MalformedEmailUrl, type PlainEmailOptions, SMTPClient, type SMTPConfig, type SendOptions, type SendResult, type TemplateNames, type TemplatePropsFor, type TemplateRecord, UnsupportedEmailScheme, autoReplying, createEmailClient, currentAutoSubmitted, isAutoSubmitted, parseEmailUrl };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
+ import { AUTO_SUBMITTED_HEADER, autoReplying, currentAutoSubmitted, isAutoSubmitted } from "./autoSubmitted.mjs";
1
2
  import { SMTPClient, createEmailClient } from "./client.mjs";
2
3
  import { EmailUrlError, MalformedEmailUrl, UnsupportedEmailScheme } from "./errors.mjs";
3
4
  import { parseEmailUrl } from "./url.mjs";
4
- export { EmailUrlError, MalformedEmailUrl, SMTPClient, UnsupportedEmailScheme, createEmailClient, parseEmailUrl };
5
+ export { AUTO_SUBMITTED_HEADER, EmailUrlError, MalformedEmailUrl, SMTPClient, UnsupportedEmailScheme, autoReplying, createEmailClient, currentAutoSubmitted, isAutoSubmitted, parseEmailUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/emailkit",
3
- "version": "10.0.0-alpha.141",
3
+ "version": "10.0.0-alpha.142",
4
4
  "description": "Type-safe email client with SMTP support and React templates",
5
5
  "private": false,
6
6
  "type": "module",