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