@lunora/mail 1.0.0-alpha.9 → 1.0.0-alpha.90
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 +124 -94
- package/dist/index.d.ts +124 -94
- 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-D5FHD5HJ.mjs +1 -0
- package/dist/packem_shared/createCaptureTransport-CKc6NpZR.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-aRrzRLwc.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-BOUnr6i3.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/inbound/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-
|
|
2
|
-
export type { a as ShardStubLike } from "../packem_shared/shard.d-
|
|
1
|
+
import { D as DurableObjectJurisdiction, S as ShardNamespaceLike } from "../packem_shared/shard.d-DVADjmEJ.js";
|
|
2
|
+
export type { a as ShardStubLike } from "../packem_shared/shard.d-DVADjmEJ.js";
|
|
3
3
|
/** A raw RFC 822 message as accepted by the parser. */
|
|
4
4
|
type RawInboundEmail = ArrayBuffer | ReadableStream<Uint8Array> | string | Uint8Array;
|
|
5
5
|
/** One parsed attachment. `content` is preserved as the parser decoded it. */
|
|
@@ -15,34 +15,79 @@ interface InboundAttachment {
|
|
|
15
15
|
/** Declared MIME type (e.g. `image/png`). */
|
|
16
16
|
mimeType: string;
|
|
17
17
|
}
|
|
18
|
+
/** One reported `method=result [ptype.property=value]` clause of an `Authentication-Results` header. */
|
|
19
|
+
interface InboundAuthResult {
|
|
20
|
+
/**
|
|
21
|
+
* The identifier the result is about, lowercased — DKIM's signing domain
|
|
22
|
+
* (`header.d=`), SPF's envelope `MAIL FROM` domain (`smtp.mailfrom=`, local
|
|
23
|
+
* part dropped) or DMARC's `header.from=`. `null` when the clause reported
|
|
24
|
+
* none, in which case it cannot be aligned and vouches for nothing.
|
|
25
|
+
*/
|
|
26
|
+
domain: string | null;
|
|
27
|
+
/** The result token, lowercased (`"pass"`, `"fail"`, `"none"`, …). */
|
|
28
|
+
result: string;
|
|
29
|
+
}
|
|
18
30
|
/**
|
|
19
|
-
* Sender-authentication verdicts pulled from the `Authentication-Results` header
|
|
20
|
-
* the receiving MX (e.g. Cloudflare Email Routing) stamped on the message.
|
|
21
|
-
*
|
|
22
|
-
* SECURITY: Cloudflare Email Routing authenticates only the *recipient* domain,
|
|
23
|
-
* **not** the sender. The envelope `from` and message content are trivially
|
|
24
|
-
* spoofable, so a downstream handler MUST NOT make trust/authorization decisions
|
|
25
|
-
* on `email.from` alone — gate on these verdicts (or your own policy) instead.
|
|
26
|
-
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
27
|
-
* `Authentication-Results` header, every
|
|
28
|
-
|
|
31
|
+
* Sender-authentication verdicts pulled from the `Authentication-Results` header
|
|
32
|
+
* the receiving MX (e.g. Cloudflare Email Routing) stamped on the message.
|
|
33
|
+
*
|
|
34
|
+
* SECURITY: Cloudflare Email Routing authenticates only the *recipient* domain,
|
|
35
|
+
* **not** the sender. The envelope `from` and message content are trivially
|
|
36
|
+
* spoofable, so a downstream handler MUST NOT make trust/authorization decisions
|
|
37
|
+
* on `email.from` alone — gate on these verdicts (or your own policy) instead.
|
|
38
|
+
* Verdicts are best-effort: when the receiving MX did not stamp an
|
|
39
|
+
* `Authentication-Results` header, every list is empty ("unknown").
|
|
40
|
+
*
|
|
41
|
+
* Each method holds a LIST, because RFC 8601 lets one header report the same
|
|
42
|
+
* method more than once and real mail does. An ESP-relayed message carries two
|
|
43
|
+
* DKIM signatures — the relay's and the author domain's — and the MX stamps a
|
|
44
|
+
* clause per signature in whatever order it verified them. Keeping only the
|
|
45
|
+
* first threw the aligned one away whenever it was not the one that happened to
|
|
46
|
+
* come first, and the message was rejected as unauthenticated. A consumer
|
|
47
|
+
* therefore asks "does ANY reported clause pass and align?", not "did the first
|
|
48
|
+
* one?".
|
|
49
|
+
*
|
|
50
|
+
* SECURITY: a bare `"pass"` is NOT proof the `From` header is genuine. SPF
|
|
51
|
+
* authenticates the envelope `MAIL FROM` domain and DKIM the signing domain
|
|
52
|
+
* (`d=`), and an attacker controls both — `spf=pass smtp.mailfrom=evil.example;
|
|
53
|
+
* dkim=pass header.d=evil.example` is routine for a message whose `From` says
|
|
54
|
+
* `ceo@victim.example`. Each verdict therefore carries the identifier it is
|
|
55
|
+
* about ({@link InboundAuthResult.domain}): an SPF or DKIM pass only vouches for
|
|
56
|
+
* `from` when that domain equals the `From` address's domain (RFC 7489 strict
|
|
57
|
+
* alignment). Only a DMARC pass already checked alignment for you. A pass with
|
|
58
|
+
* no identifier reported cannot be aligned and must be treated as
|
|
59
|
+
* unauthenticated.
|
|
60
|
+
*
|
|
61
|
+
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
62
|
+
* header in document order. The receiving MX prepends its own genuine header per
|
|
63
|
+
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
64
|
+
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
65
|
+
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
66
|
+
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
67
|
+
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
68
|
+
*/
|
|
29
69
|
interface InboundAuthentication {
|
|
30
|
-
/** DKIM
|
|
31
|
-
dkim:
|
|
32
|
-
/** DMARC
|
|
33
|
-
dmarc:
|
|
34
|
-
/** SPF
|
|
35
|
-
spf:
|
|
70
|
+
/** Every DKIM clause the header reported, in header order; empty when the method was not reported. */
|
|
71
|
+
dkim: InboundAuthResult[];
|
|
72
|
+
/** Every DMARC clause the header reported, in header order; empty when the method was not reported. */
|
|
73
|
+
dmarc: InboundAuthResult[];
|
|
74
|
+
/** Every SPF clause the header reported, in header order; empty when the method was not reported. */
|
|
75
|
+
spf: InboundAuthResult[];
|
|
36
76
|
}
|
|
37
77
|
/** Normalised, transport-agnostic view of a received message. */
|
|
38
78
|
interface InboundEmail {
|
|
39
79
|
/** Decoded attachments (empty array when none). */
|
|
40
80
|
attachments: InboundAttachment[];
|
|
41
81
|
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
82
|
+
* Sender-authentication verdicts (DKIM/SPF/DMARC) and the domain each is
|
|
83
|
+
* about, parsed from the receiving MX's **first/topmost**
|
|
84
|
+
* `Authentication-Results` header. SECURITY: see
|
|
85
|
+
* {@link InboundAuthentication} — `from` is spoofable, and an SPF/DKIM pass
|
|
86
|
+
* vouches for it only when that clause's `domain` equals the `From` domain (a
|
|
87
|
+
* DMARC pass checked that already). Reading the raw `headers["authentication-results"]`
|
|
88
|
+
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
89
|
+
* value — trust `authentication`, not the raw map.
|
|
90
|
+
*/
|
|
46
91
|
authentication: InboundAuthentication;
|
|
47
92
|
/** Sender mailbox (`from`), CR/LF-checked. Empty string when the message omitted it. SECURITY: spoofable — do not trust for authorization. */
|
|
48
93
|
from: string;
|
|
@@ -64,17 +109,48 @@ interface InboundEmail {
|
|
|
64
109
|
to: string[];
|
|
65
110
|
}
|
|
66
111
|
/**
|
|
67
|
-
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
68
|
-
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
69
|
-
* `Uint8Array`, or a decoded string.
|
|
70
|
-
*/
|
|
112
|
+
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
113
|
+
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
114
|
+
* `Uint8Array`, or a decoded string.
|
|
115
|
+
*/
|
|
71
116
|
declare const parseInboundEmail: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
72
117
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
118
|
+
* THE inbound sender-authentication gate: does the receiving MX vouch for this
|
|
119
|
+
* message's `From` domain? Pass it as the `verify` hook of
|
|
120
|
+
* `createInboundEmailHandler` (or call it from your own) — it is one exported
|
|
121
|
+
* helper precisely so the insecure variants cannot be hand-rolled again.
|
|
122
|
+
*
|
|
123
|
+
* True when ANY reported DMARC, SPF or DKIM clause both **passes** and names a
|
|
124
|
+
* `domain` equal to the `From` address's domain. False otherwise — including for
|
|
125
|
+
* an empty verdict list (the MX stamped no `Authentication-Results` header at
|
|
126
|
+
* all, which is "unknown", not "fine") and for a `From` with no single mailbox
|
|
127
|
+
* to align against.
|
|
128
|
+
*
|
|
129
|
+
* SECURITY — the two halves are each load-bearing.
|
|
130
|
+
*
|
|
131
|
+
* **Alignment.** A bare `pass` proves nothing about `From`. SPF authenticates the
|
|
132
|
+
* envelope `MAIL FROM` domain and DKIM the signing `d=`, both attacker-chosen, so
|
|
133
|
+
* `spf=pass`+`dkim=pass` for `evil.example` is routine on a message whose `From`
|
|
134
|
+
* says `ceo@victim.example`. Only a clause whose own `domain` equals the `From`
|
|
135
|
+
* domain vouches for it. Alignment is STRICT (RFC 7489): there is no
|
|
136
|
+
* public-suffix list here, so `mail.example.com` does not vouch for
|
|
137
|
+
* `example.com`. A pass reporting no domain (`null`) cannot be aligned and is
|
|
138
|
+
* rejected. A DMARC pass already checked alignment at the MX.
|
|
139
|
+
*
|
|
140
|
+
* **Every clause, not the first.** One header legitimately reports a method more
|
|
141
|
+
* than once (an ESP-relayed message is DKIM-signed by both the relay and the
|
|
142
|
+
* author domain), and the aligned clause is not reliably the first, so reading
|
|
143
|
+
* only the first bounced fully authenticated mail. "Any clause passes AND aligns"
|
|
144
|
+
* stays strictly narrower than a bare pass: a clause vouching for some other
|
|
145
|
+
* domain contributes nothing.
|
|
146
|
+
*/
|
|
147
|
+
declare const authenticatesFrom: (email: InboundEmail) => boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Structural projection of Cloudflare's `ForwardableEmailMessage` (verified
|
|
150
|
+
* against `@cloudflare/workers-types`' `ForwardableEmailMessage`). Only the
|
|
151
|
+
* members the handler touches are modelled, so the host can pass the real
|
|
152
|
+
* runtime object without `@lunora/mail` importing `cloudflare:email`.
|
|
153
|
+
*/
|
|
78
154
|
interface ForwardableEmailMessageLike {
|
|
79
155
|
/** Forward this message to a verified destination address. */
|
|
80
156
|
forward: (rcptTo: string, headers?: Headers) => Promise<unknown>;
|
|
@@ -90,7 +166,14 @@ interface ForwardableEmailMessageLike {
|
|
|
90
166
|
raw: string;
|
|
91
167
|
to: string;
|
|
92
168
|
}) => Promise<unknown>;
|
|
93
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Reject the message with a PERMANENT SMTP error — Cloudflare returns it to
|
|
171
|
+
* the connecting client with this reason, and it is never redelivered.
|
|
172
|
+
* Documented at https://developers.cloudflare.com/email-routing/email-workers/runtime-api/
|
|
173
|
+
* ("Reject this email message by returning a permanent SMTP error back to
|
|
174
|
+
* the connecting client, including the given reason") and mirrored in
|
|
175
|
+
* workerd's own `types/defines/email.d.ts`.
|
|
176
|
+
*/
|
|
94
177
|
setReject: (reason: string) => void;
|
|
95
178
|
/** Envelope `To`. */
|
|
96
179
|
readonly to: string;
|
|
@@ -107,43 +190,73 @@ interface InboundDispatchContext<TEnv = Record<string, unknown>> {
|
|
|
107
190
|
/** Routes a parsed message into a Lunora function (or anywhere). */
|
|
108
191
|
type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<void>;
|
|
109
192
|
/**
|
|
110
|
-
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
111
|
-
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
112
|
-
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
113
|
-
* `email.authentication`, an allow-list, etc.
|
|
114
|
-
*
|
|
115
|
-
|
|
193
|
+
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
194
|
+
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
195
|
+
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
196
|
+
* `email.authentication`, an allow-list, etc.
|
|
197
|
+
*
|
|
198
|
+
* `true` and `undefined` are the ONLY answers that proceed — `undefined` so a
|
|
199
|
+
* `(): void` hook that rejects by throwing type-checks. Anything else is read as a
|
|
200
|
+
* rejection: this is the gate whose failure mode would otherwise grant.
|
|
201
|
+
*/
|
|
116
202
|
type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
|
|
203
|
+
/**
|
|
204
|
+
* Opt-in durable sink for a failed `dispatch`. Hand the parsed message to
|
|
205
|
+
* something that owns the retry — a queue producer, a Durable Object, an alarm
|
|
206
|
+
* — and the handler ACCEPTS the SMTP session instead of bouncing, because the
|
|
207
|
+
* message is now owned rather than lost. Returning normally means "I have it";
|
|
208
|
+
* throwing means the hand-off itself failed and the message bounces with the
|
|
209
|
+
* generic reason (see {@link createInboundEmailHandler}).
|
|
210
|
+
*
|
|
211
|
+
* `error` is the dispatch failure, for classification/logging by the sink.
|
|
212
|
+
*
|
|
213
|
+
* NOTE: binary attachment `content` is an `ArrayBuffer`/`Uint8Array`, which
|
|
214
|
+
* survives structured clone (Cloudflare Queues, DO storage) but is corrupted by
|
|
215
|
+
* `JSON.stringify` — encode it yourself if the sink is JSON-bodied.
|
|
216
|
+
*/
|
|
217
|
+
type InboundRetain<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>, error: unknown) => Promise<void> | void;
|
|
117
218
|
/** Options for {@link createInboundEmailHandler}. */
|
|
118
219
|
interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
|
|
119
220
|
/** Routes the parsed message onward (e.g. {@link dispatchToLunoraFunction}). */
|
|
120
221
|
dispatch: InboundDispatch<TEnv>;
|
|
121
222
|
/**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
223
|
+
* Called when `parse`, `verify`, or `dispatch` fails — but with two DIFFERENT
|
|
224
|
+
* contracts, because only the first two decide the message's fate here:
|
|
225
|
+
*
|
|
226
|
+
* - `parse` / `verify` — this hook DECIDES the outcome. The default
|
|
227
|
+
* ({@link rejectOnError}) rejects via `message.setReject`; supplying your own
|
|
228
|
+
* replaces that, so the message is accepted unless you reject it yourself.
|
|
229
|
+
* - `dispatch` — this hook is OBSERVABILITY ONLY. It is called for the side
|
|
230
|
+
* effect (log, alert, forward) and the outcome is then decided by `retain`
|
|
231
|
+
* (accept) or a generic reject, regardless of what it does; the built-in
|
|
232
|
+
* default is deliberately NOT applied there. A `setReject` from inside it
|
|
233
|
+
* still takes effect, which would bounce a message `retain` went on to
|
|
234
|
+
* accept — almost certainly not what you want. A throw from the hook itself
|
|
235
|
+
* is logged and swallowed so it cannot mask the original dispatch error.
|
|
236
|
+
*
|
|
237
|
+
* SECURITY: a reject reason is delivered to the (attacker-controlled) sender
|
|
238
|
+
* as a bounce, so the default reason is a fixed, generic string and the real
|
|
239
|
+
* error is logged server-side. Never pass internal error text to `setReject`.
|
|
240
|
+
*/
|
|
129
241
|
onError?: (error: unknown, context: InboundDispatchContext<TEnv>) => Promise<void> | void;
|
|
130
242
|
/** Parses raw bytes into an {@link InboundEmail} (e.g. `parseInboundEmail`). */
|
|
131
243
|
parse: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
132
244
|
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
245
|
+
* Opt-in: take durable ownership of a message whose `dispatch` failed, so a
|
|
246
|
+
* transient fault (a shard 502, a briefly-absent admin token) is retried
|
|
247
|
+
* instead of bounced. Omit it and a dispatch failure bounces, as it always
|
|
248
|
+
* has. See {@link InboundRetain}.
|
|
249
|
+
*/
|
|
250
|
+
retain?: InboundRetain<TEnv>;
|
|
251
|
+
/**
|
|
252
|
+
* Opt-in sender-authentication gate run before `dispatch`. SECURITY: inbound
|
|
253
|
+
* `from` is spoofable and dispatch is privileged — supply this (gating on
|
|
254
|
+
* `email.authentication`) when an inbound function makes any trust decision.
|
|
255
|
+
*/
|
|
137
256
|
verify?: InboundVerify<TEnv>;
|
|
138
257
|
}
|
|
139
258
|
/** The `email(message, env, ctx)` callback the factory returns. */
|
|
140
259
|
type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: ForwardableEmailMessageLike, env: TEnv, context: unknown) => Promise<void>;
|
|
141
|
-
/**
|
|
142
|
-
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
143
|
-
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
144
|
-
* (d) calls `dispatch(parsed, { message, env, ctx })`. Any throw (or a falsy
|
|
145
|
-
* `verify`) routes through `onError` (default: a generic `message.setReject`).
|
|
146
|
-
*/
|
|
147
260
|
declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
|
|
148
261
|
/** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
|
|
149
262
|
interface RpcEnvelope {
|
|
@@ -154,23 +267,25 @@ interface RpcEnvelope {
|
|
|
154
267
|
/** Options for {@link dispatchToLunoraFunction}. */
|
|
155
268
|
interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
156
269
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
270
|
+
* Admin bearer authorizing the shard RPC. Defaults to reading
|
|
271
|
+
* `env.LUNORA_ADMIN_TOKEN` at dispatch time.
|
|
272
|
+
*/
|
|
160
273
|
adminToken?: string;
|
|
161
274
|
/** `functionPath` of the target mutation/action (e.g. `"inbound:onEmail"`). */
|
|
162
275
|
functionPath: string;
|
|
163
276
|
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
277
|
+
* Pin inbound dispatch to a Cloudflare data-residency jurisdiction. Defaults
|
|
278
|
+
* to the jurisdiction the schema declares (`.jurisdiction("eu")`), so a
|
|
279
|
+
* generated app needs nothing here; set it only for a hand-written worker, to
|
|
280
|
+
* the same value as its `jurisdiction`. A value that contradicts the schema's
|
|
281
|
+
* throws.
|
|
282
|
+
*/
|
|
168
283
|
jurisdiction?: DurableObjectJurisdiction;
|
|
169
284
|
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
285
|
+
* Map the parsed message into the function's args. Defaults to passing the
|
|
286
|
+
* whole {@link InboundEmail} with binary attachment `content` base64-encoded
|
|
287
|
+
* (see {@link toJsonSafeEmail}) so it survives the JSON-serialised RPC body.
|
|
288
|
+
*/
|
|
174
289
|
resolveArgs?: (email: InboundEmail, context: InboundDispatchContext<TEnv>) => unknown;
|
|
175
290
|
/** The `SHARD` Durable Object namespace. */
|
|
176
291
|
shard: ShardNamespaceLike;
|
|
@@ -178,16 +293,147 @@ interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
|
178
293
|
shardKey?: string;
|
|
179
294
|
}
|
|
180
295
|
/**
|
|
181
|
-
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
182
|
-
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
183
|
-
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
184
|
-
* mutation/action. Throws on a non-2xx RPC or a missing admin token
|
|
185
|
-
* handler
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
|
|
296
|
+
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
297
|
+
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
298
|
+
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
299
|
+
* mutation/action. Throws on a non-2xx RPC or a missing admin token — both
|
|
300
|
+
* transient in principle, so the handler reports it through a custom `onError`
|
|
301
|
+
* and then hands the message to `retain` if one is configured, bouncing only
|
|
302
|
+
* when there is nowhere durable to put it (see {@link createInboundEmailHandler}).
|
|
303
|
+
*
|
|
304
|
+
* SECURITY: the RPC is marked a trusted system dispatch, so `functionPath` may
|
|
305
|
+
* (and should) name an `internalMutation`/`internalAction` — a public `mutation`
|
|
306
|
+
* target is callable by any browser client with a forged message. The input is
|
|
307
|
+
* fully attacker-controlled and spoofable; verify the sender (`verify` hook /
|
|
308
|
+
* `email.authentication`) before making any trust decision in the target.
|
|
309
|
+
*/
|
|
192
310
|
declare const dispatchToLunoraFunction: <TEnv extends Record<string, unknown> = Record<string, unknown>>(options: DispatchToLunoraFunctionOptions<TEnv>) => InboundDispatch<TEnv>;
|
|
193
|
-
export {
|
|
311
|
+
export {
|
|
312
|
+
/**
|
|
313
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
314
|
+
*
|
|
315
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
316
|
+
* mutation/action:
|
|
317
|
+
*
|
|
318
|
+
* ```ts
|
|
319
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
320
|
+
*
|
|
321
|
+
* export const email = createInboundEmailHandler({
|
|
322
|
+
* parse: parseInboundEmail,
|
|
323
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
324
|
+
* });
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
type DispatchToLunoraFunctionOptions,
|
|
328
|
+
/**
|
|
329
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
330
|
+
*
|
|
331
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
332
|
+
* mutation/action:
|
|
333
|
+
*
|
|
334
|
+
* ```ts
|
|
335
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
336
|
+
*
|
|
337
|
+
* export const email = createInboundEmailHandler({
|
|
338
|
+
* parse: parseInboundEmail,
|
|
339
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
340
|
+
* });
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
type ForwardableEmailMessageLike, type InboundAttachment, type InboundAuthResult, type InboundAuthentication,
|
|
344
|
+
/**
|
|
345
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
346
|
+
*
|
|
347
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
348
|
+
* mutation/action:
|
|
349
|
+
*
|
|
350
|
+
* ```ts
|
|
351
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
352
|
+
*
|
|
353
|
+
* export const email = createInboundEmailHandler({
|
|
354
|
+
* parse: parseInboundEmail,
|
|
355
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
356
|
+
* });
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
type InboundDispatch,
|
|
360
|
+
/**
|
|
361
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
362
|
+
*
|
|
363
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
364
|
+
* mutation/action:
|
|
365
|
+
*
|
|
366
|
+
* ```ts
|
|
367
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
368
|
+
*
|
|
369
|
+
* export const email = createInboundEmailHandler({
|
|
370
|
+
* parse: parseInboundEmail,
|
|
371
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
372
|
+
* });
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
type InboundDispatchContext, type InboundEmail,
|
|
376
|
+
/**
|
|
377
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
378
|
+
*
|
|
379
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
380
|
+
* mutation/action:
|
|
381
|
+
*
|
|
382
|
+
* ```ts
|
|
383
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
384
|
+
*
|
|
385
|
+
* export const email = createInboundEmailHandler({
|
|
386
|
+
* parse: parseInboundEmail,
|
|
387
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
388
|
+
* });
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
type InboundEmailHandler,
|
|
392
|
+
/**
|
|
393
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
394
|
+
*
|
|
395
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
396
|
+
* mutation/action:
|
|
397
|
+
*
|
|
398
|
+
* ```ts
|
|
399
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
400
|
+
*
|
|
401
|
+
* export const email = createInboundEmailHandler({
|
|
402
|
+
* parse: parseInboundEmail,
|
|
403
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
404
|
+
* });
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
type InboundEmailHandlerOptions,
|
|
408
|
+
/**
|
|
409
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
410
|
+
*
|
|
411
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
412
|
+
* mutation/action:
|
|
413
|
+
*
|
|
414
|
+
* ```ts
|
|
415
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
416
|
+
*
|
|
417
|
+
* export const email = createInboundEmailHandler({
|
|
418
|
+
* parse: parseInboundEmail,
|
|
419
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
420
|
+
* });
|
|
421
|
+
* ```
|
|
422
|
+
*/
|
|
423
|
+
type InboundVerify, type RawInboundEmail,
|
|
424
|
+
/**
|
|
425
|
+
* `@lunora/mail/inbound` — inbound Email Routing support.
|
|
426
|
+
*
|
|
427
|
+
* Wire a Cloudflare Email Worker entry to route received mail into a Lunora
|
|
428
|
+
* mutation/action:
|
|
429
|
+
*
|
|
430
|
+
* ```ts
|
|
431
|
+
* import { createInboundEmailHandler, parseInboundEmail, dispatchToLunoraFunction } from "@lunora/mail/inbound";
|
|
432
|
+
*
|
|
433
|
+
* export const email = createInboundEmailHandler({
|
|
434
|
+
* parse: parseInboundEmail,
|
|
435
|
+
* dispatch: dispatchToLunoraFunction({ shard: env.SHARD, functionPath: "inbound:onEmail" }),
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
type RpcEnvelope, type ShardNamespaceLike, authenticatesFrom, createInboundEmailHandler, dispatchToLunoraFunction, parseInboundEmail };
|
package/dist/inbound/index.mjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { parseInboundEmail } from '../packem_shared/parseInboundEmail-6lafQBT1.mjs';
|
|
1
|
+
import{createInboundEmailHandler as n,dispatchToLunoraFunction as r}from"../packem_shared/createInboundEmailHandler-QnZHQZpF.mjs";import{authenticatesFrom as t,parseInboundEmail as i}from"../packem_shared/authenticatesFrom-BOX-Paur.mjs";export{t as authenticatesFrom,n as createInboundEmailHandler,r as dispatchToLunoraFunction,i as parseInboundEmail};
|