@geekmidas/emailkit 10.0.0-alpha.14 → 10.0.0-alpha.141
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 +47 -25
- 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/dist/errors.d.cts.map +1 -1
- package/dist/errors.d.mts.map +1 -1
- package/dist/inbound.cjs +130 -0
- package/dist/inbound.cjs.map +1 -0
- package/dist/inbound.d.cts +95 -0
- package/dist/inbound.d.cts.map +1 -0
- package/dist/inbound.d.mts +95 -0
- package/dist/inbound.d.mts.map +1 -0
- package/dist/inbound.mjs +123 -0
- package/dist/inbound.mjs.map +1 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/package.json +13 -7
package/dist/client.cjs
CHANGED
|
@@ -1,29 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
-
key = keys[i];
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
-
get: ((k) => from[k]).bind(null, key),
|
|
14
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
|
-
value: mod,
|
|
21
|
-
enumerable: true
|
|
22
|
-
}) : target, mod));
|
|
23
|
-
//#endregion
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let _opentelemetry_api = require("@opentelemetry/api");
|
|
24
4
|
let _react_email_components = require("@react-email/components");
|
|
25
5
|
let nodemailer = require("nodemailer");
|
|
26
|
-
nodemailer = __toESM(nodemailer, 1);
|
|
6
|
+
nodemailer = require_rolldown_runtime.__toESM(nodemailer, 1);
|
|
27
7
|
//#region src/client.ts
|
|
28
8
|
var SMTPClient = class {
|
|
29
9
|
transporter;
|
|
@@ -32,7 +12,15 @@ var SMTPClient = class {
|
|
|
32
12
|
this.config = config;
|
|
33
13
|
this.transporter = nodemailer.default.createTransport(config.smtp);
|
|
34
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Send one message. An `email.send` span covers it, through the global
|
|
17
|
+
* OpenTelemetry tracer: the transport and how many were accepted or
|
|
18
|
+
* rejected, never an address, subject or body.
|
|
19
|
+
*/
|
|
35
20
|
async send(options) {
|
|
21
|
+
return traceSend({ "email.recipients": recipients(options) }, () => this.deliver(options));
|
|
22
|
+
}
|
|
23
|
+
async deliver(options) {
|
|
36
24
|
const mailOptions = {
|
|
37
25
|
...this.config.defaults,
|
|
38
26
|
...options
|
|
@@ -52,10 +40,13 @@ var SMTPClient = class {
|
|
|
52
40
|
if (!Component) throw new Error(`Template "${String(template)}" not found`);
|
|
53
41
|
const element = Component(options.props);
|
|
54
42
|
const html = await (0, _react_email_components.render)(element);
|
|
55
|
-
return
|
|
43
|
+
return traceSend({
|
|
44
|
+
"email.template": String(template),
|
|
45
|
+
"email.recipients": recipients(options)
|
|
46
|
+
}, () => this.deliver({
|
|
56
47
|
...options,
|
|
57
48
|
html
|
|
58
|
-
});
|
|
49
|
+
}));
|
|
59
50
|
}
|
|
60
51
|
async verify() {
|
|
61
52
|
try {
|
|
@@ -75,6 +66,37 @@ var SMTPClient = class {
|
|
|
75
66
|
function createEmailClient(config) {
|
|
76
67
|
return new SMTPClient(config);
|
|
77
68
|
}
|
|
69
|
+
/** How many addresses a message is for: to, cc and bcc. */
|
|
70
|
+
function recipients(options) {
|
|
71
|
+
return count(options.to) + count(options.cc) + count(options.bcc);
|
|
72
|
+
}
|
|
73
|
+
function count(value) {
|
|
74
|
+
if (!value) return 0;
|
|
75
|
+
return Array.isArray(value) ? value.length : 1;
|
|
76
|
+
}
|
|
77
|
+
async function traceSend(attributes, send) {
|
|
78
|
+
const span = _opentelemetry_api.trace.getTracer("@geekmidas/emailkit").startSpan("email.send", {
|
|
79
|
+
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
80
|
+
attributes: {
|
|
81
|
+
"email.transport": "smtp",
|
|
82
|
+
...attributes
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
try {
|
|
86
|
+
const result = await send();
|
|
87
|
+
if (result.accepted.length || result.rejected.length) span.setAttributes({
|
|
88
|
+
"email.accepted": count(result.accepted),
|
|
89
|
+
"email.rejected": count(result.rejected)
|
|
90
|
+
});
|
|
91
|
+
return result;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof Error) span.recordException(error);
|
|
94
|
+
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
95
|
+
throw error;
|
|
96
|
+
} finally {
|
|
97
|
+
span.end();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
78
100
|
//#endregion
|
|
79
101
|
exports.SMTPClient = SMTPClient;
|
|
80
102
|
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/dist/errors.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.cts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;8BAcsB,sBAAsB;;WAElC;
|
|
1
|
+
{"version":3,"file":"errors.d.cts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;8BAcsB,sBAAsB;;WAElC;EAET,YAAY,aAAa;;;qBAQb,0BAA0B;EACtC,YAAY;;;;;;;;;qBAYA,+BAA+B;;WAElC;;WAEA;EAET,YAAY,aAAa"}
|
package/dist/errors.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;8BAcsB,sBAAsB;;WAElC;
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;;;8BAcsB,sBAAsB;;WAElC;EAET,YAAY,aAAa;;;qBAQb,0BAA0B;EACtC,YAAY;;;;;;;;;qBAYA,+BAA+B;;WAElC;;WAEA;EAET,YAAY,aAAa"}
|
package/dist/inbound.cjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
3
|
+
let nodemailer = require("nodemailer");
|
|
4
|
+
let nodemailer_lib_mail_composer_index_js = require("nodemailer/lib/mail-composer/index.js");
|
|
5
|
+
nodemailer_lib_mail_composer_index_js = require_rolldown_runtime.__toESM(nodemailer_lib_mail_composer_index_js, 1);
|
|
6
|
+
let postal_mime = require("postal-mime");
|
|
7
|
+
postal_mime = require_rolldown_runtime.__toESM(postal_mime, 1);
|
|
8
|
+
//#region src/inbound.ts
|
|
9
|
+
/**
|
|
10
|
+
* Received mail: the one MIME parser everything that reads a message uses, and
|
|
11
|
+
* the composer that builds one.
|
|
12
|
+
*
|
|
13
|
+
* An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that
|
|
14
|
+
* reads what an app sent all go through {@link parseEmail}, so an attachment
|
|
15
|
+
* that survives one survives all of them. {@link composeEmail} builds a real
|
|
16
|
+
* MIME message from fields — what a test hands an inbox when it does not
|
|
17
|
+
* bring a `.eml` of its own.
|
|
18
|
+
*/
|
|
19
|
+
/** Parse a raw RFC 5322 message. */
|
|
20
|
+
async function parseEmail(raw) {
|
|
21
|
+
const parsed = await postal_mime.default.parse(raw, { attachmentEncoding: "arraybuffer" });
|
|
22
|
+
const headers = {};
|
|
23
|
+
for (const { key, value } of parsed.headers) {
|
|
24
|
+
const name = key.toLowerCase();
|
|
25
|
+
headers[name] = [...headers[name] ?? [], value];
|
|
26
|
+
}
|
|
27
|
+
const from = parsed.from ? addresses([parsed.from])[0] : void 0;
|
|
28
|
+
return {
|
|
29
|
+
...from ? { from } : {},
|
|
30
|
+
to: addresses(parsed.to),
|
|
31
|
+
cc: addresses(parsed.cc),
|
|
32
|
+
replyTo: addresses(parsed.replyTo),
|
|
33
|
+
...parsed.subject !== void 0 ? { subject: parsed.subject } : {},
|
|
34
|
+
...parsed.date !== void 0 ? { date: parsed.date } : {},
|
|
35
|
+
...parsed.messageId ? { messageId: parsed.messageId } : {},
|
|
36
|
+
...parsed.inReplyTo ? { inReplyTo: parsed.inReplyTo } : {},
|
|
37
|
+
references: (parsed.references ?? "").split(/\s+/).filter(Boolean),
|
|
38
|
+
headers,
|
|
39
|
+
...parsed.text !== void 0 ? { text: parsed.text } : {},
|
|
40
|
+
...parsed.html !== void 0 ? { html: parsed.html } : {},
|
|
41
|
+
attachments: parsed.attachments.map(attachment)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Groups flattened into their members: a header's mailboxes, in order. */
|
|
45
|
+
function addresses(list) {
|
|
46
|
+
const flat = [];
|
|
47
|
+
for (const entry of list ?? []) {
|
|
48
|
+
const members = entry.group ?? [entry];
|
|
49
|
+
for (const { name, address } of members) {
|
|
50
|
+
if (!address) continue;
|
|
51
|
+
flat.push({
|
|
52
|
+
...name ? { name } : {},
|
|
53
|
+
address
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return flat;
|
|
58
|
+
}
|
|
59
|
+
function attachment(file) {
|
|
60
|
+
const bytes = typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content instanceof Uint8Array ? file.content : new Uint8Array(file.content);
|
|
61
|
+
return {
|
|
62
|
+
...file.filename ? { filename: file.filename } : {},
|
|
63
|
+
contentType: file.mimeType,
|
|
64
|
+
size: bytes.byteLength,
|
|
65
|
+
...file.disposition ? { disposition: file.disposition } : {},
|
|
66
|
+
...file.contentId ? { contentId: file.contentId } : {},
|
|
67
|
+
content: async () => bytes.slice()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** A real MIME message from fields, as `parseEmail` reads one. */
|
|
71
|
+
async function composeEmail(options) {
|
|
72
|
+
const list = (value) => value === void 0 ? void 0 : typeof value === "string" ? value : [...value];
|
|
73
|
+
const built = await new nodemailer_lib_mail_composer_index_js.default({
|
|
74
|
+
from: options.from,
|
|
75
|
+
...options.to !== void 0 ? { to: list(options.to) } : {},
|
|
76
|
+
...options.cc !== void 0 ? { cc: list(options.cc) } : {},
|
|
77
|
+
...options.replyTo ? { replyTo: options.replyTo } : {},
|
|
78
|
+
...options.subject !== void 0 ? { subject: options.subject } : {},
|
|
79
|
+
...options.text !== void 0 ? { text: options.text } : {},
|
|
80
|
+
...options.html !== void 0 ? { html: options.html } : {},
|
|
81
|
+
...options.messageId ? { messageId: options.messageId } : {},
|
|
82
|
+
...options.date ? { date: options.date } : {},
|
|
83
|
+
...options.headers ? { headers: { ...options.headers } } : {},
|
|
84
|
+
attachments: (options.attachments ?? []).map((file) => ({
|
|
85
|
+
filename: file.filename,
|
|
86
|
+
content: typeof file.content === "string" ? file.content : Buffer.from(file.content),
|
|
87
|
+
...file.contentType ? { contentType: file.contentType } : {}
|
|
88
|
+
}))
|
|
89
|
+
}).compile().build();
|
|
90
|
+
return new Uint8Array(built);
|
|
91
|
+
}
|
|
92
|
+
/** The receiving server's reply named no id for the message it accepted. */
|
|
93
|
+
var DeliveryNotQueued = class extends Error {
|
|
94
|
+
response;
|
|
95
|
+
constructor(response) {
|
|
96
|
+
super("The mail server accepted the message but named no id for it ('queued as …'), so it cannot be read back. Mailpit names one; deliver to Mailpit.");
|
|
97
|
+
this.response = response;
|
|
98
|
+
this.name = "DeliveryNotQueued";
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Deliver a raw message over SMTP with an envelope of its own — what a
|
|
103
|
+
* sender's server does, `RCPT TO` and all — and return the id the receiving
|
|
104
|
+
* server queued it under: Mailpit's message id, which its API serves the raw
|
|
105
|
+
* message by.
|
|
106
|
+
*/
|
|
107
|
+
async function deliverRaw(smtpUrl, envelope, raw) {
|
|
108
|
+
const transport = (0, nodemailer.createTransport)(smtpUrl);
|
|
109
|
+
try {
|
|
110
|
+
const info = await transport.sendMail({
|
|
111
|
+
envelope: {
|
|
112
|
+
from: envelope.from,
|
|
113
|
+
to: [...envelope.to]
|
|
114
|
+
},
|
|
115
|
+
raw: typeof raw === "string" ? raw : Buffer.from(raw)
|
|
116
|
+
});
|
|
117
|
+
const id = /queued as (\S+)/i.exec(String(info.response))?.[1];
|
|
118
|
+
if (!id) throw new DeliveryNotQueued(String(info.response));
|
|
119
|
+
return id;
|
|
120
|
+
} finally {
|
|
121
|
+
transport.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
exports.DeliveryNotQueued = DeliveryNotQueued;
|
|
126
|
+
exports.composeEmail = composeEmail;
|
|
127
|
+
exports.deliverRaw = deliverRaw;
|
|
128
|
+
exports.parseEmail = parseEmail;
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=inbound.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.cjs","names":["PostalMime","MailComposer","createTransport"],"sources":["../src/inbound.ts"],"sourcesContent":["/**\n * Received mail: the one MIME parser everything that reads a message uses, and\n * the composer that builds one.\n *\n * An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that\n * reads what an app sent all go through {@link parseEmail}, so an attachment\n * that survives one survives all of them. {@link composeEmail} builds a real\n * MIME message from fields — what a test hands an inbox when it does not\n * bring a `.eml` of its own.\n */\n\nimport { createTransport } from 'nodemailer';\nimport MailComposer from 'nodemailer/lib/mail-composer/index.js';\nimport PostalMime, {\n\ttype Address,\n\ttype Attachment as PostalAttachment,\n} from 'postal-mime';\n\n/** A mailbox in a header: `\"Ada\" <ada@example.com>`. */\nexport interface EmailAddress {\n\t/** The display name, where the header gave one. */\n\tname?: string;\n\taddress: string;\n}\n\n/** A file in a message. */\nexport interface ReceivedAttachment {\n\t/**\n\t * As the sender named it — never a key or a path: it is theirs to choose,\n\t * `../../etc/passwd` included.\n\t */\n\tfilename?: string;\n\tcontentType: string;\n\t/** Bytes. */\n\tsize: number;\n\t/** `attachment`, or `inline` for one the HTML refers to by `contentId`. */\n\tdisposition?: string;\n\tcontentId?: string;\n\t/** Its bytes. */\n\tcontent(): Promise<Uint8Array>;\n}\n\n/** A message, parsed: its headers as the sender wrote them, its body, its files. */\nexport interface ParsedEmail {\n\tfrom?: EmailAddress;\n\tto: EmailAddress[];\n\tcc: EmailAddress[];\n\treplyTo: EmailAddress[];\n\tsubject?: string;\n\t/** `Date:`, as written. */\n\tdate?: string;\n\tmessageId?: string;\n\tinReplyTo?: string;\n\t/** `References:`, one message id each. */\n\treferences: string[];\n\t/** Every header, by lowercase name, each occurrence in order. */\n\theaders: Readonly<Record<string, readonly string[]>>;\n\ttext?: string;\n\thtml?: string;\n\tattachments: ReceivedAttachment[];\n}\n\n/** Parse a raw RFC 5322 message. */\nexport async function parseEmail(\n\traw: string | Uint8Array | ArrayBuffer,\n): Promise<ParsedEmail> {\n\tconst parsed = await PostalMime.parse(raw, {\n\t\tattachmentEncoding: 'arraybuffer',\n\t});\n\n\tconst headers: Record<string, string[]> = {};\n\tfor (const { key, value } of parsed.headers) {\n\t\tconst name = key.toLowerCase();\n\t\theaders[name] = [...(headers[name] ?? []), value];\n\t}\n\n\tconst from = parsed.from ? addresses([parsed.from])[0] : undefined;\n\treturn {\n\t\t...(from ? { from } : {}),\n\t\tto: addresses(parsed.to),\n\t\tcc: addresses(parsed.cc),\n\t\treplyTo: addresses(parsed.replyTo),\n\t\t...(parsed.subject !== undefined ? { subject: parsed.subject } : {}),\n\t\t...(parsed.date !== undefined ? { date: parsed.date } : {}),\n\t\t...(parsed.messageId ? { messageId: parsed.messageId } : {}),\n\t\t...(parsed.inReplyTo ? { inReplyTo: parsed.inReplyTo } : {}),\n\t\treferences: (parsed.references ?? '').split(/\\s+/).filter(Boolean),\n\t\theaders,\n\t\t...(parsed.text !== undefined ? { text: parsed.text } : {}),\n\t\t...(parsed.html !== undefined ? { html: parsed.html } : {}),\n\t\tattachments: parsed.attachments.map(attachment),\n\t};\n}\n\n/** Groups flattened into their members: a header's mailboxes, in order. */\nfunction addresses(list: readonly Address[] | undefined): EmailAddress[] {\n\tconst flat: EmailAddress[] = [];\n\tfor (const entry of list ?? []) {\n\t\tconst members = entry.group ?? [entry];\n\t\tfor (const { name, address } of members) {\n\t\t\tif (!address) continue;\n\t\t\tflat.push({ ...(name ? { name } : {}), address });\n\t\t}\n\t}\n\treturn flat;\n}\n\nfunction attachment(file: PostalAttachment): ReceivedAttachment {\n\tconst bytes =\n\t\ttypeof file.content === 'string'\n\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t: file.content instanceof Uint8Array\n\t\t\t\t? file.content\n\t\t\t\t: new Uint8Array(file.content);\n\treturn {\n\t\t...(file.filename ? { filename: file.filename } : {}),\n\t\tcontentType: file.mimeType,\n\t\tsize: bytes.byteLength,\n\t\t...(file.disposition ? { disposition: file.disposition } : {}),\n\t\t...(file.contentId ? { contentId: file.contentId } : {}),\n\t\t// A copy each time: a handler that mutates what it was given must not\n\t\t// change what the next read sees.\n\t\tcontent: async () => bytes.slice(),\n\t};\n}\n\n/** A file to put in a composed message. */\nexport interface ComposedAttachment {\n\tfilename: string;\n\tcontent: string | Uint8Array;\n\tcontentType?: string;\n}\n\n/** The fields {@link composeEmail} builds a message from. */\nexport interface ComposeEmailOptions {\n\tfrom: string;\n\t/** `To:` as written — not where the message is delivered, which is the envelope's. */\n\tto?: string | readonly string[];\n\tcc?: string | readonly string[];\n\treplyTo?: string;\n\tsubject?: string;\n\ttext?: string;\n\thtml?: string;\n\tattachments?: readonly ComposedAttachment[];\n\t/** Any other header, as written: `Auto-Submitted`, `In-Reply-To`, `References`. */\n\theaders?: Readonly<Record<string, string>>;\n\tmessageId?: string;\n\tdate?: Date;\n}\n\n/** A real MIME message from fields, as `parseEmail` reads one. */\nexport async function composeEmail(\n\toptions: ComposeEmailOptions,\n): Promise<Uint8Array> {\n\tconst list = (value: string | readonly string[] | undefined) =>\n\t\tvalue === undefined\n\t\t\t? undefined\n\t\t\t: typeof value === 'string'\n\t\t\t\t? value\n\t\t\t\t: [...value];\n\tconst composer = new MailComposer({\n\t\tfrom: options.from,\n\t\t...(options.to !== undefined ? { to: list(options.to) } : {}),\n\t\t...(options.cc !== undefined ? { cc: list(options.cc) } : {}),\n\t\t...(options.replyTo ? { replyTo: options.replyTo } : {}),\n\t\t...(options.subject !== undefined ? { subject: options.subject } : {}),\n\t\t...(options.text !== undefined ? { text: options.text } : {}),\n\t\t...(options.html !== undefined ? { html: options.html } : {}),\n\t\t...(options.messageId ? { messageId: options.messageId } : {}),\n\t\t...(options.date ? { date: options.date } : {}),\n\t\t...(options.headers ? { headers: { ...options.headers } } : {}),\n\t\tattachments: (options.attachments ?? []).map((file) => ({\n\t\t\tfilename: file.filename,\n\t\t\tcontent:\n\t\t\t\ttypeof file.content === 'string'\n\t\t\t\t\t? file.content\n\t\t\t\t\t: Buffer.from(file.content),\n\t\t\t...(file.contentType ? { contentType: file.contentType } : {}),\n\t\t})),\n\t});\n\tconst built = await composer.compile().build();\n\treturn new Uint8Array(built);\n}\n\n/** The receiving server's reply named no id for the message it accepted. */\nexport class DeliveryNotQueued extends Error {\n\tconstructor(readonly response: string) {\n\t\tsuper(\n\t\t\t`The mail server accepted the message but named no id for it ` +\n\t\t\t\t`('queued as …'), so it cannot be read back. Mailpit names one; ` +\n\t\t\t\t`deliver to Mailpit.`,\n\t\t);\n\t\tthis.name = 'DeliveryNotQueued';\n\t}\n}\n\n/**\n * Deliver a raw message over SMTP with an envelope of its own — what a\n * sender's server does, `RCPT TO` and all — and return the id the receiving\n * server queued it under: Mailpit's message id, which its API serves the raw\n * message by.\n */\nexport async function deliverRaw(\n\tsmtpUrl: string,\n\tenvelope: { from: string; to: readonly string[] },\n\traw: Uint8Array | string,\n): Promise<string> {\n\tconst transport = createTransport(smtpUrl);\n\ttry {\n\t\tconst info = await transport.sendMail({\n\t\t\tenvelope: { from: envelope.from, to: [...envelope.to] },\n\t\t\traw: typeof raw === 'string' ? raw : Buffer.from(raw),\n\t\t});\n\t\tconst id = /queued as (\\S+)/i.exec(String(info.response))?.[1];\n\t\tif (!id) throw new DeliveryNotQueued(String(info.response));\n\t\treturn id;\n\t} finally {\n\t\ttransport.close();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/inbound.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Received mail: the one MIME parser everything that reads a message uses, and
|
|
4
|
+
* the composer that builds one.
|
|
5
|
+
*
|
|
6
|
+
* An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that
|
|
7
|
+
* reads what an app sent all go through {@link parseEmail}, so an attachment
|
|
8
|
+
* that survives one survives all of them. {@link composeEmail} builds a real
|
|
9
|
+
* MIME message from fields — what a test hands an inbox when it does not
|
|
10
|
+
* bring a `.eml` of its own.
|
|
11
|
+
*/
|
|
12
|
+
/** A mailbox in a header: `"Ada" <ada@example.com>`. */
|
|
13
|
+
export interface EmailAddress {
|
|
14
|
+
/** The display name, where the header gave one. */
|
|
15
|
+
name?: string;
|
|
16
|
+
address: string;
|
|
17
|
+
}
|
|
18
|
+
/** A file in a message. */
|
|
19
|
+
export interface ReceivedAttachment {
|
|
20
|
+
/**
|
|
21
|
+
* As the sender named it — never a key or a path: it is theirs to choose,
|
|
22
|
+
* `../../etc/passwd` included.
|
|
23
|
+
*/
|
|
24
|
+
filename?: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
/** Bytes. */
|
|
27
|
+
size: number;
|
|
28
|
+
/** `attachment`, or `inline` for one the HTML refers to by `contentId`. */
|
|
29
|
+
disposition?: string;
|
|
30
|
+
contentId?: string;
|
|
31
|
+
/** Its bytes. */
|
|
32
|
+
content(): Promise<Uint8Array>;
|
|
33
|
+
}
|
|
34
|
+
/** A message, parsed: its headers as the sender wrote them, its body, its files. */
|
|
35
|
+
export interface ParsedEmail {
|
|
36
|
+
from?: EmailAddress;
|
|
37
|
+
to: EmailAddress[];
|
|
38
|
+
cc: EmailAddress[];
|
|
39
|
+
replyTo: EmailAddress[];
|
|
40
|
+
subject?: string;
|
|
41
|
+
/** `Date:`, as written. */
|
|
42
|
+
date?: string;
|
|
43
|
+
messageId?: string;
|
|
44
|
+
inReplyTo?: string;
|
|
45
|
+
/** `References:`, one message id each. */
|
|
46
|
+
references: string[];
|
|
47
|
+
/** Every header, by lowercase name, each occurrence in order. */
|
|
48
|
+
headers: Readonly<Record<string, readonly string[]>>;
|
|
49
|
+
text?: string;
|
|
50
|
+
html?: string;
|
|
51
|
+
attachments: ReceivedAttachment[];
|
|
52
|
+
}
|
|
53
|
+
/** Parse a raw RFC 5322 message. */
|
|
54
|
+
export declare function parseEmail(raw: string | Uint8Array | ArrayBuffer): Promise<ParsedEmail>;
|
|
55
|
+
/** A file to put in a composed message. */
|
|
56
|
+
export interface ComposedAttachment {
|
|
57
|
+
filename: string;
|
|
58
|
+
content: string | Uint8Array;
|
|
59
|
+
contentType?: string;
|
|
60
|
+
}
|
|
61
|
+
/** The fields {@link composeEmail} builds a message from. */
|
|
62
|
+
export interface ComposeEmailOptions {
|
|
63
|
+
from: string;
|
|
64
|
+
/** `To:` as written — not where the message is delivered, which is the envelope's. */
|
|
65
|
+
to?: string | readonly string[];
|
|
66
|
+
cc?: string | readonly string[];
|
|
67
|
+
replyTo?: string;
|
|
68
|
+
subject?: string;
|
|
69
|
+
text?: string;
|
|
70
|
+
html?: string;
|
|
71
|
+
attachments?: readonly ComposedAttachment[];
|
|
72
|
+
/** Any other header, as written: `Auto-Submitted`, `In-Reply-To`, `References`. */
|
|
73
|
+
headers?: Readonly<Record<string, string>>;
|
|
74
|
+
messageId?: string;
|
|
75
|
+
date?: Date;
|
|
76
|
+
}
|
|
77
|
+
/** A real MIME message from fields, as `parseEmail` reads one. */
|
|
78
|
+
export declare function composeEmail(options: ComposeEmailOptions): Promise<Uint8Array>;
|
|
79
|
+
/** The receiving server's reply named no id for the message it accepted. */
|
|
80
|
+
export declare class DeliveryNotQueued extends Error {
|
|
81
|
+
readonly response: string;
|
|
82
|
+
constructor(response: string);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Deliver a raw message over SMTP with an envelope of its own — what a
|
|
86
|
+
* sender's server does, `RCPT TO` and all — and return the id the receiving
|
|
87
|
+
* server queued it under: Mailpit's message id, which its API serves the raw
|
|
88
|
+
* message by.
|
|
89
|
+
*/
|
|
90
|
+
export declare function deliverRaw(smtpUrl: string, envelope: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: readonly string[];
|
|
93
|
+
}, raw: Uint8Array | string): Promise<string>;
|
|
94
|
+
//#endregion
|
|
95
|
+
//# sourceMappingURL=inbound.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.cts","names":[],"sources":["../src/inbound.ts"],"mappings":";;;;;;;;;;;;iBAmBiB;;EAEhB;EACA;;;iBAIgB;;;;;EAKhB;EACA;;EAEA;;EAEA;EACA;;EAEA,WAAW,QAAQ;;;iBAIH;EAChB,OAAO;EACP,IAAI;EACJ,IAAI;EACJ,SAAS;EACT;;EAEA;EACA;EACA;;EAEA;;EAEA,SAAS,SAAS;EAClB;EACA;EACA,aAAa;;;wBAIQ,WACrB,cAAc,aAAa,cACzB,QAAQ;;iBA8DM;EAChB;EACA,kBAAkB;EAClB;;;iBAIgB;EAChB;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA,uBAAuB;;EAEvB,UAAU,SAAS;EACnB;EACA,OAAO;;;wBAIc,aACrB,SAAS,sBACP,QAAQ;;qBAgCE,0BAA0B;WACjB;EAArB,YAAqB;;;;;;;;wBAgBA,WACrB,iBACA;EAAY;EAAc;GAC1B,KAAK,sBACH"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/inbound.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Received mail: the one MIME parser everything that reads a message uses, and
|
|
4
|
+
* the composer that builds one.
|
|
5
|
+
*
|
|
6
|
+
* An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that
|
|
7
|
+
* reads what an app sent all go through {@link parseEmail}, so an attachment
|
|
8
|
+
* that survives one survives all of them. {@link composeEmail} builds a real
|
|
9
|
+
* MIME message from fields — what a test hands an inbox when it does not
|
|
10
|
+
* bring a `.eml` of its own.
|
|
11
|
+
*/
|
|
12
|
+
/** A mailbox in a header: `"Ada" <ada@example.com>`. */
|
|
13
|
+
export interface EmailAddress {
|
|
14
|
+
/** The display name, where the header gave one. */
|
|
15
|
+
name?: string;
|
|
16
|
+
address: string;
|
|
17
|
+
}
|
|
18
|
+
/** A file in a message. */
|
|
19
|
+
export interface ReceivedAttachment {
|
|
20
|
+
/**
|
|
21
|
+
* As the sender named it — never a key or a path: it is theirs to choose,
|
|
22
|
+
* `../../etc/passwd` included.
|
|
23
|
+
*/
|
|
24
|
+
filename?: string;
|
|
25
|
+
contentType: string;
|
|
26
|
+
/** Bytes. */
|
|
27
|
+
size: number;
|
|
28
|
+
/** `attachment`, or `inline` for one the HTML refers to by `contentId`. */
|
|
29
|
+
disposition?: string;
|
|
30
|
+
contentId?: string;
|
|
31
|
+
/** Its bytes. */
|
|
32
|
+
content(): Promise<Uint8Array>;
|
|
33
|
+
}
|
|
34
|
+
/** A message, parsed: its headers as the sender wrote them, its body, its files. */
|
|
35
|
+
export interface ParsedEmail {
|
|
36
|
+
from?: EmailAddress;
|
|
37
|
+
to: EmailAddress[];
|
|
38
|
+
cc: EmailAddress[];
|
|
39
|
+
replyTo: EmailAddress[];
|
|
40
|
+
subject?: string;
|
|
41
|
+
/** `Date:`, as written. */
|
|
42
|
+
date?: string;
|
|
43
|
+
messageId?: string;
|
|
44
|
+
inReplyTo?: string;
|
|
45
|
+
/** `References:`, one message id each. */
|
|
46
|
+
references: string[];
|
|
47
|
+
/** Every header, by lowercase name, each occurrence in order. */
|
|
48
|
+
headers: Readonly<Record<string, readonly string[]>>;
|
|
49
|
+
text?: string;
|
|
50
|
+
html?: string;
|
|
51
|
+
attachments: ReceivedAttachment[];
|
|
52
|
+
}
|
|
53
|
+
/** Parse a raw RFC 5322 message. */
|
|
54
|
+
export declare function parseEmail(raw: string | Uint8Array | ArrayBuffer): Promise<ParsedEmail>;
|
|
55
|
+
/** A file to put in a composed message. */
|
|
56
|
+
export interface ComposedAttachment {
|
|
57
|
+
filename: string;
|
|
58
|
+
content: string | Uint8Array;
|
|
59
|
+
contentType?: string;
|
|
60
|
+
}
|
|
61
|
+
/** The fields {@link composeEmail} builds a message from. */
|
|
62
|
+
export interface ComposeEmailOptions {
|
|
63
|
+
from: string;
|
|
64
|
+
/** `To:` as written — not where the message is delivered, which is the envelope's. */
|
|
65
|
+
to?: string | readonly string[];
|
|
66
|
+
cc?: string | readonly string[];
|
|
67
|
+
replyTo?: string;
|
|
68
|
+
subject?: string;
|
|
69
|
+
text?: string;
|
|
70
|
+
html?: string;
|
|
71
|
+
attachments?: readonly ComposedAttachment[];
|
|
72
|
+
/** Any other header, as written: `Auto-Submitted`, `In-Reply-To`, `References`. */
|
|
73
|
+
headers?: Readonly<Record<string, string>>;
|
|
74
|
+
messageId?: string;
|
|
75
|
+
date?: Date;
|
|
76
|
+
}
|
|
77
|
+
/** A real MIME message from fields, as `parseEmail` reads one. */
|
|
78
|
+
export declare function composeEmail(options: ComposeEmailOptions): Promise<Uint8Array>;
|
|
79
|
+
/** The receiving server's reply named no id for the message it accepted. */
|
|
80
|
+
export declare class DeliveryNotQueued extends Error {
|
|
81
|
+
readonly response: string;
|
|
82
|
+
constructor(response: string);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Deliver a raw message over SMTP with an envelope of its own — what a
|
|
86
|
+
* sender's server does, `RCPT TO` and all — and return the id the receiving
|
|
87
|
+
* server queued it under: Mailpit's message id, which its API serves the raw
|
|
88
|
+
* message by.
|
|
89
|
+
*/
|
|
90
|
+
export declare function deliverRaw(smtpUrl: string, envelope: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: readonly string[];
|
|
93
|
+
}, raw: Uint8Array | string): Promise<string>;
|
|
94
|
+
//#endregion
|
|
95
|
+
//# sourceMappingURL=inbound.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.mts","names":[],"sources":["../src/inbound.ts"],"mappings":";;;;;;;;;;;;iBAmBiB;;EAEhB;EACA;;;iBAIgB;;;;;EAKhB;EACA;;EAEA;;EAEA;EACA;;EAEA,WAAW,QAAQ;;;iBAIH;EAChB,OAAO;EACP,IAAI;EACJ,IAAI;EACJ,SAAS;EACT;;EAEA;EACA;EACA;;EAEA;;EAEA,SAAS,SAAS;EAClB;EACA;EACA,aAAa;;;wBAIQ,WACrB,cAAc,aAAa,cACzB,QAAQ;;iBA8DM;EAChB;EACA,kBAAkB;EAClB;;;iBAIgB;EAChB;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA,uBAAuB;;EAEvB,UAAU,SAAS;EACnB;EACA,OAAO;;;wBAIc,aACrB,SAAS,sBACP,QAAQ;;qBAgCE,0BAA0B;WACjB;EAArB,YAAqB;;;;;;;;wBAgBA,WACrB,iBACA;EAAY;EAAc;GAC1B,KAAK,sBACH"}
|
package/dist/inbound.mjs
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createTransport } from "nodemailer";
|
|
2
|
+
import MailComposer from "nodemailer/lib/mail-composer/index.js";
|
|
3
|
+
import PostalMime from "postal-mime";
|
|
4
|
+
//#region src/inbound.ts
|
|
5
|
+
/**
|
|
6
|
+
* Received mail: the one MIME parser everything that reads a message uses, and
|
|
7
|
+
* the composer that builds one.
|
|
8
|
+
*
|
|
9
|
+
* An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that
|
|
10
|
+
* reads what an app sent all go through {@link parseEmail}, so an attachment
|
|
11
|
+
* that survives one survives all of them. {@link composeEmail} builds a real
|
|
12
|
+
* MIME message from fields — what a test hands an inbox when it does not
|
|
13
|
+
* bring a `.eml` of its own.
|
|
14
|
+
*/
|
|
15
|
+
/** Parse a raw RFC 5322 message. */
|
|
16
|
+
async function parseEmail(raw) {
|
|
17
|
+
const parsed = await PostalMime.parse(raw, { attachmentEncoding: "arraybuffer" });
|
|
18
|
+
const headers = {};
|
|
19
|
+
for (const { key, value } of parsed.headers) {
|
|
20
|
+
const name = key.toLowerCase();
|
|
21
|
+
headers[name] = [...headers[name] ?? [], value];
|
|
22
|
+
}
|
|
23
|
+
const from = parsed.from ? addresses([parsed.from])[0] : void 0;
|
|
24
|
+
return {
|
|
25
|
+
...from ? { from } : {},
|
|
26
|
+
to: addresses(parsed.to),
|
|
27
|
+
cc: addresses(parsed.cc),
|
|
28
|
+
replyTo: addresses(parsed.replyTo),
|
|
29
|
+
...parsed.subject !== void 0 ? { subject: parsed.subject } : {},
|
|
30
|
+
...parsed.date !== void 0 ? { date: parsed.date } : {},
|
|
31
|
+
...parsed.messageId ? { messageId: parsed.messageId } : {},
|
|
32
|
+
...parsed.inReplyTo ? { inReplyTo: parsed.inReplyTo } : {},
|
|
33
|
+
references: (parsed.references ?? "").split(/\s+/).filter(Boolean),
|
|
34
|
+
headers,
|
|
35
|
+
...parsed.text !== void 0 ? { text: parsed.text } : {},
|
|
36
|
+
...parsed.html !== void 0 ? { html: parsed.html } : {},
|
|
37
|
+
attachments: parsed.attachments.map(attachment)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Groups flattened into their members: a header's mailboxes, in order. */
|
|
41
|
+
function addresses(list) {
|
|
42
|
+
const flat = [];
|
|
43
|
+
for (const entry of list ?? []) {
|
|
44
|
+
const members = entry.group ?? [entry];
|
|
45
|
+
for (const { name, address } of members) {
|
|
46
|
+
if (!address) continue;
|
|
47
|
+
flat.push({
|
|
48
|
+
...name ? { name } : {},
|
|
49
|
+
address
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return flat;
|
|
54
|
+
}
|
|
55
|
+
function attachment(file) {
|
|
56
|
+
const bytes = typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content instanceof Uint8Array ? file.content : new Uint8Array(file.content);
|
|
57
|
+
return {
|
|
58
|
+
...file.filename ? { filename: file.filename } : {},
|
|
59
|
+
contentType: file.mimeType,
|
|
60
|
+
size: bytes.byteLength,
|
|
61
|
+
...file.disposition ? { disposition: file.disposition } : {},
|
|
62
|
+
...file.contentId ? { contentId: file.contentId } : {},
|
|
63
|
+
content: async () => bytes.slice()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** A real MIME message from fields, as `parseEmail` reads one. */
|
|
67
|
+
async function composeEmail(options) {
|
|
68
|
+
const list = (value) => value === void 0 ? void 0 : typeof value === "string" ? value : [...value];
|
|
69
|
+
const built = await new MailComposer({
|
|
70
|
+
from: options.from,
|
|
71
|
+
...options.to !== void 0 ? { to: list(options.to) } : {},
|
|
72
|
+
...options.cc !== void 0 ? { cc: list(options.cc) } : {},
|
|
73
|
+
...options.replyTo ? { replyTo: options.replyTo } : {},
|
|
74
|
+
...options.subject !== void 0 ? { subject: options.subject } : {},
|
|
75
|
+
...options.text !== void 0 ? { text: options.text } : {},
|
|
76
|
+
...options.html !== void 0 ? { html: options.html } : {},
|
|
77
|
+
...options.messageId ? { messageId: options.messageId } : {},
|
|
78
|
+
...options.date ? { date: options.date } : {},
|
|
79
|
+
...options.headers ? { headers: { ...options.headers } } : {},
|
|
80
|
+
attachments: (options.attachments ?? []).map((file) => ({
|
|
81
|
+
filename: file.filename,
|
|
82
|
+
content: typeof file.content === "string" ? file.content : Buffer.from(file.content),
|
|
83
|
+
...file.contentType ? { contentType: file.contentType } : {}
|
|
84
|
+
}))
|
|
85
|
+
}).compile().build();
|
|
86
|
+
return new Uint8Array(built);
|
|
87
|
+
}
|
|
88
|
+
/** The receiving server's reply named no id for the message it accepted. */
|
|
89
|
+
var DeliveryNotQueued = class extends Error {
|
|
90
|
+
response;
|
|
91
|
+
constructor(response) {
|
|
92
|
+
super("The mail server accepted the message but named no id for it ('queued as …'), so it cannot be read back. Mailpit names one; deliver to Mailpit.");
|
|
93
|
+
this.response = response;
|
|
94
|
+
this.name = "DeliveryNotQueued";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Deliver a raw message over SMTP with an envelope of its own — what a
|
|
99
|
+
* sender's server does, `RCPT TO` and all — and return the id the receiving
|
|
100
|
+
* server queued it under: Mailpit's message id, which its API serves the raw
|
|
101
|
+
* message by.
|
|
102
|
+
*/
|
|
103
|
+
async function deliverRaw(smtpUrl, envelope, raw) {
|
|
104
|
+
const transport = createTransport(smtpUrl);
|
|
105
|
+
try {
|
|
106
|
+
const info = await transport.sendMail({
|
|
107
|
+
envelope: {
|
|
108
|
+
from: envelope.from,
|
|
109
|
+
to: [...envelope.to]
|
|
110
|
+
},
|
|
111
|
+
raw: typeof raw === "string" ? raw : Buffer.from(raw)
|
|
112
|
+
});
|
|
113
|
+
const id = /queued as (\S+)/i.exec(String(info.response))?.[1];
|
|
114
|
+
if (!id) throw new DeliveryNotQueued(String(info.response));
|
|
115
|
+
return id;
|
|
116
|
+
} finally {
|
|
117
|
+
transport.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { DeliveryNotQueued, composeEmail, deliverRaw, parseEmail };
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=inbound.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.mjs","names":[],"sources":["../src/inbound.ts"],"sourcesContent":["/**\n * Received mail: the one MIME parser everything that reads a message uses, and\n * the composer that builds one.\n *\n * An `Inbox`'s handler, the test mailbox's `receive` and the mailbox that\n * reads what an app sent all go through {@link parseEmail}, so an attachment\n * that survives one survives all of them. {@link composeEmail} builds a real\n * MIME message from fields — what a test hands an inbox when it does not\n * bring a `.eml` of its own.\n */\n\nimport { createTransport } from 'nodemailer';\nimport MailComposer from 'nodemailer/lib/mail-composer/index.js';\nimport PostalMime, {\n\ttype Address,\n\ttype Attachment as PostalAttachment,\n} from 'postal-mime';\n\n/** A mailbox in a header: `\"Ada\" <ada@example.com>`. */\nexport interface EmailAddress {\n\t/** The display name, where the header gave one. */\n\tname?: string;\n\taddress: string;\n}\n\n/** A file in a message. */\nexport interface ReceivedAttachment {\n\t/**\n\t * As the sender named it — never a key or a path: it is theirs to choose,\n\t * `../../etc/passwd` included.\n\t */\n\tfilename?: string;\n\tcontentType: string;\n\t/** Bytes. */\n\tsize: number;\n\t/** `attachment`, or `inline` for one the HTML refers to by `contentId`. */\n\tdisposition?: string;\n\tcontentId?: string;\n\t/** Its bytes. */\n\tcontent(): Promise<Uint8Array>;\n}\n\n/** A message, parsed: its headers as the sender wrote them, its body, its files. */\nexport interface ParsedEmail {\n\tfrom?: EmailAddress;\n\tto: EmailAddress[];\n\tcc: EmailAddress[];\n\treplyTo: EmailAddress[];\n\tsubject?: string;\n\t/** `Date:`, as written. */\n\tdate?: string;\n\tmessageId?: string;\n\tinReplyTo?: string;\n\t/** `References:`, one message id each. */\n\treferences: string[];\n\t/** Every header, by lowercase name, each occurrence in order. */\n\theaders: Readonly<Record<string, readonly string[]>>;\n\ttext?: string;\n\thtml?: string;\n\tattachments: ReceivedAttachment[];\n}\n\n/** Parse a raw RFC 5322 message. */\nexport async function parseEmail(\n\traw: string | Uint8Array | ArrayBuffer,\n): Promise<ParsedEmail> {\n\tconst parsed = await PostalMime.parse(raw, {\n\t\tattachmentEncoding: 'arraybuffer',\n\t});\n\n\tconst headers: Record<string, string[]> = {};\n\tfor (const { key, value } of parsed.headers) {\n\t\tconst name = key.toLowerCase();\n\t\theaders[name] = [...(headers[name] ?? []), value];\n\t}\n\n\tconst from = parsed.from ? addresses([parsed.from])[0] : undefined;\n\treturn {\n\t\t...(from ? { from } : {}),\n\t\tto: addresses(parsed.to),\n\t\tcc: addresses(parsed.cc),\n\t\treplyTo: addresses(parsed.replyTo),\n\t\t...(parsed.subject !== undefined ? { subject: parsed.subject } : {}),\n\t\t...(parsed.date !== undefined ? { date: parsed.date } : {}),\n\t\t...(parsed.messageId ? { messageId: parsed.messageId } : {}),\n\t\t...(parsed.inReplyTo ? { inReplyTo: parsed.inReplyTo } : {}),\n\t\treferences: (parsed.references ?? '').split(/\\s+/).filter(Boolean),\n\t\theaders,\n\t\t...(parsed.text !== undefined ? { text: parsed.text } : {}),\n\t\t...(parsed.html !== undefined ? { html: parsed.html } : {}),\n\t\tattachments: parsed.attachments.map(attachment),\n\t};\n}\n\n/** Groups flattened into their members: a header's mailboxes, in order. */\nfunction addresses(list: readonly Address[] | undefined): EmailAddress[] {\n\tconst flat: EmailAddress[] = [];\n\tfor (const entry of list ?? []) {\n\t\tconst members = entry.group ?? [entry];\n\t\tfor (const { name, address } of members) {\n\t\t\tif (!address) continue;\n\t\t\tflat.push({ ...(name ? { name } : {}), address });\n\t\t}\n\t}\n\treturn flat;\n}\n\nfunction attachment(file: PostalAttachment): ReceivedAttachment {\n\tconst bytes =\n\t\ttypeof file.content === 'string'\n\t\t\t? new TextEncoder().encode(file.content)\n\t\t\t: file.content instanceof Uint8Array\n\t\t\t\t? file.content\n\t\t\t\t: new Uint8Array(file.content);\n\treturn {\n\t\t...(file.filename ? { filename: file.filename } : {}),\n\t\tcontentType: file.mimeType,\n\t\tsize: bytes.byteLength,\n\t\t...(file.disposition ? { disposition: file.disposition } : {}),\n\t\t...(file.contentId ? { contentId: file.contentId } : {}),\n\t\t// A copy each time: a handler that mutates what it was given must not\n\t\t// change what the next read sees.\n\t\tcontent: async () => bytes.slice(),\n\t};\n}\n\n/** A file to put in a composed message. */\nexport interface ComposedAttachment {\n\tfilename: string;\n\tcontent: string | Uint8Array;\n\tcontentType?: string;\n}\n\n/** The fields {@link composeEmail} builds a message from. */\nexport interface ComposeEmailOptions {\n\tfrom: string;\n\t/** `To:` as written — not where the message is delivered, which is the envelope's. */\n\tto?: string | readonly string[];\n\tcc?: string | readonly string[];\n\treplyTo?: string;\n\tsubject?: string;\n\ttext?: string;\n\thtml?: string;\n\tattachments?: readonly ComposedAttachment[];\n\t/** Any other header, as written: `Auto-Submitted`, `In-Reply-To`, `References`. */\n\theaders?: Readonly<Record<string, string>>;\n\tmessageId?: string;\n\tdate?: Date;\n}\n\n/** A real MIME message from fields, as `parseEmail` reads one. */\nexport async function composeEmail(\n\toptions: ComposeEmailOptions,\n): Promise<Uint8Array> {\n\tconst list = (value: string | readonly string[] | undefined) =>\n\t\tvalue === undefined\n\t\t\t? undefined\n\t\t\t: typeof value === 'string'\n\t\t\t\t? value\n\t\t\t\t: [...value];\n\tconst composer = new MailComposer({\n\t\tfrom: options.from,\n\t\t...(options.to !== undefined ? { to: list(options.to) } : {}),\n\t\t...(options.cc !== undefined ? { cc: list(options.cc) } : {}),\n\t\t...(options.replyTo ? { replyTo: options.replyTo } : {}),\n\t\t...(options.subject !== undefined ? { subject: options.subject } : {}),\n\t\t...(options.text !== undefined ? { text: options.text } : {}),\n\t\t...(options.html !== undefined ? { html: options.html } : {}),\n\t\t...(options.messageId ? { messageId: options.messageId } : {}),\n\t\t...(options.date ? { date: options.date } : {}),\n\t\t...(options.headers ? { headers: { ...options.headers } } : {}),\n\t\tattachments: (options.attachments ?? []).map((file) => ({\n\t\t\tfilename: file.filename,\n\t\t\tcontent:\n\t\t\t\ttypeof file.content === 'string'\n\t\t\t\t\t? file.content\n\t\t\t\t\t: Buffer.from(file.content),\n\t\t\t...(file.contentType ? { contentType: file.contentType } : {}),\n\t\t})),\n\t});\n\tconst built = await composer.compile().build();\n\treturn new Uint8Array(built);\n}\n\n/** The receiving server's reply named no id for the message it accepted. */\nexport class DeliveryNotQueued extends Error {\n\tconstructor(readonly response: string) {\n\t\tsuper(\n\t\t\t`The mail server accepted the message but named no id for it ` +\n\t\t\t\t`('queued as …'), so it cannot be read back. Mailpit names one; ` +\n\t\t\t\t`deliver to Mailpit.`,\n\t\t);\n\t\tthis.name = 'DeliveryNotQueued';\n\t}\n}\n\n/**\n * Deliver a raw message over SMTP with an envelope of its own — what a\n * sender's server does, `RCPT TO` and all — and return the id the receiving\n * server queued it under: Mailpit's message id, which its API serves the raw\n * message by.\n */\nexport async function deliverRaw(\n\tsmtpUrl: string,\n\tenvelope: { from: string; to: readonly string[] },\n\traw: Uint8Array | string,\n): Promise<string> {\n\tconst transport = createTransport(smtpUrl);\n\ttry {\n\t\tconst info = await transport.sendMail({\n\t\t\tenvelope: { from: envelope.from, to: [...envelope.to] },\n\t\t\traw: typeof raw === 'string' ? raw : Buffer.from(raw),\n\t\t});\n\t\tconst id = /queued as (\\S+)/i.exec(String(info.response))?.[1];\n\t\tif (!id) throw new DeliveryNotQueued(String(info.response));\n\t\treturn id;\n\t} finally {\n\t\ttransport.close();\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
Object.defineProperty(exports, "__toESM", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function() {
|
|
26
|
+
return __toESM;
|
|
27
|
+
}
|
|
28
|
+
});
|
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.141",
|
|
4
4
|
"description": "Type-safe email client with SMTP support and React templates",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,20 +38,26 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"
|
|
41
|
+
"@opentelemetry/api": "~1.9.1",
|
|
42
|
+
"nodemailer": "~10.0.10",
|
|
43
|
+
"postal-mime": "~4.1.4"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
46
|
+
"@opentelemetry/sdk-trace-node": "~2.11.0",
|
|
44
47
|
"@types/nodemailer": "~8.0.2",
|
|
45
|
-
"@types/react": "~19.
|
|
46
|
-
"@types/react-dom": "~19.
|
|
47
|
-
"typescript": "~
|
|
48
|
+
"@types/react": "~19.3.0",
|
|
49
|
+
"@types/react-dom": "~19.3.0",
|
|
50
|
+
"typescript": "~7.0.2"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
53
|
+
"@react-email/components": ">=1.0.12",
|
|
50
54
|
"react": ">=18.0.0",
|
|
51
|
-
"react-dom": ">=18.0.0"
|
|
52
|
-
"@react-email/components": ">=1.0.12"
|
|
55
|
+
"react-dom": ">=18.0.0"
|
|
53
56
|
},
|
|
54
57
|
"engines": {
|
|
55
58
|
"node": ">=22.0.0"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown --config ../../tsdown.config.ts --filter ."
|
|
56
62
|
}
|
|
57
63
|
}
|