@lunora/mail 1.0.0-alpha.12 → 1.0.0-alpha.120
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.d.mts +324 -78
- package/dist/inbound/index.d.ts +324 -78
- package/dist/inbound/index.mjs +1 -2
- package/dist/index.d.mts +143 -96
- package/dist/index.d.ts +143 -96
- package/dist/index.mjs +1 -7
- package/dist/packem_shared/address-oKy_d0aG.mjs +3 -0
- package/dist/packem_shared/authenticatesFrom-BOX-Paur.mjs +1 -0
- package/dist/packem_shared/capture-transport.d-BD67fxbB.d.mts +130 -0
- package/dist/packem_shared/capture-transport.d-BD67fxbB.d.ts +130 -0
- package/dist/packem_shared/consumeQueuedSend-CRfALnCU.mjs +1 -0
- package/dist/packem_shared/createCaptureSink-BIu-c0Kd.mjs +1 -0
- package/dist/packem_shared/createCaptureTransport-DZhMUmaI.mjs +1 -0
- package/dist/packem_shared/createCloudflareTransport-bWoNSnK_.mjs +1 -0
- package/dist/packem_shared/createInboundEmailHandler-QnZHQZpF.mjs +1 -0
- package/dist/packem_shared/createMailer-ImyDcZuj.mjs +1 -0
- package/dist/packem_shared/createResendTransport-BMjRlHmy.mjs +1 -0
- package/dist/packem_shared/provider-transport-BnA39QbI.mjs +1 -0
- package/dist/packem_shared/renderEmail-CkxYrU2s.mjs +1 -0
- package/dist/packem_shared/shard-C5t-b7FX.mjs +1 -0
- package/dist/packem_shared/shard.d-DVADjmEJ.d.mts +29 -0
- package/dist/packem_shared/shard.d-DVADjmEJ.d.ts +29 -0
- package/dist/testing.d.mts +9 -9
- package/dist/testing.d.ts +9 -9
- package/dist/testing.mjs +1 -63
- package/package.json +5 -5
- package/dist/packem_shared/address-vSUAVU2T.mjs +0 -64
- package/dist/packem_shared/capture-transport.d-ChnhdPO2.d.mts +0 -117
- package/dist/packem_shared/capture-transport.d-ChnhdPO2.d.ts +0 -117
- package/dist/packem_shared/consumeQueuedSend-B9hTDOZ6.mjs +0 -77
- package/dist/packem_shared/createCaptureSink-Di-5AJUs.mjs +0 -65
- package/dist/packem_shared/createCaptureTransport-Crz_8822.mjs +0 -11
- package/dist/packem_shared/createCloudflareTransport-DXh1nUNi.mjs +0 -28
- package/dist/packem_shared/createInboundEmailHandler-BW_s0-Vc.mjs +0 -84
- package/dist/packem_shared/createMailer-B4Z_Cgiu.mjs +0 -81
- package/dist/packem_shared/createResendTransport-CMQWjZoi.mjs +0 -16
- package/dist/packem_shared/parseInboundEmail-6lafQBT1.mjs +0 -72
- package/dist/packem_shared/provider-transport-C0xHi3oy.mjs +0 -48
- package/dist/packem_shared/renderEmail-hyS1bpVP.mjs +0 -8
- package/dist/packem_shared/shard-CJ-TvmfT.mjs +0 -13
- package/dist/packem_shared/shard.d-CL2Lmliv.d.mts +0 -39
- package/dist/packem_shared/shard.d-CL2Lmliv.d.ts +0 -39
package/dist/index.d.mts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-
|
|
2
|
-
export { type C as CapturedMail, type
|
|
3
|
-
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-
|
|
1
|
+
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, Q as QueueLike, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-BD67fxbB.mjs";
|
|
2
|
+
export { type C as CapturedMail, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-BD67fxbB.mjs";
|
|
3
|
+
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-DVADjmEJ.mjs";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
6
|
-
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
|
7
|
-
* runtime owns the binding, so the caller supplies this thin callback — keeping
|
|
8
|
-
* `@lunora/mail` free of a `cloudflare:email` import and unit-testable. Wire it
|
|
9
|
-
* in your project as:
|
|
10
|
-
*
|
|
11
|
-
* ```ts
|
|
12
|
-
* send: async (from, to, raw) => {
|
|
13
|
-
* const { EmailMessage } = await import("cloudflare:email");
|
|
14
|
-
* await env.SEND_EMAIL.send(new EmailMessage(from, to, raw));
|
|
15
|
-
* }
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
6
|
+
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
|
7
|
+
* runtime owns the binding, so the caller supplies this thin callback — keeping
|
|
8
|
+
* `@lunora/mail` free of a `cloudflare:email` import and unit-testable. Wire it
|
|
9
|
+
* in your project as:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* send: async (from, to, raw) => {
|
|
13
|
+
* const { EmailMessage } = await import("cloudflare:email");
|
|
14
|
+
* await env.SEND_EMAIL.send(new EmailMessage(from, to, raw));
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
18
|
type CloudflareSend = (from: string, to: string, raw: string) => Promise<void>;
|
|
19
19
|
interface CloudflareTransportOptions {
|
|
20
20
|
/** Default sender used when a `SendOptions.from` isn't supplied. */
|
|
@@ -23,75 +23,89 @@ interface CloudflareTransportOptions {
|
|
|
23
23
|
send: CloudflareSend;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Build the default Cloudflare Email Workers transport via `@visulima/email`.
|
|
27
|
-
* Cloudflare's `send_email` binding is **single-recipient** and only delivers to
|
|
28
|
-
* **verified Email Routing destination addresses**, and the underlying provider
|
|
29
|
-
* rejects any `cc`/`bcc` or a non-single `to` outright. So this enforces a single
|
|
30
|
-
* `to` recipient (throwing a clear error instead of silently dropping the rest)
|
|
31
|
-
* and rejects `cc`/`bcc` rather than forwarding them into a generic "send failed".
|
|
32
|
-
* Multi-recipient transactional mail must fan out one send per recipient at the
|
|
33
|
-
* call site. In dev the capture transport intercepts before this runs, so the
|
|
34
|
-
* verified-address constraint never bites the dev loop.
|
|
35
|
-
*/
|
|
26
|
+
* Build the default Cloudflare Email Workers transport via `@visulima/email`.
|
|
27
|
+
* Cloudflare's `send_email` binding is **single-recipient** and only delivers to
|
|
28
|
+
* **verified Email Routing destination addresses**, and the underlying provider
|
|
29
|
+
* rejects any `cc`/`bcc` or a non-single `to` outright. So this enforces a single
|
|
30
|
+
* `to` recipient (throwing a clear error instead of silently dropping the rest)
|
|
31
|
+
* and rejects `cc`/`bcc` rather than forwarding them into a generic "send failed".
|
|
32
|
+
* Multi-recipient transactional mail must fan out one send per recipient at the
|
|
33
|
+
* call site. In dev the capture transport intercepts before this runs, so the
|
|
34
|
+
* verified-address constraint never bites the dev loop.
|
|
35
|
+
*/
|
|
36
36
|
declare const createCloudflareTransport: (options: CloudflareTransportOptions) => MailTransport;
|
|
37
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
|
-
*/
|
|
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
|
+
*/
|
|
54
54
|
declare const createMailer: (options: LunoraMailOptions) => Mailer;
|
|
55
55
|
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
56
56
|
type MailEnv = Record<string, unknown>;
|
|
57
57
|
/** Options for {@link createMailerFromEnv}. */
|
|
58
58
|
interface FromEnvOptions {
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* RFC 822 send callback bound to the Worker's Cloudflare Email binding (the
|
|
61
|
+
* default transport). Honoured whenever it is passed, whatever the binding is
|
|
62
|
+
* named: passing it is the caller's choice of transport, so pass it only when
|
|
63
|
+
* the binding exists.
|
|
64
|
+
*/
|
|
60
65
|
cloudflareSend?: CloudflareSend;
|
|
61
66
|
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
* Pin the captured-mail inbox shard to a Cloudflare data-residency
|
|
68
|
+
* jurisdiction. Defaults to the jurisdiction the schema declares
|
|
69
|
+
* (`.jurisdiction("eu")`), so a generated app needs nothing here; set it only
|
|
70
|
+
* for a hand-written worker, to the same value as its `jurisdiction`. A value
|
|
71
|
+
* that contradicts the schema's throws.
|
|
72
|
+
*/
|
|
66
73
|
jurisdiction?: DurableObjectJurisdiction;
|
|
74
|
+
/** Cloudflare Queue binding backing `mailer.queue()`; without it `queue()` throws outside capture mode. */
|
|
75
|
+
queue?: QueueLike;
|
|
67
76
|
/** Shard the captured-mail inbox lives on; override if your worker sets a custom `defaultShardKey`. */
|
|
68
77
|
rootShard?: string;
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
|
-
* Whether outbound mail should be captured (into the studio inbox) rather than
|
|
72
|
-
* delivered. Explicit `LUNORA_MAIL_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`)
|
|
73
|
-
* always wins; unset, capture is on only
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
80
|
+
* Whether outbound mail should be captured (into the studio inbox) rather than
|
|
81
|
+
* delivered. Explicit `LUNORA_MAIL_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`)
|
|
82
|
+
* always wins; unset, capture is on only when every set environment var (`CF_ENV`,
|
|
83
|
+
* `ENVIRONMENT`, `NODE_ENV`, `WORKER_ENV`) names a development one. It does
|
|
84
|
+
* NOT fall back to "no SEND_EMAIL binding ⇒ capture" — a production deploy that
|
|
85
|
+
* forgot the binding must fail loudly on send, not silently swallow mail.
|
|
86
|
+
*/
|
|
77
87
|
declare const shouldCaptureMail: (env: MailEnv) => boolean;
|
|
78
88
|
/**
|
|
79
|
-
* Build the {@link MailboxSink} that records a captured message into the studio's
|
|
80
|
-
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
81
|
-
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
82
|
-
* the `SHARD` binding or `LUNORA_ADMIN_TOKEN` it returns a sentinel id so a send
|
|
83
|
-
* never fails for lack of somewhere to record.
|
|
84
|
-
*/
|
|
89
|
+
* Build the {@link MailboxSink} that records a captured message into the studio's
|
|
90
|
+
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
91
|
+
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
92
|
+
* the `SHARD` binding or `LUNORA_ADMIN_TOKEN` it returns a sentinel id so a send
|
|
93
|
+
* never fails for lack of somewhere to record — but it says so first (see below).
|
|
94
|
+
*/
|
|
85
95
|
declare const createCaptureSink: (env: MailEnv, rootShard?: string, jurisdiction?: DurableObjectJurisdiction) => MailboxSink;
|
|
86
96
|
/**
|
|
87
|
-
* Build a {@link Mailer} from a Worker `env`. In a dev environment every send is
|
|
88
|
-
* captured into the studio's Mail inbox; otherwise it delivers via the supplied
|
|
89
|
-
* `cloudflareSend` (
|
|
90
|
-
*
|
|
91
|
-
* misconfigured production deploy fails loudly instead of silently dropping mail
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
97
|
+
* Build a {@link Mailer} from a Worker `env`. In a dev environment every send is
|
|
98
|
+
* captured into the studio's Mail inbox; otherwise it delivers via the supplied
|
|
99
|
+
* `cloudflareSend` (when passed) or,
|
|
100
|
+
* failing that, `RESEND_API_KEY`. With no real transport `send()` rejects, so a
|
|
101
|
+
* misconfigured production deploy fails loudly instead of silently dropping mail
|
|
102
|
+
* — while `queue()` still works for a producer-only worker.
|
|
103
|
+
*
|
|
104
|
+
* A queue consumer should build its mailer here too: `consumeQueuedSend` goes
|
|
105
|
+
* through `send()`, so in dev queued mail is captured at consume time.
|
|
106
|
+
*
|
|
107
|
+
* `MAIL_FROM` is required (the default sender).
|
|
108
|
+
*/
|
|
95
109
|
declare const createMailerFromEnv: (env: MailEnv, options?: FromEnvOptions) => Mailer;
|
|
96
110
|
/** Serializable representation of a `SendOptions` payload — drops the `react` field. */
|
|
97
111
|
interface QueuedSend {
|
|
@@ -100,51 +114,84 @@ interface QueuedSend {
|
|
|
100
114
|
from?: string;
|
|
101
115
|
headers?: Record<string, string>;
|
|
102
116
|
html?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Stable dedup key, minted at enqueue time by `createMailer(...).queue()` — see
|
|
119
|
+
* `SendOptions.idempotencyKey`. Carried through untouched by `consumeQueuedSend`;
|
|
120
|
+
* a consumer wanting exactly-once delivery dedupes against its own store using it.
|
|
121
|
+
*/
|
|
122
|
+
idempotencyKey?: string;
|
|
103
123
|
replyTo?: string;
|
|
104
124
|
subject: string;
|
|
105
125
|
text?: string;
|
|
106
126
|
to: string | string[];
|
|
107
127
|
}
|
|
108
128
|
/**
|
|
109
|
-
* Narrow a `SendOptions` to its serializable `QueuedSend` shape by dropping the
|
|
110
|
-
* non-cloneable `react` field. React elements are not structured-cloneable, so
|
|
111
|
-
* the queue body must carry only the pre-rendered html/text and scalar fields.
|
|
112
|
-
*/
|
|
129
|
+
* Narrow a `SendOptions` to its serializable `QueuedSend` shape by dropping the
|
|
130
|
+
* non-cloneable `react` field. React elements are not structured-cloneable, so
|
|
131
|
+
* the queue body must carry only the pre-rendered html/text and scalar fields.
|
|
132
|
+
*/
|
|
113
133
|
declare const toQueuedPayload: (options: SendOptions) => QueuedSend;
|
|
114
134
|
/**
|
|
115
|
-
* Helper used by Queue consumers: rehydrate a `QueuedSend` payload and forward
|
|
116
|
-
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
117
|
-
* handler.
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
|
|
135
|
+
* Helper used by Queue consumers: rehydrate a `QueuedSend` payload and forward
|
|
136
|
+
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
137
|
+
* handler.
|
|
138
|
+
*
|
|
139
|
+
* DEDUPE IS THE CONSUMER'S JOB, and this helper does not do it. The payload's
|
|
140
|
+
* `idempotencyKey` was minted once at enqueue time so it survives redelivery, but
|
|
141
|
+
* nothing downstream reads it: no transport forwards it to the provider (Resend
|
|
142
|
+
* dedupes on an `Idempotency-Key` REQUEST header, which the provider client offers
|
|
143
|
+
* no hook for — its `headers` field becomes message headers in the body). A
|
|
144
|
+
* consumer that only acks is correct for the failure the ack covers and sends a
|
|
145
|
+
* duplicate for the one it does not: the provider accepted the message and the
|
|
146
|
+
* worker died before acking.
|
|
147
|
+
*
|
|
148
|
+
* The recipe below NARROWS that window; it does not close it, and nothing on
|
|
149
|
+
* this path can. The mark is written after the provider accepted the message, so
|
|
150
|
+
* a crash in between still redelivers and resends, and a KV read is eventually
|
|
151
|
+
* consistent — a redelivery seconds later can miss a mark that was written.
|
|
152
|
+
* Delivery is at-least-once end to end. An app that must not double-send needs a
|
|
153
|
+
* strongly-consistent mark taken BEFORE the send (a Durable Object or D1 row
|
|
154
|
+
* claimed by this key, released on failure), and even then the send-then-crash
|
|
155
|
+
* window is only ever traded for a send-that-may-not-have-happened one.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* export default {
|
|
159
|
+
* queue: async (batch, env) => {
|
|
160
|
+
* // createMailerFromEnv, so dev captures the message here instead of sending it.
|
|
161
|
+
* const mailer = createMailerFromEnv(env);
|
|
162
|
+
* for (const message of batch.messages) {
|
|
163
|
+
* const { idempotencyKey } = message.body;
|
|
164
|
+
* if (await env.SENT.get(idempotencyKey)) { message.ack(); continue; }
|
|
165
|
+
* await consumeQueuedSend(mailer, message.body);
|
|
166
|
+
* await env.SENT.put(idempotencyKey, "1", { expirationTtl: 86_400 });
|
|
167
|
+
* message.ack();
|
|
168
|
+
* }
|
|
169
|
+
* },
|
|
170
|
+
* };
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
130
173
|
declare const consumeQueuedSend: (mailer: Mailer, payload: unknown) => Promise<{
|
|
131
174
|
id: string;
|
|
132
175
|
}>;
|
|
133
176
|
/**
|
|
134
|
-
* Render a React element to an HTML/text pair suitable for inlining into a
|
|
135
|
-
* provider payload. Wraps `@react-email/render` so we can swap to
|
|
136
|
-
* `@visulima/email`'s react-email template engine without touching callers.
|
|
137
|
-
|
|
177
|
+
* Render a React element to an HTML/text pair suitable for inlining into a
|
|
178
|
+
* provider payload. Wraps `@react-email/render` so we can swap to
|
|
179
|
+
* `@visulima/email`'s react-email template engine without touching callers.
|
|
180
|
+
*
|
|
181
|
+
* The renderer is imported lazily so its top-level evaluation (prettier and
|
|
182
|
+
* html-to-text, in its workerd build) is deferred until the first React render
|
|
183
|
+
* instead of running on cold start. Bundlers may still include the code.
|
|
184
|
+
*/
|
|
138
185
|
declare const renderEmail: (element: ReactElement) => Promise<{
|
|
139
186
|
html: string;
|
|
140
187
|
text: string;
|
|
141
188
|
}>;
|
|
142
189
|
/**
|
|
143
|
-
* Build a Resend-backed transport via `@visulima/email`. Wraps the provider's
|
|
144
|
-
* `sendEmail()` into the minimal `{ send(payload) -> { id } }` shape the rest of
|
|
145
|
-
* `@lunora/mail` consumes. Kept reachable as a named export so a project that
|
|
146
|
-
* prefers Resend over the Cloudflare default can pass
|
|
147
|
-
* `transport: createResendTransport(apiKey, from)` to `createMailer`.
|
|
148
|
-
*/
|
|
190
|
+
* Build a Resend-backed transport via `@visulima/email`. Wraps the provider's
|
|
191
|
+
* `sendEmail()` into the minimal `{ send(payload) -> { id } }` shape the rest of
|
|
192
|
+
* `@lunora/mail` consumes. Kept reachable as a named export so a project that
|
|
193
|
+
* prefers Resend over the Cloudflare default can pass
|
|
194
|
+
* `transport: createResendTransport(apiKey, from)` to `createMailer`.
|
|
195
|
+
*/
|
|
149
196
|
declare const createResendTransport: (apiKey: string, defaultFrom: string) => MailTransport;
|
|
150
|
-
export { type CloudflareSend, type CloudflareTransportOptions, type FromEnvOptions, type LunoraMailOptions, type MailEnv, type MailTransport, type MailboxSink, type Mailer, type QueuedSend, type SendOptions, consumeQueuedSend, createCaptureSink, createCloudflareTransport, createMailer, createMailerFromEnv, createResendTransport, renderEmail, shouldCaptureMail, toQueuedPayload };
|
|
197
|
+
export { type CloudflareSend, type CloudflareTransportOptions, type FromEnvOptions, type LunoraMailOptions, type MailEnv, type MailTransport, type MailboxSink, type Mailer, type QueueLike, type QueuedSend, type SendOptions, consumeQueuedSend, createCaptureSink, createCloudflareTransport, createMailer, createMailerFromEnv, createResendTransport, renderEmail, shouldCaptureMail, toQueuedPayload };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-
|
|
2
|
-
export { type C as CapturedMail, type
|
|
3
|
-
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-
|
|
1
|
+
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, Q as QueueLike, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-BD67fxbB.js";
|
|
2
|
+
export { type C as CapturedMail, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-BD67fxbB.js";
|
|
3
|
+
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-DVADjmEJ.js";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
6
|
-
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
|
7
|
-
* runtime owns the binding, so the caller supplies this thin callback — keeping
|
|
8
|
-
* `@lunora/mail` free of a `cloudflare:email` import and unit-testable. Wire it
|
|
9
|
-
* in your project as:
|
|
10
|
-
*
|
|
11
|
-
* ```ts
|
|
12
|
-
* send: async (from, to, raw) => {
|
|
13
|
-
* const { EmailMessage } = await import("cloudflare:email");
|
|
14
|
-
* await env.SEND_EMAIL.send(new EmailMessage(from, to, raw));
|
|
15
|
-
* }
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
6
|
+
* Sends a raw RFC 822 message through a Worker's Email send binding. The Workers
|
|
7
|
+
* runtime owns the binding, so the caller supplies this thin callback — keeping
|
|
8
|
+
* `@lunora/mail` free of a `cloudflare:email` import and unit-testable. Wire it
|
|
9
|
+
* in your project as:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* send: async (from, to, raw) => {
|
|
13
|
+
* const { EmailMessage } = await import("cloudflare:email");
|
|
14
|
+
* await env.SEND_EMAIL.send(new EmailMessage(from, to, raw));
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
18
|
type CloudflareSend = (from: string, to: string, raw: string) => Promise<void>;
|
|
19
19
|
interface CloudflareTransportOptions {
|
|
20
20
|
/** Default sender used when a `SendOptions.from` isn't supplied. */
|
|
@@ -23,75 +23,89 @@ interface CloudflareTransportOptions {
|
|
|
23
23
|
send: CloudflareSend;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Build the default Cloudflare Email Workers transport via `@visulima/email`.
|
|
27
|
-
* Cloudflare's `send_email` binding is **single-recipient** and only delivers to
|
|
28
|
-
* **verified Email Routing destination addresses**, and the underlying provider
|
|
29
|
-
* rejects any `cc`/`bcc` or a non-single `to` outright. So this enforces a single
|
|
30
|
-
* `to` recipient (throwing a clear error instead of silently dropping the rest)
|
|
31
|
-
* and rejects `cc`/`bcc` rather than forwarding them into a generic "send failed".
|
|
32
|
-
* Multi-recipient transactional mail must fan out one send per recipient at the
|
|
33
|
-
* call site. In dev the capture transport intercepts before this runs, so the
|
|
34
|
-
* verified-address constraint never bites the dev loop.
|
|
35
|
-
*/
|
|
26
|
+
* Build the default Cloudflare Email Workers transport via `@visulima/email`.
|
|
27
|
+
* Cloudflare's `send_email` binding is **single-recipient** and only delivers to
|
|
28
|
+
* **verified Email Routing destination addresses**, and the underlying provider
|
|
29
|
+
* rejects any `cc`/`bcc` or a non-single `to` outright. So this enforces a single
|
|
30
|
+
* `to` recipient (throwing a clear error instead of silently dropping the rest)
|
|
31
|
+
* and rejects `cc`/`bcc` rather than forwarding them into a generic "send failed".
|
|
32
|
+
* Multi-recipient transactional mail must fan out one send per recipient at the
|
|
33
|
+
* call site. In dev the capture transport intercepts before this runs, so the
|
|
34
|
+
* verified-address constraint never bites the dev loop.
|
|
35
|
+
*/
|
|
36
36
|
declare const createCloudflareTransport: (options: CloudflareTransportOptions) => MailTransport;
|
|
37
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
|
-
*/
|
|
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
|
+
*/
|
|
54
54
|
declare const createMailer: (options: LunoraMailOptions) => Mailer;
|
|
55
55
|
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
56
56
|
type MailEnv = Record<string, unknown>;
|
|
57
57
|
/** Options for {@link createMailerFromEnv}. */
|
|
58
58
|
interface FromEnvOptions {
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* RFC 822 send callback bound to the Worker's Cloudflare Email binding (the
|
|
61
|
+
* default transport). Honoured whenever it is passed, whatever the binding is
|
|
62
|
+
* named: passing it is the caller's choice of transport, so pass it only when
|
|
63
|
+
* the binding exists.
|
|
64
|
+
*/
|
|
60
65
|
cloudflareSend?: CloudflareSend;
|
|
61
66
|
/**
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
* Pin the captured-mail inbox shard to a Cloudflare data-residency
|
|
68
|
+
* jurisdiction. Defaults to the jurisdiction the schema declares
|
|
69
|
+
* (`.jurisdiction("eu")`), so a generated app needs nothing here; set it only
|
|
70
|
+
* for a hand-written worker, to the same value as its `jurisdiction`. A value
|
|
71
|
+
* that contradicts the schema's throws.
|
|
72
|
+
*/
|
|
66
73
|
jurisdiction?: DurableObjectJurisdiction;
|
|
74
|
+
/** Cloudflare Queue binding backing `mailer.queue()`; without it `queue()` throws outside capture mode. */
|
|
75
|
+
queue?: QueueLike;
|
|
67
76
|
/** Shard the captured-mail inbox lives on; override if your worker sets a custom `defaultShardKey`. */
|
|
68
77
|
rootShard?: string;
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
|
-
* Whether outbound mail should be captured (into the studio inbox) rather than
|
|
72
|
-
* delivered. Explicit `LUNORA_MAIL_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`)
|
|
73
|
-
* always wins; unset, capture is on only
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
|
|
80
|
+
* Whether outbound mail should be captured (into the studio inbox) rather than
|
|
81
|
+
* delivered. Explicit `LUNORA_MAIL_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`)
|
|
82
|
+
* always wins; unset, capture is on only when every set environment var (`CF_ENV`,
|
|
83
|
+
* `ENVIRONMENT`, `NODE_ENV`, `WORKER_ENV`) names a development one. It does
|
|
84
|
+
* NOT fall back to "no SEND_EMAIL binding ⇒ capture" — a production deploy that
|
|
85
|
+
* forgot the binding must fail loudly on send, not silently swallow mail.
|
|
86
|
+
*/
|
|
77
87
|
declare const shouldCaptureMail: (env: MailEnv) => boolean;
|
|
78
88
|
/**
|
|
79
|
-
* Build the {@link MailboxSink} that records a captured message into the studio's
|
|
80
|
-
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
81
|
-
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
82
|
-
* the `SHARD` binding or `LUNORA_ADMIN_TOKEN` it returns a sentinel id so a send
|
|
83
|
-
* never fails for lack of somewhere to record.
|
|
84
|
-
*/
|
|
89
|
+
* Build the {@link MailboxSink} that records a captured message into the studio's
|
|
90
|
+
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
91
|
+
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
92
|
+
* the `SHARD` binding or `LUNORA_ADMIN_TOKEN` it returns a sentinel id so a send
|
|
93
|
+
* never fails for lack of somewhere to record — but it says so first (see below).
|
|
94
|
+
*/
|
|
85
95
|
declare const createCaptureSink: (env: MailEnv, rootShard?: string, jurisdiction?: DurableObjectJurisdiction) => MailboxSink;
|
|
86
96
|
/**
|
|
87
|
-
* Build a {@link Mailer} from a Worker `env`. In a dev environment every send is
|
|
88
|
-
* captured into the studio's Mail inbox; otherwise it delivers via the supplied
|
|
89
|
-
* `cloudflareSend` (
|
|
90
|
-
*
|
|
91
|
-
* misconfigured production deploy fails loudly instead of silently dropping mail
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
97
|
+
* Build a {@link Mailer} from a Worker `env`. In a dev environment every send is
|
|
98
|
+
* captured into the studio's Mail inbox; otherwise it delivers via the supplied
|
|
99
|
+
* `cloudflareSend` (when passed) or,
|
|
100
|
+
* failing that, `RESEND_API_KEY`. With no real transport `send()` rejects, so a
|
|
101
|
+
* misconfigured production deploy fails loudly instead of silently dropping mail
|
|
102
|
+
* — while `queue()` still works for a producer-only worker.
|
|
103
|
+
*
|
|
104
|
+
* A queue consumer should build its mailer here too: `consumeQueuedSend` goes
|
|
105
|
+
* through `send()`, so in dev queued mail is captured at consume time.
|
|
106
|
+
*
|
|
107
|
+
* `MAIL_FROM` is required (the default sender).
|
|
108
|
+
*/
|
|
95
109
|
declare const createMailerFromEnv: (env: MailEnv, options?: FromEnvOptions) => Mailer;
|
|
96
110
|
/** Serializable representation of a `SendOptions` payload — drops the `react` field. */
|
|
97
111
|
interface QueuedSend {
|
|
@@ -100,51 +114,84 @@ interface QueuedSend {
|
|
|
100
114
|
from?: string;
|
|
101
115
|
headers?: Record<string, string>;
|
|
102
116
|
html?: string;
|
|
117
|
+
/**
|
|
118
|
+
* Stable dedup key, minted at enqueue time by `createMailer(...).queue()` — see
|
|
119
|
+
* `SendOptions.idempotencyKey`. Carried through untouched by `consumeQueuedSend`;
|
|
120
|
+
* a consumer wanting exactly-once delivery dedupes against its own store using it.
|
|
121
|
+
*/
|
|
122
|
+
idempotencyKey?: string;
|
|
103
123
|
replyTo?: string;
|
|
104
124
|
subject: string;
|
|
105
125
|
text?: string;
|
|
106
126
|
to: string | string[];
|
|
107
127
|
}
|
|
108
128
|
/**
|
|
109
|
-
* Narrow a `SendOptions` to its serializable `QueuedSend` shape by dropping the
|
|
110
|
-
* non-cloneable `react` field. React elements are not structured-cloneable, so
|
|
111
|
-
* the queue body must carry only the pre-rendered html/text and scalar fields.
|
|
112
|
-
*/
|
|
129
|
+
* Narrow a `SendOptions` to its serializable `QueuedSend` shape by dropping the
|
|
130
|
+
* non-cloneable `react` field. React elements are not structured-cloneable, so
|
|
131
|
+
* the queue body must carry only the pre-rendered html/text and scalar fields.
|
|
132
|
+
*/
|
|
113
133
|
declare const toQueuedPayload: (options: SendOptions) => QueuedSend;
|
|
114
134
|
/**
|
|
115
|
-
* Helper used by Queue consumers: rehydrate a `QueuedSend` payload and forward
|
|
116
|
-
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
117
|
-
* handler.
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
|
|
135
|
+
* Helper used by Queue consumers: rehydrate a `QueuedSend` payload and forward
|
|
136
|
+
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
137
|
+
* handler.
|
|
138
|
+
*
|
|
139
|
+
* DEDUPE IS THE CONSUMER'S JOB, and this helper does not do it. The payload's
|
|
140
|
+
* `idempotencyKey` was minted once at enqueue time so it survives redelivery, but
|
|
141
|
+
* nothing downstream reads it: no transport forwards it to the provider (Resend
|
|
142
|
+
* dedupes on an `Idempotency-Key` REQUEST header, which the provider client offers
|
|
143
|
+
* no hook for — its `headers` field becomes message headers in the body). A
|
|
144
|
+
* consumer that only acks is correct for the failure the ack covers and sends a
|
|
145
|
+
* duplicate for the one it does not: the provider accepted the message and the
|
|
146
|
+
* worker died before acking.
|
|
147
|
+
*
|
|
148
|
+
* The recipe below NARROWS that window; it does not close it, and nothing on
|
|
149
|
+
* this path can. The mark is written after the provider accepted the message, so
|
|
150
|
+
* a crash in between still redelivers and resends, and a KV read is eventually
|
|
151
|
+
* consistent — a redelivery seconds later can miss a mark that was written.
|
|
152
|
+
* Delivery is at-least-once end to end. An app that must not double-send needs a
|
|
153
|
+
* strongly-consistent mark taken BEFORE the send (a Durable Object or D1 row
|
|
154
|
+
* claimed by this key, released on failure), and even then the send-then-crash
|
|
155
|
+
* window is only ever traded for a send-that-may-not-have-happened one.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* export default {
|
|
159
|
+
* queue: async (batch, env) => {
|
|
160
|
+
* // createMailerFromEnv, so dev captures the message here instead of sending it.
|
|
161
|
+
* const mailer = createMailerFromEnv(env);
|
|
162
|
+
* for (const message of batch.messages) {
|
|
163
|
+
* const { idempotencyKey } = message.body;
|
|
164
|
+
* if (await env.SENT.get(idempotencyKey)) { message.ack(); continue; }
|
|
165
|
+
* await consumeQueuedSend(mailer, message.body);
|
|
166
|
+
* await env.SENT.put(idempotencyKey, "1", { expirationTtl: 86_400 });
|
|
167
|
+
* message.ack();
|
|
168
|
+
* }
|
|
169
|
+
* },
|
|
170
|
+
* };
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
130
173
|
declare const consumeQueuedSend: (mailer: Mailer, payload: unknown) => Promise<{
|
|
131
174
|
id: string;
|
|
132
175
|
}>;
|
|
133
176
|
/**
|
|
134
|
-
* Render a React element to an HTML/text pair suitable for inlining into a
|
|
135
|
-
* provider payload. Wraps `@react-email/render` so we can swap to
|
|
136
|
-
* `@visulima/email`'s react-email template engine without touching callers.
|
|
137
|
-
|
|
177
|
+
* Render a React element to an HTML/text pair suitable for inlining into a
|
|
178
|
+
* provider payload. Wraps `@react-email/render` so we can swap to
|
|
179
|
+
* `@visulima/email`'s react-email template engine without touching callers.
|
|
180
|
+
*
|
|
181
|
+
* The renderer is imported lazily so its top-level evaluation (prettier and
|
|
182
|
+
* html-to-text, in its workerd build) is deferred until the first React render
|
|
183
|
+
* instead of running on cold start. Bundlers may still include the code.
|
|
184
|
+
*/
|
|
138
185
|
declare const renderEmail: (element: ReactElement) => Promise<{
|
|
139
186
|
html: string;
|
|
140
187
|
text: string;
|
|
141
188
|
}>;
|
|
142
189
|
/**
|
|
143
|
-
* Build a Resend-backed transport via `@visulima/email`. Wraps the provider's
|
|
144
|
-
* `sendEmail()` into the minimal `{ send(payload) -> { id } }` shape the rest of
|
|
145
|
-
* `@lunora/mail` consumes. Kept reachable as a named export so a project that
|
|
146
|
-
* prefers Resend over the Cloudflare default can pass
|
|
147
|
-
* `transport: createResendTransport(apiKey, from)` to `createMailer`.
|
|
148
|
-
*/
|
|
190
|
+
* Build a Resend-backed transport via `@visulima/email`. Wraps the provider's
|
|
191
|
+
* `sendEmail()` into the minimal `{ send(payload) -> { id } }` shape the rest of
|
|
192
|
+
* `@lunora/mail` consumes. Kept reachable as a named export so a project that
|
|
193
|
+
* prefers Resend over the Cloudflare default can pass
|
|
194
|
+
* `transport: createResendTransport(apiKey, from)` to `createMailer`.
|
|
195
|
+
*/
|
|
149
196
|
declare const createResendTransport: (apiKey: string, defaultFrom: string) => MailTransport;
|
|
150
|
-
export { type CloudflareSend, type CloudflareTransportOptions, type FromEnvOptions, type LunoraMailOptions, type MailEnv, type MailTransport, type MailboxSink, type Mailer, type QueuedSend, type SendOptions, consumeQueuedSend, createCaptureSink, createCloudflareTransport, createMailer, createMailerFromEnv, createResendTransport, renderEmail, shouldCaptureMail, toQueuedPayload };
|
|
197
|
+
export { type CloudflareSend, type CloudflareTransportOptions, type FromEnvOptions, type LunoraMailOptions, type MailEnv, type MailTransport, type MailboxSink, type Mailer, type QueueLike, type QueuedSend, type SendOptions, consumeQueuedSend, createCaptureSink, createCloudflareTransport, createMailer, createMailerFromEnv, createResendTransport, renderEmail, shouldCaptureMail, toQueuedPayload };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
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
|
-
export { default as renderEmail } from './packem_shared/renderEmail-hyS1bpVP.mjs';
|
|
7
|
-
export { default as createResendTransport } from './packem_shared/createResendTransport-CMQWjZoi.mjs';
|
|
1
|
+
import{createCaptureTransport as a}from"./packem_shared/createCaptureTransport-DZhMUmaI.mjs";import{createCloudflareTransport as t}from"./packem_shared/createCloudflareTransport-bWoNSnK_.mjs";import{default as u}from"./packem_shared/createMailer-ImyDcZuj.mjs";import{createCaptureSink as f,createMailerFromEnv as l,shouldCaptureMail as m}from"./packem_shared/createCaptureSink-BIu-c0Kd.mjs";import{consumeQueuedSend as s,toQueuedPayload as c}from"./packem_shared/consumeQueuedSend-CRfALnCU.mjs";import{default as i}from"./packem_shared/renderEmail-CkxYrU2s.mjs";import{default as M}from"./packem_shared/createResendTransport-BMjRlHmy.mjs";export{s as consumeQueuedSend,f as createCaptureSink,a as createCaptureTransport,t as createCloudflareTransport,u as createMailer,l as createMailerFromEnv,M as createResendTransport,i as renderEmail,m as shouldCaptureMail,c as toQueuedPayload};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{LunoraError as t}from"@lunora/errors";const n=320,a=256,d=/^([^<]*)<([^>]*)>\s*$/,i=(r,s)=>{if(s.includes("\r")||s.includes(`
|
|
2
|
+
`)||s.includes(","))throw new t("INTERNAL",`@lunora/mail: address ${r} must not contain CR, LF, or comma`)},u=(r,s)=>{if(s.includes("\r")||s.includes(`
|
|
3
|
+
`))throw new t("INTERNAL",`@lunora/mail: ${r} must not contain CR or LF`)},m=(r,s)=>{if(r.length>a)throw new t("INTERNAL",`@lunora/mail: address name must be <= ${String(a)} characters`);if(s.length>n)throw new t("INTERNAL",`@lunora/mail: address email must be <= ${String(n)} characters`);return r&&i("name",r),i("email",s),r?{email:s,name:r}:{email:s}},l=r=>{const s=r.trim();if(s.length>n)throw new t("INTERNAL",`@lunora/mail: address email must be <= ${String(n)} characters`);return i("email",s),{email:s}},c=r=>{const s=d.exec(r),e=(s?.[2]??"").trim();return s&&e?m((s[1]??"").trim(),e):l(r)},o=r=>{if(r===void 0)return;const s=Array.isArray(r)?r:[r];if(s.length!==0)return s.map(e=>c(e))},f=r=>{if(o(r.to)===void 0)throw new t("INTERNAL","@lunora/mail: at least one recipient is required");o(r.cc),o(r.bcc),r.from!==void 0&&c(r.from),r.replyTo!==void 0&&c(r.replyTo)};export{u as a,c as b,f as c,o as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import p from"postal-mime";import{a as c}from"./address-oKy_d0aG.mjs";const d=(s,e)=>{if(e!==void 0)return c(`inbound ${s}`,e),e},u=s=>s.address!==void 0&&s.address!==""?s.name?`${s.name} <${s.address}>`:s.address:s.group?s.group.map(e=>e.address??"").filter(e=>e!=="").join(", "):s.name??"",l=/"(?:[^"\\]|\\.)*(?:"|$)|\((?:[^()\\]|\\.)*(?:\)|$)/g,g=s=>s.replaceAll(l,e=>e.startsWith('"')?e.replaceAll(/[;=]/gu," "):" "),m=(s,e,r)=>{const i=g(s).matchAll(new RegExp(String.raw`(?:^|;)\s*${e}\s*(?:/\s*\d+\s*)?=\s*([a-z]+)([^;]*)`,"gi")),a=new RegExp(String.raw`\b${r.split(".").join(String.raw`\s*\.\s*`)}\s*=\s*"?([^\s;"]+)`,"i");return[...i].map(t=>{const n=a.exec(t[2]??"")?.[1];return{domain:n===void 0?null:n.slice(n.lastIndexOf("@")+1).toLowerCase(),result:(t[1]??"").toLowerCase()}})},v=s=>s===void 0||s===""?{dkim:[],dmarc:[],spf:[]}:{dkim:m(s,"dkim","header.d"),dmarc:m(s,"dmarc","header.from"),spf:m(s,"spf","smtp.mailfrom")},I=async s=>{const e=await p.parse(s),r={};for(const o of e.headers)c(`inbound header \`${o.key}\``,o.value),r[o.key]=o.value;const i=e.headers.find(o=>o.key==="authentication-results")?.value,a=(e.to??[]).map(o=>{const f=u(o);return c("inbound to",f),f}),t=e.from?u(e.from):"";return c("inbound from",t),{attachments:e.attachments.map(o=>({content:o.content,disposition:o.disposition,...o.encoding===void 0?{}:{encoding:o.encoding},filename:o.filename,mimeType:o.mimeType})),authentication:v(i),from:t,headers:r,...e.html===void 0?{}:{html:e.html},...d("inReplyTo",e.inReplyTo)===void 0?{}:{inReplyTo:e.inReplyTo},...d("messageId",e.messageId)===void 0?{}:{messageId:e.messageId},...d("references",e.references)===void 0?{}:{references:e.references},...d("subject",e.subject)===void 0?{}:{subject:e.subject},...e.text===void 0?{}:{text:e.text},to:a}},h=/<([^<>]*)>$/,x=s=>{const e=h.exec(s)?.[1]??s,r=e.indexOf("@");if(!(r===-1||r!==e.lastIndexOf("@")))return e.slice(r+1).trim().toLowerCase()},$=s=>{const e=x(s.from);if(e===void 0)return!1;const r=n=>n.some(o=>o.result==="pass"&&o.domain===e),{dkim:i,dmarc:a,spf:t}=s.authentication;return r(a)||r(t)||r(i)};export{$ as authenticatesFrom,I as parseInboundEmail};
|