@geekmidas/emailkit 10.0.0-alpha.78 → 10.0.0-alpha.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +45 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -0
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.mts +6 -0
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +45 -2
- package/dist/client.mjs.map +1 -1
- package/package.json +3 -1
package/dist/client.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
24
25
|
let _react_email_components = require("@react-email/components");
|
|
25
26
|
let nodemailer = require("nodemailer");
|
|
26
27
|
nodemailer = __toESM(nodemailer, 1);
|
|
@@ -32,7 +33,15 @@ var SMTPClient = class {
|
|
|
32
33
|
this.config = config;
|
|
33
34
|
this.transporter = nodemailer.default.createTransport(config.smtp);
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Send one message. An `email.send` span covers it, through the global
|
|
38
|
+
* OpenTelemetry tracer: the transport and how many were accepted or
|
|
39
|
+
* rejected, never an address, subject or body.
|
|
40
|
+
*/
|
|
35
41
|
async send(options) {
|
|
42
|
+
return traceSend({ "email.recipients": recipients(options) }, () => this.deliver(options));
|
|
43
|
+
}
|
|
44
|
+
async deliver(options) {
|
|
36
45
|
const mailOptions = {
|
|
37
46
|
...this.config.defaults,
|
|
38
47
|
...options
|
|
@@ -52,10 +61,13 @@ var SMTPClient = class {
|
|
|
52
61
|
if (!Component) throw new Error(`Template "${String(template)}" not found`);
|
|
53
62
|
const element = Component(options.props);
|
|
54
63
|
const html = await (0, _react_email_components.render)(element);
|
|
55
|
-
return
|
|
64
|
+
return traceSend({
|
|
65
|
+
"email.template": String(template),
|
|
66
|
+
"email.recipients": recipients(options)
|
|
67
|
+
}, () => this.deliver({
|
|
56
68
|
...options,
|
|
57
69
|
html
|
|
58
|
-
});
|
|
70
|
+
}));
|
|
59
71
|
}
|
|
60
72
|
async verify() {
|
|
61
73
|
try {
|
|
@@ -75,6 +87,37 @@ var SMTPClient = class {
|
|
|
75
87
|
function createEmailClient(config) {
|
|
76
88
|
return new SMTPClient(config);
|
|
77
89
|
}
|
|
90
|
+
/** How many addresses a message is for: to, cc and bcc. */
|
|
91
|
+
function recipients(options) {
|
|
92
|
+
return count(options.to) + count(options.cc) + count(options.bcc);
|
|
93
|
+
}
|
|
94
|
+
function count(value) {
|
|
95
|
+
if (!value) return 0;
|
|
96
|
+
return Array.isArray(value) ? value.length : 1;
|
|
97
|
+
}
|
|
98
|
+
async function traceSend(attributes, send) {
|
|
99
|
+
const span = _opentelemetry_api.trace.getTracer("@geekmidas/emailkit").startSpan("email.send", {
|
|
100
|
+
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
101
|
+
attributes: {
|
|
102
|
+
"email.transport": "smtp",
|
|
103
|
+
...attributes
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
const result = await send();
|
|
108
|
+
if (result.accepted.length || result.rejected.length) span.setAttributes({
|
|
109
|
+
"email.accepted": count(result.accepted),
|
|
110
|
+
"email.rejected": count(result.rejected)
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof Error) span.recordException(error);
|
|
115
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
116
|
+
throw error;
|
|
117
|
+
} finally {
|
|
118
|
+
span.end();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
78
121
|
//#endregion
|
|
79
122
|
exports.SMTPClient = SMTPClient;
|
|
80
123
|
exports.createEmailClient = createEmailClient;
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["render"],"sources":["../src/client.ts"],"sourcesContent":["import { 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\tasync send(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
|
|
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"}
|
package/dist/client.d.cts
CHANGED
|
@@ -4,7 +4,13 @@ export declare class SMTPClient<T extends TemplateRecord> implements EmailClient
|
|
|
4
4
|
private transporter;
|
|
5
5
|
private config;
|
|
6
6
|
constructor(config: EmailClientConfig<T>);
|
|
7
|
+
/**
|
|
8
|
+
* Send one message. An `email.send` span covers it, through the global
|
|
9
|
+
* OpenTelemetry tracer: the transport and how many were accepted or
|
|
10
|
+
* rejected, never an address, subject or body.
|
|
11
|
+
*/
|
|
7
12
|
send(options: SendOptions): Promise<SendResult>;
|
|
13
|
+
private deliver;
|
|
8
14
|
sendTemplate<K extends TemplateNames<T>>(template: K, options: Omit<SendOptions, 'template'> & {
|
|
9
15
|
props: TemplatePropsFor<T, K>;
|
|
10
16
|
}): Promise<SendResult>;
|
package/dist/client.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;
|
|
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"}
|
package/dist/client.d.mts
CHANGED
|
@@ -4,7 +4,13 @@ export declare class SMTPClient<T extends TemplateRecord> implements EmailClient
|
|
|
4
4
|
private transporter;
|
|
5
5
|
private config;
|
|
6
6
|
constructor(config: EmailClientConfig<T>);
|
|
7
|
+
/**
|
|
8
|
+
* Send one message. An `email.send` span covers it, through the global
|
|
9
|
+
* OpenTelemetry tracer: the transport and how many were accepted or
|
|
10
|
+
* rejected, never an address, subject or body.
|
|
11
|
+
*/
|
|
7
12
|
send(options: SendOptions): Promise<SendResult>;
|
|
13
|
+
private deliver;
|
|
8
14
|
sendTemplate<K extends TemplateNames<T>>(template: K, options: Omit<SendOptions, 'template'> & {
|
|
9
15
|
props: TemplatePropsFor<T, K>;
|
|
10
16
|
}): Promise<SendResult>;
|
package/dist/client.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;
|
|
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"}
|
package/dist/client.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
1
2
|
import { render } from "@react-email/components";
|
|
2
3
|
import nodemailer from "nodemailer";
|
|
3
4
|
//#region src/client.ts
|
|
@@ -8,7 +9,15 @@ var SMTPClient = class {
|
|
|
8
9
|
this.config = config;
|
|
9
10
|
this.transporter = nodemailer.createTransport(config.smtp);
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Send one message. An `email.send` span covers it, through the global
|
|
14
|
+
* OpenTelemetry tracer: the transport and how many were accepted or
|
|
15
|
+
* rejected, never an address, subject or body.
|
|
16
|
+
*/
|
|
11
17
|
async send(options) {
|
|
18
|
+
return traceSend({ "email.recipients": recipients(options) }, () => this.deliver(options));
|
|
19
|
+
}
|
|
20
|
+
async deliver(options) {
|
|
12
21
|
const mailOptions = {
|
|
13
22
|
...this.config.defaults,
|
|
14
23
|
...options
|
|
@@ -28,10 +37,13 @@ var SMTPClient = class {
|
|
|
28
37
|
if (!Component) throw new Error(`Template "${String(template)}" not found`);
|
|
29
38
|
const element = Component(options.props);
|
|
30
39
|
const html = await render(element);
|
|
31
|
-
return
|
|
40
|
+
return traceSend({
|
|
41
|
+
"email.template": String(template),
|
|
42
|
+
"email.recipients": recipients(options)
|
|
43
|
+
}, () => this.deliver({
|
|
32
44
|
...options,
|
|
33
45
|
html
|
|
34
|
-
});
|
|
46
|
+
}));
|
|
35
47
|
}
|
|
36
48
|
async verify() {
|
|
37
49
|
try {
|
|
@@ -51,6 +63,37 @@ var SMTPClient = class {
|
|
|
51
63
|
function createEmailClient(config) {
|
|
52
64
|
return new SMTPClient(config);
|
|
53
65
|
}
|
|
66
|
+
/** How many addresses a message is for: to, cc and bcc. */
|
|
67
|
+
function recipients(options) {
|
|
68
|
+
return count(options.to) + count(options.cc) + count(options.bcc);
|
|
69
|
+
}
|
|
70
|
+
function count(value) {
|
|
71
|
+
if (!value) return 0;
|
|
72
|
+
return Array.isArray(value) ? value.length : 1;
|
|
73
|
+
}
|
|
74
|
+
async function traceSend(attributes, send) {
|
|
75
|
+
const span = trace.getTracer("@geekmidas/emailkit").startSpan("email.send", {
|
|
76
|
+
kind: SpanKind.CLIENT,
|
|
77
|
+
attributes: {
|
|
78
|
+
"email.transport": "smtp",
|
|
79
|
+
...attributes
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const result = await send();
|
|
84
|
+
if (result.accepted.length || result.rejected.length) span.setAttributes({
|
|
85
|
+
"email.accepted": count(result.accepted),
|
|
86
|
+
"email.rejected": count(result.rejected)
|
|
87
|
+
});
|
|
88
|
+
return result;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof Error) span.recordException(error);
|
|
91
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
92
|
+
throw error;
|
|
93
|
+
} finally {
|
|
94
|
+
span.end();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
54
97
|
//#endregion
|
|
55
98
|
export { SMTPClient, createEmailClient };
|
|
56
99
|
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { 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\tasync send(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
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/emailkit",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.80",
|
|
4
4
|
"description": "Type-safe email client with SMTP support and React templates",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"@opentelemetry/api": "~1.9.1",
|
|
41
42
|
"nodemailer": "~10.0.10"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
45
|
+
"@opentelemetry/sdk-trace-node": "~2.11.0",
|
|
44
46
|
"@types/nodemailer": "~8.0.2",
|
|
45
47
|
"@types/react": "~19.3.0",
|
|
46
48
|
"@types/react-dom": "~19.3.0",
|