@lunora/mail 1.0.0-alpha.4 → 1.0.0-alpha.6
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/inbound/index.mjs +2 -2
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.mjs +5 -5
- package/dist/packem_shared/{address-fkXxLKza.mjs → address-vSUAVU2T.mjs} +7 -5
- package/dist/packem_shared/{consumeQueuedSend-BEKOdaxU.mjs → consumeQueuedSend-B9hTDOZ6.mjs} +4 -2
- package/dist/packem_shared/{createCaptureSink-DeihS4LH.mjs → createCaptureSink-Di-5AJUs.mjs} +5 -3
- package/dist/packem_shared/{createCloudflareTransport-yHOVEsZv.mjs → createCloudflareTransport-DXh1nUNi.mjs} +5 -3
- package/dist/packem_shared/{createInboundEmailHandler-D0uCOrU-.mjs → createInboundEmailHandler-BW_s0-Vc.mjs} +5 -4
- package/dist/packem_shared/{createMailer-oEKPAd4J.mjs → createMailer-B4Z_Cgiu.mjs} +11 -7
- package/dist/packem_shared/{createResendTransport-oNIorpzv.mjs → createResendTransport-CMQWjZoi.mjs} +1 -1
- package/dist/packem_shared/{parseInboundEmail-Bw9u_1oc.mjs → parseInboundEmail-6lafQBT1.mjs} +1 -1
- package/dist/packem_shared/{provider-transport-C5CVbjRF.mjs → provider-transport-C0xHi3oy.mjs} +4 -3
- package/dist/testing.mjs +9 -3
- package/package.json +2 -1
package/dist/inbound/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { createInboundEmailHandler, dispatchToLunoraFunction } from '../packem_shared/createInboundEmailHandler-
|
|
2
|
-
export { parseInboundEmail } from '../packem_shared/parseInboundEmail-
|
|
1
|
+
export { createInboundEmailHandler, dispatchToLunoraFunction } from '../packem_shared/createInboundEmailHandler-BW_s0-Vc.mjs';
|
|
2
|
+
export { parseInboundEmail } from '../packem_shared/parseInboundEmail-6lafQBT1.mjs';
|
package/dist/index.d.mts
CHANGED
|
@@ -34,6 +34,23 @@ interface CloudflareTransportOptions {
|
|
|
34
34
|
* verified-address constraint never bites the dev loop.
|
|
35
35
|
*/
|
|
36
36
|
declare const createCloudflareTransport: (options: CloudflareTransportOptions) => MailTransport;
|
|
37
|
+
/**
|
|
38
|
+
* Create a mailer bound to a transport.
|
|
39
|
+
*
|
|
40
|
+
* SECURITY — recipient policy and HTML content are the caller's responsibility.
|
|
41
|
+
* The mailer fully blocks header/CRLF/comma injection in addresses
|
|
42
|
+
* (`assertSafeAddresses` / `assertSafeHeaderValue`), but it does NOT decide WHO
|
|
43
|
+
* you may send to or WHAT HTML you render.
|
|
44
|
+
*
|
|
45
|
+
* Open relay: derive `to`/`cc`/`bcc` from server-trusted state, never from raw
|
|
46
|
+
* request input, and prefer a fixed/allowlisted `from` — sending to an arbitrary
|
|
47
|
+
* user-supplied address turns your deployment into a spam relay.
|
|
48
|
+
*
|
|
49
|
+
* Template XSS / content injection: treat template HTML like any other HTML sink
|
|
50
|
+
* — never interpolate untrusted data into raw markup (or a
|
|
51
|
+
* `dangerouslySetInnerHTML`-style template) without escaping. The mailer sends
|
|
52
|
+
* whatever HTML you hand it verbatim.
|
|
53
|
+
*/
|
|
37
54
|
declare const createMailer: (options: LunoraMailOptions) => Mailer;
|
|
38
55
|
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
39
56
|
type MailEnv = Record<string, unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,23 @@ interface CloudflareTransportOptions {
|
|
|
34
34
|
* verified-address constraint never bites the dev loop.
|
|
35
35
|
*/
|
|
36
36
|
declare const createCloudflareTransport: (options: CloudflareTransportOptions) => MailTransport;
|
|
37
|
+
/**
|
|
38
|
+
* Create a mailer bound to a transport.
|
|
39
|
+
*
|
|
40
|
+
* SECURITY — recipient policy and HTML content are the caller's responsibility.
|
|
41
|
+
* The mailer fully blocks header/CRLF/comma injection in addresses
|
|
42
|
+
* (`assertSafeAddresses` / `assertSafeHeaderValue`), but it does NOT decide WHO
|
|
43
|
+
* you may send to or WHAT HTML you render.
|
|
44
|
+
*
|
|
45
|
+
* Open relay: derive `to`/`cc`/`bcc` from server-trusted state, never from raw
|
|
46
|
+
* request input, and prefer a fixed/allowlisted `from` — sending to an arbitrary
|
|
47
|
+
* user-supplied address turns your deployment into a spam relay.
|
|
48
|
+
*
|
|
49
|
+
* Template XSS / content injection: treat template HTML like any other HTML sink
|
|
50
|
+
* — never interpolate untrusted data into raw markup (or a
|
|
51
|
+
* `dangerouslySetInnerHTML`-style template) without escaping. The mailer sends
|
|
52
|
+
* whatever HTML you hand it verbatim.
|
|
53
|
+
*/
|
|
37
54
|
declare const createMailer: (options: LunoraMailOptions) => Mailer;
|
|
38
55
|
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
39
56
|
type MailEnv = Record<string, unknown>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createCaptureTransport } from './packem_shared/createCaptureTransport-Crz_8822.mjs';
|
|
2
|
-
export { createCloudflareTransport } from './packem_shared/createCloudflareTransport-
|
|
3
|
-
export { default as createMailer } from './packem_shared/createMailer-
|
|
4
|
-
export { createCaptureSink, createMailerFromEnv, shouldCaptureMail } from './packem_shared/createCaptureSink-
|
|
5
|
-
export { consumeQueuedSend, toQueuedPayload } from './packem_shared/consumeQueuedSend-
|
|
2
|
+
export { createCloudflareTransport } from './packem_shared/createCloudflareTransport-DXh1nUNi.mjs';
|
|
3
|
+
export { default as createMailer } from './packem_shared/createMailer-B4Z_Cgiu.mjs';
|
|
4
|
+
export { createCaptureSink, createMailerFromEnv, shouldCaptureMail } from './packem_shared/createCaptureSink-Di-5AJUs.mjs';
|
|
5
|
+
export { consumeQueuedSend, toQueuedPayload } from './packem_shared/consumeQueuedSend-B9hTDOZ6.mjs';
|
|
6
6
|
export { default as renderEmail } from './packem_shared/renderEmail-hyS1bpVP.mjs';
|
|
7
|
-
export { default as createResendTransport } from './packem_shared/createResendTransport-
|
|
7
|
+
export { default as createResendTransport } from './packem_shared/createResendTransport-CMQWjZoi.mjs';
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const MAX_EMAIL_LENGTH = 320;
|
|
2
4
|
const MAX_NAME_LENGTH = 256;
|
|
3
5
|
const ADDRESS_PATTERN = /^([^<]*)<([^>]*)>\s*$/;
|
|
4
6
|
const assertSafeAddressField = (field, value) => {
|
|
5
7
|
if (value.includes("\r") || value.includes("\n") || value.includes(",")) {
|
|
6
|
-
throw new
|
|
8
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: address ${field} must not contain CR, LF, or comma`);
|
|
7
9
|
}
|
|
8
10
|
};
|
|
9
11
|
const assertSafeHeaderValue = (label, value) => {
|
|
10
12
|
if (value.includes("\r") || value.includes("\n")) {
|
|
11
|
-
throw new
|
|
13
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: ${label} must not contain CR or LF`);
|
|
12
14
|
}
|
|
13
15
|
};
|
|
14
16
|
const toBracketedAddress = (name, email) => {
|
|
15
17
|
if (name.length > MAX_NAME_LENGTH) {
|
|
16
|
-
throw new
|
|
18
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: address name must be <= ${String(MAX_NAME_LENGTH)} characters`);
|
|
17
19
|
}
|
|
18
20
|
if (email.length > MAX_EMAIL_LENGTH) {
|
|
19
|
-
throw new
|
|
21
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: address email must be <= ${String(MAX_EMAIL_LENGTH)} characters`);
|
|
20
22
|
}
|
|
21
23
|
if (name) {
|
|
22
24
|
assertSafeAddressField("name", name);
|
|
@@ -27,7 +29,7 @@ const toBracketedAddress = (name, email) => {
|
|
|
27
29
|
const toBareAddress = (input) => {
|
|
28
30
|
const email = input.trim();
|
|
29
31
|
if (email.length > MAX_EMAIL_LENGTH) {
|
|
30
|
-
throw new
|
|
32
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: address email must be <= ${String(MAX_EMAIL_LENGTH)} characters`);
|
|
31
33
|
}
|
|
32
34
|
assertSafeAddressField("email", email);
|
|
33
35
|
return { email };
|
package/dist/packem_shared/{consumeQueuedSend-BEKOdaxU.mjs → consumeQueuedSend-B9hTDOZ6.mjs}
RENAMED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const toQueuedPayload = (options) => {
|
|
2
4
|
return {
|
|
3
5
|
bcc: options.bcc,
|
|
@@ -13,7 +15,7 @@ const toQueuedPayload = (options) => {
|
|
|
13
15
|
};
|
|
14
16
|
const consumeQueuedSend = async (mailer, payload) => {
|
|
15
17
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
16
|
-
throw new
|
|
18
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: queue message body must be an object");
|
|
17
19
|
}
|
|
18
20
|
const candidate = payload;
|
|
19
21
|
if (typeof candidate.subject !== "string") {
|
|
@@ -22,7 +24,7 @@ const consumeQueuedSend = async (mailer, payload) => {
|
|
|
22
24
|
const recipientIsString = typeof candidate.to === "string";
|
|
23
25
|
const recipientIsStringArray = Array.isArray(candidate.to) && candidate.to.every((value) => typeof value === "string");
|
|
24
26
|
if (!recipientIsString && !recipientIsStringArray) {
|
|
25
|
-
throw new
|
|
27
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: queue message `to` must be a string or string[]");
|
|
26
28
|
}
|
|
27
29
|
const assertOptionalString = (field, value) => {
|
|
28
30
|
if (value === void 0) {
|
package/dist/packem_shared/{createCaptureSink-DeihS4LH.mjs → createCaptureSink-Di-5AJUs.mjs}
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { createCaptureTransport } from './createCaptureTransport-Crz_8822.mjs';
|
|
2
|
-
import createMailer from './createMailer-
|
|
3
|
+
import createMailer from './createMailer-B4Z_Cgiu.mjs';
|
|
3
4
|
import { a as applyJurisdiction } from './shard-CJ-TvmfT.mjs';
|
|
4
5
|
|
|
5
6
|
const RECORD_MAIL_OP = "__lunora_admin__:recordMail";
|
|
@@ -9,7 +10,7 @@ const ENVIRONMENT_VARS = ["CF_ENV", "ENVIRONMENT", "NODE_ENV", "WORKER_ENV"];
|
|
|
9
10
|
const requireStringEnv = (env, name) => {
|
|
10
11
|
const value = env[name];
|
|
11
12
|
if (typeof value !== "string" || value === "") {
|
|
12
|
-
throw new
|
|
13
|
+
throw new LunoraError("INTERNAL", `@lunora/mail: missing env var \`${name}\` — set it in .dev.vars (and \`wrangler secret put ${name}\` for secrets).`);
|
|
13
14
|
}
|
|
14
15
|
return value;
|
|
15
16
|
};
|
|
@@ -55,7 +56,8 @@ const createMailerFromEnv = (env, options = {}) => {
|
|
|
55
56
|
if (apiKey !== void 0 && apiKey !== "") {
|
|
56
57
|
return createMailer({ apiKey, from });
|
|
57
58
|
}
|
|
58
|
-
throw new
|
|
59
|
+
throw new LunoraError(
|
|
60
|
+
"INTERNAL",
|
|
59
61
|
"@lunora/mail: no transport configured — provide `cloudflareSend` (a SEND_EMAIL binding) or RESEND_API_KEY, or run in a dev environment to capture."
|
|
60
62
|
);
|
|
61
63
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { cloudflareEmailProvider } from '@visulima/email/providers/cloudflare-email';
|
|
2
|
-
import { r as requireRecipients, t as toProviderEmail, i as interpretSendResult } from './provider-transport-
|
|
3
|
+
import { r as requireRecipients, t as toProviderEmail, i as interpretSendResult } from './provider-transport-C0xHi3oy.mjs';
|
|
3
4
|
|
|
4
5
|
const createCloudflareTransport = (options) => {
|
|
5
6
|
const provider = cloudflareEmailProvider({ send: options.send });
|
|
@@ -9,11 +10,12 @@ const createCloudflareTransport = (options) => {
|
|
|
9
10
|
const hasCc = payload.cc !== void 0 && payload.cc.length > 0;
|
|
10
11
|
const hasBcc = payload.bcc !== void 0 && payload.bcc.length > 0;
|
|
11
12
|
if (hasCc || hasBcc) {
|
|
12
|
-
throw new
|
|
13
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: Cloudflare Email Workers does not support cc/bcc — fan out one send per recipient instead");
|
|
13
14
|
}
|
|
14
15
|
const { first, list } = requireRecipients(payload.to);
|
|
15
16
|
if (list.length > 1) {
|
|
16
|
-
throw new
|
|
17
|
+
throw new LunoraError(
|
|
18
|
+
"INTERNAL",
|
|
17
19
|
`@lunora/mail: Cloudflare Email Workers is single-recipient but received ${String(list.length)} \`to\` addresses — fan out one send per recipient instead`
|
|
18
20
|
);
|
|
19
21
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { a as applyJurisdiction } from './shard-CJ-TvmfT.mjs';
|
|
2
3
|
|
|
3
4
|
const GENERIC_REJECT_REASON = "message could not be processed";
|
|
@@ -14,7 +15,7 @@ const createInboundEmailHandler = (options) => {
|
|
|
14
15
|
if (options.verify) {
|
|
15
16
|
const verified = await options.verify(parsed, context);
|
|
16
17
|
if (verified === false) {
|
|
17
|
-
throw new
|
|
18
|
+
throw new LunoraError("INTERNAL", "@lunora/mail/inbound: sender verification rejected the message");
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
await options.dispatch(parsed, context);
|
|
@@ -53,7 +54,7 @@ const dispatchToLunoraFunction = (options) => {
|
|
|
53
54
|
return async (email, context) => {
|
|
54
55
|
const adminToken = options.adminToken ?? (typeof context.env["LUNORA_ADMIN_TOKEN"] === "string" ? context.env["LUNORA_ADMIN_TOKEN"] : void 0);
|
|
55
56
|
if (adminToken === void 0 || adminToken === "") {
|
|
56
|
-
throw new
|
|
57
|
+
throw new LunoraError("INTERNAL", "@lunora/mail/inbound: missing LUNORA_ADMIN_TOKEN — cannot authorize inbound dispatch to the shard RPC.");
|
|
57
58
|
}
|
|
58
59
|
const envelope = {
|
|
59
60
|
args: resolveArgs(email, context),
|
|
@@ -68,13 +69,13 @@ const dispatchToLunoraFunction = (options) => {
|
|
|
68
69
|
method: "POST"
|
|
69
70
|
});
|
|
70
71
|
if (response.ok === false) {
|
|
71
|
-
throw new
|
|
72
|
+
throw new LunoraError("INTERNAL", `@lunora/mail/inbound: dispatch to \`${options.functionPath}\` failed (HTTP ${String(response.status ?? "?")}).`);
|
|
72
73
|
}
|
|
73
74
|
const body = await response.json();
|
|
74
75
|
if (typeof body === "object" && body !== null && "error" in body) {
|
|
75
76
|
const { error } = body;
|
|
76
77
|
if (error !== void 0 && error !== null) {
|
|
77
|
-
throw new
|
|
78
|
+
throw new LunoraError("INTERNAL", `@lunora/mail/inbound: dispatch to \`${options.functionPath}\` returned an error: ${JSON.stringify(error)}`);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { a as assertSafeHeaderValue, b as assertSafeAddresses } from './address-vSUAVU2T.mjs';
|
|
2
3
|
import { isCaptureTransport } from './createCaptureTransport-Crz_8822.mjs';
|
|
3
|
-
import { createCloudflareTransport } from './createCloudflareTransport-
|
|
4
|
-
import { toQueuedPayload } from './consumeQueuedSend-
|
|
4
|
+
import { createCloudflareTransport } from './createCloudflareTransport-DXh1nUNi.mjs';
|
|
5
|
+
import { toQueuedPayload } from './consumeQueuedSend-B9hTDOZ6.mjs';
|
|
5
6
|
import renderEmail from './renderEmail-hyS1bpVP.mjs';
|
|
6
|
-
import createResendTransport from './createResendTransport-
|
|
7
|
+
import createResendTransport from './createResendTransport-CMQWjZoi.mjs';
|
|
7
8
|
|
|
8
9
|
const buildDefaultTransport = (options) => {
|
|
9
10
|
if (options.cloudflareSend) {
|
|
@@ -12,11 +13,14 @@ const buildDefaultTransport = (options) => {
|
|
|
12
13
|
if (options.apiKey) {
|
|
13
14
|
return createResendTransport(options.apiKey, options.from);
|
|
14
15
|
}
|
|
15
|
-
throw new
|
|
16
|
+
throw new LunoraError(
|
|
17
|
+
"INTERNAL",
|
|
18
|
+
"@lunora/mail: a transport is required — pass `transport`, `cloudflareSend` (Cloudflare Email Workers, the default), or `apiKey` (Resend)"
|
|
19
|
+
);
|
|
16
20
|
};
|
|
17
21
|
const createMailer = (options) => {
|
|
18
22
|
if (!options.from) {
|
|
19
|
-
throw new
|
|
23
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: `from` is required");
|
|
20
24
|
}
|
|
21
25
|
const transport = options.transport ?? buildDefaultTransport(options);
|
|
22
26
|
const buildPayload = async (options_) => {
|
|
@@ -65,7 +69,7 @@ const createMailer = (options) => {
|
|
|
65
69
|
await transport.send(captured);
|
|
66
70
|
return { queued: true };
|
|
67
71
|
}
|
|
68
|
-
throw new
|
|
72
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: `queue` binding is required for mailer.queue()");
|
|
69
73
|
}
|
|
70
74
|
const payload = await buildPayload(options_);
|
|
71
75
|
await options.queue.send(toQueuedPayload(payload));
|
package/dist/packem_shared/{createResendTransport-oNIorpzv.mjs → createResendTransport-CMQWjZoi.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resendProvider } from '@visulima/email/providers/resend';
|
|
2
|
-
import { r as requireRecipients, t as toProviderEmail, i as interpretSendResult } from './provider-transport-
|
|
2
|
+
import { r as requireRecipients, t as toProviderEmail, i as interpretSendResult } from './provider-transport-C0xHi3oy.mjs';
|
|
3
3
|
|
|
4
4
|
const createResendTransport = (apiKey, defaultFrom) => {
|
|
5
5
|
const provider = resendProvider({ apiKey });
|
package/dist/packem_shared/{provider-transport-C5CVbjRF.mjs → provider-transport-C0xHi3oy.mjs}
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { t as toAddressList, c as toAddress } from './address-vSUAVU2T.mjs';
|
|
2
3
|
|
|
3
4
|
const reasonOf = (rawError) => {
|
|
4
5
|
if (rawError instanceof Error) {
|
|
@@ -19,7 +20,7 @@ const requireRecipients = (to) => {
|
|
|
19
20
|
const list = toAddressList(to);
|
|
20
21
|
const [first] = list ?? [];
|
|
21
22
|
if (!list || first === void 0) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: at least one recipient is required");
|
|
23
24
|
}
|
|
24
25
|
return { first, list };
|
|
25
26
|
};
|
|
@@ -39,7 +40,7 @@ const toProviderEmail = (payload, defaultFrom, to) => {
|
|
|
39
40
|
const interpretSendResult = (result) => {
|
|
40
41
|
if (!result.success || !result.data) {
|
|
41
42
|
console.error(`@lunora/mail: send failed: ${reasonOf(result.error)}`);
|
|
42
|
-
throw new
|
|
43
|
+
throw new LunoraError("INTERNAL", "@lunora/mail: send failed");
|
|
43
44
|
}
|
|
44
45
|
return { id: result.data.messageId };
|
|
45
46
|
};
|
package/dist/testing.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const GET_CAPTURED_MAIL_OP = "__lunora_admin__:getCapturedMail";
|
|
2
4
|
const DEFAULT_RPC_PATH = "/_lunora/rpc";
|
|
3
5
|
const TRAILING_SLASH = /\/$/;
|
|
@@ -14,7 +16,7 @@ const listCapturedMail = async (options) => {
|
|
|
14
16
|
method: "POST"
|
|
15
17
|
});
|
|
16
18
|
if (!response.ok) {
|
|
17
|
-
throw new
|
|
19
|
+
throw new LunoraError("INTERNAL", `@lunora/mail/testing: getCapturedMail failed (HTTP ${String(response.status)})`);
|
|
18
20
|
}
|
|
19
21
|
const body = await response.json();
|
|
20
22
|
return body.result?.entries ?? [];
|
|
@@ -32,7 +34,8 @@ const waitForMail = async (options) => {
|
|
|
32
34
|
return match;
|
|
33
35
|
}
|
|
34
36
|
if (Date.now() >= deadline) {
|
|
35
|
-
throw new
|
|
37
|
+
throw new LunoraError(
|
|
38
|
+
"INTERNAL",
|
|
36
39
|
`@lunora/mail/testing: no mail to "${options.to}"${options.subjectMatch === void 0 ? "" : ` matching "${options.subjectMatch}"`} within ${String(timeoutMs)}ms`
|
|
37
40
|
);
|
|
38
41
|
}
|
|
@@ -51,7 +54,10 @@ const extractLink = (mail, options = {}) => {
|
|
|
51
54
|
return link;
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
|
-
throw new
|
|
57
|
+
throw new LunoraError(
|
|
58
|
+
"INTERNAL",
|
|
59
|
+
`@lunora/mail/testing: no link${options.match === void 0 ? "" : ` containing "${options.match}"`} found in the captured message`
|
|
60
|
+
);
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
export { extractLink, listCapturedMail, waitForMail };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/mail",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"description": "Email for Lunora: Resend adapter, TSX templates, and queue-backed sends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.1",
|
|
57
58
|
"@react-email/render": "2.0.9",
|
|
58
59
|
"@visulima/email": "1.0.0-alpha.41",
|
|
59
60
|
"postal-mime": "2.7.5"
|