@lunora/mail 1.0.0-alpha.15 → 1.0.0-alpha.16
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 +80 -80
- package/dist/inbound/index.d.ts +80 -80
- package/dist/index.d.mts +94 -94
- package/dist/index.d.ts +94 -94
- package/dist/packem_shared/capture-transport.d-BJ__iWzS.d.mts +117 -0
- package/dist/packem_shared/capture-transport.d-BJ__iWzS.d.ts +117 -0
- package/dist/packem_shared/{shard.d-C4kxvfmd.d.mts → shard.d-DVADjmEJ.d.mts} +7 -12
- package/dist/packem_shared/{shard.d-C4kxvfmd.d.ts → shard.d-DVADjmEJ.d.ts} +7 -12
- package/dist/testing.d.mts +9 -9
- package/dist/testing.d.ts +9 -9
- package/package.json +4 -4
- 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/inbound/index.d.mts
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.mjs";
|
|
2
|
+
export type { a as ShardStubLike } from "../packem_shared/shard.d-DVADjmEJ.mjs";
|
|
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. */
|
|
@@ -16,24 +16,24 @@ interface InboundAttachment {
|
|
|
16
16
|
mimeType: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
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 field is `null` ("unknown").
|
|
28
|
-
*
|
|
29
|
-
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
-
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
-
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
-
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
-
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
-
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
-
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
36
|
-
*/
|
|
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 field is `null` ("unknown").
|
|
28
|
+
*
|
|
29
|
+
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
+
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
+
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
+
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
+
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
+
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
+
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
36
|
+
*/
|
|
37
37
|
interface InboundAuthentication {
|
|
38
38
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
39
39
|
dkim: string | null;
|
|
@@ -47,13 +47,13 @@ interface InboundEmail {
|
|
|
47
47
|
/** Decoded attachments (empty array when none). */
|
|
48
48
|
attachments: InboundAttachment[];
|
|
49
49
|
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
* Sender-authentication verdicts (DKIM/SPF/DMARC) parsed from the receiving
|
|
51
|
+
* MX's **first/topmost** `Authentication-Results` header. SECURITY: see
|
|
52
|
+
* {@link InboundAuthentication} — `from` is spoofable; gate trust on these
|
|
53
|
+
* verdicts, not on `from`. Reading the raw `headers["authentication-results"]`
|
|
54
|
+
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
|
+
* value — trust `authentication`, not the raw map.
|
|
56
|
+
*/
|
|
57
57
|
authentication: InboundAuthentication;
|
|
58
58
|
/** Sender mailbox (`from`), CR/LF-checked. Empty string when the message omitted it. SECURITY: spoofable — do not trust for authorization. */
|
|
59
59
|
from: string;
|
|
@@ -75,17 +75,17 @@ interface InboundEmail {
|
|
|
75
75
|
to: string[];
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
79
|
-
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
80
|
-
* `Uint8Array`, or a decoded string.
|
|
81
|
-
*/
|
|
78
|
+
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
79
|
+
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
80
|
+
* `Uint8Array`, or a decoded string.
|
|
81
|
+
*/
|
|
82
82
|
declare const parseInboundEmail: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
83
83
|
/**
|
|
84
|
-
* Structural projection of Cloudflare's `ForwardableEmailMessage` (verified
|
|
85
|
-
* against `@cloudflare/workers-types`' `ForwardableEmailMessage`). Only the
|
|
86
|
-
* members the handler touches are modelled, so the host can pass the real
|
|
87
|
-
* runtime object without `@lunora/mail` importing `cloudflare:email`.
|
|
88
|
-
*/
|
|
84
|
+
* Structural projection of Cloudflare's `ForwardableEmailMessage` (verified
|
|
85
|
+
* against `@cloudflare/workers-types`' `ForwardableEmailMessage`). Only the
|
|
86
|
+
* members the handler touches are modelled, so the host can pass the real
|
|
87
|
+
* runtime object without `@lunora/mail` importing `cloudflare:email`.
|
|
88
|
+
*/
|
|
89
89
|
interface ForwardableEmailMessageLike {
|
|
90
90
|
/** Forward this message to a verified destination address. */
|
|
91
91
|
forward: (rcptTo: string, headers?: Headers) => Promise<unknown>;
|
|
@@ -118,43 +118,43 @@ interface InboundDispatchContext<TEnv = Record<string, unknown>> {
|
|
|
118
118
|
/** Routes a parsed message into a Lunora function (or anywhere). */
|
|
119
119
|
type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<void>;
|
|
120
120
|
/**
|
|
121
|
-
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
122
|
-
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
123
|
-
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
124
|
-
* `email.authentication`, an allow-list, etc. Returning `true`/`undefined`
|
|
125
|
-
* proceeds.
|
|
126
|
-
*/
|
|
121
|
+
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
122
|
+
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
123
|
+
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
124
|
+
* `email.authentication`, an allow-list, etc. Returning `true`/`undefined`
|
|
125
|
+
* proceeds.
|
|
126
|
+
*/
|
|
127
127
|
type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
|
|
128
128
|
/** Options for {@link createInboundEmailHandler}. */
|
|
129
129
|
interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
|
|
130
130
|
/** Routes the parsed message onward (e.g. {@link dispatchToLunoraFunction}). */
|
|
131
131
|
dispatch: InboundDispatch<TEnv>;
|
|
132
132
|
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
* Called when `parse`/`verify`/`dispatch` throws. The default rejects the
|
|
134
|
+
* message via `message.setReject` so Cloudflare bounces/retries rather than
|
|
135
|
+
* silently dropping it. SECURITY: the reject reason is delivered to the
|
|
136
|
+
* (attacker-controlled) sender as a bounce, so the default reason is a fixed,
|
|
137
|
+
* generic string and the real error is logged server-side. Override to log,
|
|
138
|
+
* forward, or swallow — but never pass internal error text to `setReject`.
|
|
139
|
+
*/
|
|
140
140
|
onError?: (error: unknown, context: InboundDispatchContext<TEnv>) => Promise<void> | void;
|
|
141
141
|
/** Parses raw bytes into an {@link InboundEmail} (e.g. `parseInboundEmail`). */
|
|
142
142
|
parse: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
143
143
|
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
* Opt-in sender-authentication gate run before `dispatch`. SECURITY: inbound
|
|
145
|
+
* `from` is spoofable and dispatch is privileged — supply this (gating on
|
|
146
|
+
* `email.authentication`) when an inbound function makes any trust decision.
|
|
147
|
+
*/
|
|
148
148
|
verify?: InboundVerify<TEnv>;
|
|
149
149
|
}
|
|
150
150
|
/** The `email(message, env, ctx)` callback the factory returns. */
|
|
151
151
|
type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: ForwardableEmailMessageLike, env: TEnv, context: unknown) => Promise<void>;
|
|
152
152
|
/**
|
|
153
|
-
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
154
|
-
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
155
|
-
* (d) calls `dispatch(parsed, { message, env, ctx })`. Any throw (or a falsy
|
|
156
|
-
* `verify`) routes through `onError` (default: a generic `message.setReject`).
|
|
157
|
-
*/
|
|
153
|
+
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
154
|
+
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
155
|
+
* (d) calls `dispatch(parsed, { message, env, ctx })`. Any throw (or a falsy
|
|
156
|
+
* `verify`) routes through `onError` (default: a generic `message.setReject`).
|
|
157
|
+
*/
|
|
158
158
|
declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
|
|
159
159
|
/** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
|
|
160
160
|
interface RpcEnvelope {
|
|
@@ -165,23 +165,23 @@ interface RpcEnvelope {
|
|
|
165
165
|
/** Options for {@link dispatchToLunoraFunction}. */
|
|
166
166
|
interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
167
167
|
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
* Admin bearer authorizing the shard RPC. Defaults to reading
|
|
169
|
+
* `env.LUNORA_ADMIN_TOKEN` at dispatch time.
|
|
170
|
+
*/
|
|
171
171
|
adminToken?: string;
|
|
172
172
|
/** `functionPath` of the target mutation/action (e.g. `"inbound:onEmail"`). */
|
|
173
173
|
functionPath: string;
|
|
174
174
|
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
* Pin inbound dispatch to a Cloudflare data-residency jurisdiction. Pass the
|
|
176
|
+
* same value as the worker's `jurisdiction` so inbound mail routes to the
|
|
177
|
+
* jurisdiction-pinned shard. Omit for the un-pinned global namespace.
|
|
178
|
+
*/
|
|
179
179
|
jurisdiction?: DurableObjectJurisdiction;
|
|
180
180
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
* Map the parsed message into the function's args. Defaults to passing the
|
|
182
|
+
* whole {@link InboundEmail} with binary attachment `content` base64-encoded
|
|
183
|
+
* (see {@link toJsonSafeEmail}) so it survives the JSON-serialised RPC body.
|
|
184
|
+
*/
|
|
185
185
|
resolveArgs?: (email: InboundEmail, context: InboundDispatchContext<TEnv>) => unknown;
|
|
186
186
|
/** The `SHARD` Durable Object namespace. */
|
|
187
187
|
shard: ShardNamespaceLike;
|
|
@@ -189,16 +189,16 @@ interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
|
189
189
|
shardKey?: string;
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
|
-
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
193
|
-
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
194
|
-
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
195
|
-
* mutation/action. Throws on a non-2xx RPC or a missing admin token so the
|
|
196
|
-
* handler's `onError` (default `setReject`) bounces the message.
|
|
197
|
-
*
|
|
198
|
-
* SECURITY: the RPC carries the admin bearer, so the target function runs with
|
|
199
|
-
* RLS bypassed over fully attacker-controlled, spoofable input — see the module
|
|
200
|
-
* docstring. Verify the sender (`verify` hook / `email.authentication`) before
|
|
201
|
-
* making any trust decision in the target function.
|
|
202
|
-
*/
|
|
192
|
+
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
193
|
+
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
194
|
+
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
195
|
+
* mutation/action. Throws on a non-2xx RPC or a missing admin token so the
|
|
196
|
+
* handler's `onError` (default `setReject`) bounces the message.
|
|
197
|
+
*
|
|
198
|
+
* SECURITY: the RPC carries the admin bearer, so the target function runs with
|
|
199
|
+
* RLS bypassed over fully attacker-controlled, spoofable input — see the module
|
|
200
|
+
* docstring. Verify the sender (`verify` hook / `email.authentication`) before
|
|
201
|
+
* making any trust decision in the target function.
|
|
202
|
+
*/
|
|
203
203
|
declare const dispatchToLunoraFunction: <TEnv extends Record<string, unknown> = Record<string, unknown>>(options: DispatchToLunoraFunctionOptions<TEnv>) => InboundDispatch<TEnv>;
|
|
204
204
|
export { type DispatchToLunoraFunctionOptions, type ForwardableEmailMessageLike, type InboundAttachment, type InboundAuthentication, type InboundDispatch, type InboundDispatchContext, type InboundEmail, type InboundEmailHandler, type InboundEmailHandlerOptions, type InboundVerify, type RawInboundEmail, type RpcEnvelope, type ShardNamespaceLike, createInboundEmailHandler, dispatchToLunoraFunction, parseInboundEmail };
|
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. */
|
|
@@ -16,24 +16,24 @@ interface InboundAttachment {
|
|
|
16
16
|
mimeType: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
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 field is `null` ("unknown").
|
|
28
|
-
*
|
|
29
|
-
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
-
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
-
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
-
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
-
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
-
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
-
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
36
|
-
*/
|
|
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 field is `null` ("unknown").
|
|
28
|
+
*
|
|
29
|
+
* SECURITY: verdicts are read from the **first/topmost** `Authentication-Results`
|
|
30
|
+
* header in document order. The receiving MX prepends its own genuine header per
|
|
31
|
+
* RFC 8601, so the topmost occurrence is the trustworthy one; any lower
|
|
32
|
+
* occurrences (which an untrusted sender can inject into the raw message) are
|
|
33
|
+
* ignored. As defense-in-depth a consumer may additionally verify the topmost
|
|
34
|
+
* header's `authserv-id` matches its receiving MX (e.g. Cloudflare) — that needs
|
|
35
|
+
* config this runtime-agnostic parser does not carry, so it is left to the host.
|
|
36
|
+
*/
|
|
37
37
|
interface InboundAuthentication {
|
|
38
38
|
/** DKIM verdict (`"pass"`/`"fail"`/…), or `null` when not reported. */
|
|
39
39
|
dkim: string | null;
|
|
@@ -47,13 +47,13 @@ interface InboundEmail {
|
|
|
47
47
|
/** Decoded attachments (empty array when none). */
|
|
48
48
|
attachments: InboundAttachment[];
|
|
49
49
|
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
* Sender-authentication verdicts (DKIM/SPF/DMARC) parsed from the receiving
|
|
51
|
+
* MX's **first/topmost** `Authentication-Results` header. SECURITY: see
|
|
52
|
+
* {@link InboundAuthentication} — `from` is spoofable; gate trust on these
|
|
53
|
+
* verdicts, not on `from`. Reading the raw `headers["authentication-results"]`
|
|
54
|
+
* map instead exposes last-wins (a lower, potentially attacker-injected)
|
|
55
|
+
* value — trust `authentication`, not the raw map.
|
|
56
|
+
*/
|
|
57
57
|
authentication: InboundAuthentication;
|
|
58
58
|
/** Sender mailbox (`from`), CR/LF-checked. Empty string when the message omitted it. SECURITY: spoofable — do not trust for authorization. */
|
|
59
59
|
from: string;
|
|
@@ -75,17 +75,17 @@ interface InboundEmail {
|
|
|
75
75
|
to: string[];
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
79
|
-
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
80
|
-
* `Uint8Array`, or a decoded string.
|
|
81
|
-
*/
|
|
78
|
+
* Parse a raw RFC 822 message into a normalised {@link InboundEmail}. Accepts the
|
|
79
|
+
* shapes a Cloudflare Email Worker can hand off — `ReadableStream`, `ArrayBuffer`,
|
|
80
|
+
* `Uint8Array`, or a decoded string.
|
|
81
|
+
*/
|
|
82
82
|
declare const parseInboundEmail: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
83
83
|
/**
|
|
84
|
-
* Structural projection of Cloudflare's `ForwardableEmailMessage` (verified
|
|
85
|
-
* against `@cloudflare/workers-types`' `ForwardableEmailMessage`). Only the
|
|
86
|
-
* members the handler touches are modelled, so the host can pass the real
|
|
87
|
-
* runtime object without `@lunora/mail` importing `cloudflare:email`.
|
|
88
|
-
*/
|
|
84
|
+
* Structural projection of Cloudflare's `ForwardableEmailMessage` (verified
|
|
85
|
+
* against `@cloudflare/workers-types`' `ForwardableEmailMessage`). Only the
|
|
86
|
+
* members the handler touches are modelled, so the host can pass the real
|
|
87
|
+
* runtime object without `@lunora/mail` importing `cloudflare:email`.
|
|
88
|
+
*/
|
|
89
89
|
interface ForwardableEmailMessageLike {
|
|
90
90
|
/** Forward this message to a verified destination address. */
|
|
91
91
|
forward: (rcptTo: string, headers?: Headers) => Promise<unknown>;
|
|
@@ -118,43 +118,43 @@ interface InboundDispatchContext<TEnv = Record<string, unknown>> {
|
|
|
118
118
|
/** Routes a parsed message into a Lunora function (or anywhere). */
|
|
119
119
|
type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<void>;
|
|
120
120
|
/**
|
|
121
|
-
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
122
|
-
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
123
|
-
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
124
|
-
* `email.authentication`, an allow-list, etc. Returning `true`/`undefined`
|
|
125
|
-
* proceeds.
|
|
126
|
-
*/
|
|
121
|
+
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
122
|
+
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
123
|
+
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
124
|
+
* `email.authentication`, an allow-list, etc. Returning `true`/`undefined`
|
|
125
|
+
* proceeds.
|
|
126
|
+
*/
|
|
127
127
|
type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
|
|
128
128
|
/** Options for {@link createInboundEmailHandler}. */
|
|
129
129
|
interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
|
|
130
130
|
/** Routes the parsed message onward (e.g. {@link dispatchToLunoraFunction}). */
|
|
131
131
|
dispatch: InboundDispatch<TEnv>;
|
|
132
132
|
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
* Called when `parse`/`verify`/`dispatch` throws. The default rejects the
|
|
134
|
+
* message via `message.setReject` so Cloudflare bounces/retries rather than
|
|
135
|
+
* silently dropping it. SECURITY: the reject reason is delivered to the
|
|
136
|
+
* (attacker-controlled) sender as a bounce, so the default reason is a fixed,
|
|
137
|
+
* generic string and the real error is logged server-side. Override to log,
|
|
138
|
+
* forward, or swallow — but never pass internal error text to `setReject`.
|
|
139
|
+
*/
|
|
140
140
|
onError?: (error: unknown, context: InboundDispatchContext<TEnv>) => Promise<void> | void;
|
|
141
141
|
/** Parses raw bytes into an {@link InboundEmail} (e.g. `parseInboundEmail`). */
|
|
142
142
|
parse: (raw: RawInboundEmail) => Promise<InboundEmail>;
|
|
143
143
|
/**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
* Opt-in sender-authentication gate run before `dispatch`. SECURITY: inbound
|
|
145
|
+
* `from` is spoofable and dispatch is privileged — supply this (gating on
|
|
146
|
+
* `email.authentication`) when an inbound function makes any trust decision.
|
|
147
|
+
*/
|
|
148
148
|
verify?: InboundVerify<TEnv>;
|
|
149
149
|
}
|
|
150
150
|
/** The `email(message, env, ctx)` callback the factory returns. */
|
|
151
151
|
type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: ForwardableEmailMessageLike, env: TEnv, context: unknown) => Promise<void>;
|
|
152
152
|
/**
|
|
153
|
-
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
154
|
-
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
155
|
-
* (d) calls `dispatch(parsed, { message, env, ctx })`. Any throw (or a falsy
|
|
156
|
-
* `verify`) routes through `onError` (default: a generic `message.setReject`).
|
|
157
|
-
*/
|
|
153
|
+
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
154
|
+
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
155
|
+
* (d) calls `dispatch(parsed, { message, env, ctx })`. Any throw (or a falsy
|
|
156
|
+
* `verify`) routes through `onError` (default: a generic `message.setReject`).
|
|
157
|
+
*/
|
|
158
158
|
declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
|
|
159
159
|
/** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
|
|
160
160
|
interface RpcEnvelope {
|
|
@@ -165,23 +165,23 @@ interface RpcEnvelope {
|
|
|
165
165
|
/** Options for {@link dispatchToLunoraFunction}. */
|
|
166
166
|
interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
167
167
|
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
* Admin bearer authorizing the shard RPC. Defaults to reading
|
|
169
|
+
* `env.LUNORA_ADMIN_TOKEN` at dispatch time.
|
|
170
|
+
*/
|
|
171
171
|
adminToken?: string;
|
|
172
172
|
/** `functionPath` of the target mutation/action (e.g. `"inbound:onEmail"`). */
|
|
173
173
|
functionPath: string;
|
|
174
174
|
/**
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
* Pin inbound dispatch to a Cloudflare data-residency jurisdiction. Pass the
|
|
176
|
+
* same value as the worker's `jurisdiction` so inbound mail routes to the
|
|
177
|
+
* jurisdiction-pinned shard. Omit for the un-pinned global namespace.
|
|
178
|
+
*/
|
|
179
179
|
jurisdiction?: DurableObjectJurisdiction;
|
|
180
180
|
/**
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
* Map the parsed message into the function's args. Defaults to passing the
|
|
182
|
+
* whole {@link InboundEmail} with binary attachment `content` base64-encoded
|
|
183
|
+
* (see {@link toJsonSafeEmail}) so it survives the JSON-serialised RPC body.
|
|
184
|
+
*/
|
|
185
185
|
resolveArgs?: (email: InboundEmail, context: InboundDispatchContext<TEnv>) => unknown;
|
|
186
186
|
/** The `SHARD` Durable Object namespace. */
|
|
187
187
|
shard: ShardNamespaceLike;
|
|
@@ -189,16 +189,16 @@ interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
|
|
|
189
189
|
shardKey?: string;
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
|
-
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
193
|
-
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
194
|
-
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
195
|
-
* mutation/action. Throws on a non-2xx RPC or a missing admin token so the
|
|
196
|
-
* handler's `onError` (default `setReject`) bounces the message.
|
|
197
|
-
*
|
|
198
|
-
* SECURITY: the RPC carries the admin bearer, so the target function runs with
|
|
199
|
-
* RLS bypassed over fully attacker-controlled, spoofable input — see the module
|
|
200
|
-
* docstring. Verify the sender (`verify` hook / `email.authentication`) before
|
|
201
|
-
* making any trust decision in the target function.
|
|
202
|
-
*/
|
|
192
|
+
* Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
|
|
193
|
+
* shard stub — the same admin-RPC-over-shard path the dev capture sink uses
|
|
194
|
+
* (`from-env.ts`) — routing the parsed message into a named Lunora
|
|
195
|
+
* mutation/action. Throws on a non-2xx RPC or a missing admin token so the
|
|
196
|
+
* handler's `onError` (default `setReject`) bounces the message.
|
|
197
|
+
*
|
|
198
|
+
* SECURITY: the RPC carries the admin bearer, so the target function runs with
|
|
199
|
+
* RLS bypassed over fully attacker-controlled, spoofable input — see the module
|
|
200
|
+
* docstring. Verify the sender (`verify` hook / `email.authentication`) before
|
|
201
|
+
* making any trust decision in the target function.
|
|
202
|
+
*/
|
|
203
203
|
declare const dispatchToLunoraFunction: <TEnv extends Record<string, unknown> = Record<string, unknown>>(options: DispatchToLunoraFunctionOptions<TEnv>) => InboundDispatch<TEnv>;
|
|
204
204
|
export { type DispatchToLunoraFunctionOptions, type ForwardableEmailMessageLike, type InboundAttachment, type InboundAuthentication, type InboundDispatch, type InboundDispatchContext, type InboundEmail, type InboundEmailHandler, type InboundEmailHandlerOptions, type InboundVerify, type RawInboundEmail, type RpcEnvelope, type ShardNamespaceLike, createInboundEmailHandler, dispatchToLunoraFunction, parseInboundEmail };
|