@lunora/mail 1.0.0-alpha.55 → 1.0.0-alpha.56

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.
@@ -101,7 +101,14 @@ interface ForwardableEmailMessageLike {
101
101
  raw: string;
102
102
  to: string;
103
103
  }) => Promise<unknown>;
104
- /** Reject the message with a permanent SMTP error (Cloudflare bounces/retries). */
104
+ /**
105
+ * Reject the message with a PERMANENT SMTP error — Cloudflare returns it to
106
+ * the connecting client with this reason, and it is never redelivered.
107
+ * Documented at https://developers.cloudflare.com/email-routing/email-workers/runtime-api/
108
+ * ("Reject this email message by returning a permanent SMTP error back to
109
+ * the connecting client, including the given reason") and mirrored in
110
+ * workerd's own `types/defines/email.d.ts`.
111
+ */
105
112
  setReject: (reason: string) => void;
106
113
  /** Envelope `To`. */
107
114
  readonly to: string;
@@ -125,21 +132,54 @@ type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, con
125
132
  * proceeds.
126
133
  */
127
134
  type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
135
+ /**
136
+ * Opt-in durable sink for a failed `dispatch`. Hand the parsed message to
137
+ * something that owns the retry — a queue producer, a Durable Object, an alarm
138
+ * — and the handler ACCEPTS the SMTP session instead of bouncing, because the
139
+ * message is now owned rather than lost. Returning normally means "I have it";
140
+ * throwing means the hand-off itself failed and the message bounces with the
141
+ * generic reason (see {@link createInboundEmailHandler}).
142
+ *
143
+ * `error` is the dispatch failure, for classification/logging by the sink.
144
+ *
145
+ * NOTE: binary attachment `content` is an `ArrayBuffer`/`Uint8Array`, which
146
+ * survives structured clone (Cloudflare Queues, DO storage) but is corrupted by
147
+ * `JSON.stringify` — encode it yourself if the sink is JSON-bodied.
148
+ */
149
+ type InboundRetain<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>, error: unknown) => Promise<void> | void;
128
150
  /** Options for {@link createInboundEmailHandler}. */
129
151
  interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
130
152
  /** Routes the parsed message onward (e.g. {@link dispatchToLunoraFunction}). */
131
153
  dispatch: InboundDispatch<TEnv>;
132
154
  /**
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`.
155
+ * Called when `parse`, `verify`, or `dispatch` fails but with two DIFFERENT
156
+ * contracts, because only the first two decide the message's fate here:
157
+ *
158
+ * - `parse` / `verify` this hook DECIDES the outcome. The default
159
+ * ({@link rejectOnError}) rejects via `message.setReject`; supplying your own
160
+ * replaces that, so the message is accepted unless you reject it yourself.
161
+ * - `dispatch` — this hook is OBSERVABILITY ONLY. It is called for the side
162
+ * effect (log, alert, forward) and the outcome is then decided by `retain`
163
+ * (accept) or a generic reject, regardless of what it does; the built-in
164
+ * default is deliberately NOT applied there. A `setReject` from inside it
165
+ * still takes effect, which would bounce a message `retain` went on to
166
+ * accept — almost certainly not what you want. A throw from the hook itself
167
+ * is logged and swallowed so it cannot mask the original dispatch error.
168
+ *
169
+ * SECURITY: a reject reason is delivered to the (attacker-controlled) sender
170
+ * as a bounce, so the default reason is a fixed, generic string and the real
171
+ * error is logged server-side. Never pass internal error text to `setReject`.
139
172
  */
140
173
  onError?: (error: unknown, context: InboundDispatchContext<TEnv>) => Promise<void> | void;
141
174
  /** Parses raw bytes into an {@link InboundEmail} (e.g. `parseInboundEmail`). */
142
175
  parse: (raw: RawInboundEmail) => Promise<InboundEmail>;
176
+ /**
177
+ * Opt-in: take durable ownership of a message whose `dispatch` failed, so a
178
+ * transient fault (a shard 502, a briefly-absent admin token) is retried
179
+ * instead of bounced. Omit it and a dispatch failure bounces, as it always
180
+ * has. See {@link InboundRetain}.
181
+ */
182
+ retain?: InboundRetain<TEnv>;
143
183
  /**
144
184
  * Opt-in sender-authentication gate run before `dispatch`. SECURITY: inbound
145
185
  * `from` is spoofable and dispatch is privileged — supply this (gating on
@@ -152,8 +192,54 @@ type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: Forwardable
152
192
  /**
153
193
  * Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
154
194
  * (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`).
195
+ * (d) calls `dispatch(parsed, { message, env, ctx })`.
196
+ *
197
+ * The two failure classes are routed differently:
198
+ *
199
+ * - `parse` / `verify` (including a falsy `verify`) → `onError` (default: a
200
+ * generic `message.setReject`). A malformed or unauthenticated message fails
201
+ * the same way on every redelivery, so bouncing it is the honest answer.
202
+ * - `dispatch` (or its transport) → a custom `onError` is called for
203
+ * observability, then the message is handed to `retain` if one is configured
204
+ * (SMTP ACCEPTs — the retry is now owned elsewhere) and otherwise bounced with
205
+ * the same generic reason. A `retain` that throws bounces too.
206
+ *
207
+ * WHY THE RETRY IS ABSORBED IN-WORKER RATHER THAN SIGNALLED OVER SMTP — there is
208
+ * no transient-reject API and no inbound redelivery to appeal to:
209
+ *
210
+ * - `setReject` is documented as a PERMANENT SMTP error
211
+ * (https://developers.cloudflare.com/email-routing/email-workers/runtime-api/),
212
+ * with no "try later" variant.
213
+ * - Cloudflare does not document what an uncaught throw from `email()` does. The
214
+ * full Email Routing and Email Service docs corpora
215
+ * (`developers.cloudflare.com/email-routing/llms-full.txt`,
216
+ * `.../email-service/llms-full.txt`) say nothing about an unhandled exception,
217
+ * and describe NO redelivery mechanism for inbound Email Workers at all. The
218
+ * documented lifecycle
219
+ * (https://developers.cloudflare.com/email-service/concepts/email-lifecycle/)
220
+ * lists exactly three worker outcomes — `forward()`, `reply()`, `setReject()` —
221
+ * with no branch for "the worker threw". (Its 4xx-retry prose is about OUTBOUND
222
+ * delivery to the destination MTA, not about invoking the worker.)
223
+ * - The behaviour reported in practice is a PERMANENT in-session rejection:
224
+ * `521 5.3.0 Upstream error`, i.e. the same permanence as `setReject` but with
225
+ * an opaque reason instead of ours. See
226
+ * https://community.cloudflare.com/t/is-it-possible-to-return-a-transient-failure-from-an-email-worker/599938
227
+ * ("If the email function raises an exception, a permanent failure is returned
228
+ * to the client after the DATA command") — a question Cloudflare never answered
229
+ * — and https://community.cloudflare.com/t/email-worker-upstream-error/457228.
230
+ * Email Routing also states it forwards upstream SMTP errors back to the sender
231
+ * in-session rather than generating a bounce later
232
+ * (https://developers.cloudflare.com/email-service/reference/postmaster/#smtp-errors).
233
+ *
234
+ * So every SMTP-visible outcome is permanent, and the only way not to lose a
235
+ * legitimate message to a two-second shard 502 is to accept it and take durable
236
+ * ownership: that is `retain`. It stays opt-in — with no `retain`, a dispatch
237
+ * failure bounces exactly as before.
238
+ *
239
+ * A dispatch that KNOWS one of its own failures is permanent should call
240
+ * `context.message.setReject(...)` and return normally rather than throw, so it
241
+ * bounces without being handed to `retain` (see `@lunora/agent`'s inbound
242
+ * handler for both cases).
157
243
  */
158
244
  declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
159
245
  /** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
@@ -192,8 +278,10 @@ interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
192
278
  * Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
193
279
  * shard stub — the same admin-RPC-over-shard path the dev capture sink uses
194
280
  * (`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.
281
+ * mutation/action. Throws on a non-2xx RPC or a missing admin token both
282
+ * transient in principle, so the handler reports it through a custom `onError`
283
+ * and then hands the message to `retain` if one is configured, bouncing only
284
+ * when there is nowhere durable to put it (see {@link createInboundEmailHandler}).
197
285
  *
198
286
  * SECURITY: the RPC carries the admin bearer, so the target function runs with
199
287
  * RLS bypassed over fully attacker-controlled, spoofable input — see the module
@@ -101,7 +101,14 @@ interface ForwardableEmailMessageLike {
101
101
  raw: string;
102
102
  to: string;
103
103
  }) => Promise<unknown>;
104
- /** Reject the message with a permanent SMTP error (Cloudflare bounces/retries). */
104
+ /**
105
+ * Reject the message with a PERMANENT SMTP error — Cloudflare returns it to
106
+ * the connecting client with this reason, and it is never redelivered.
107
+ * Documented at https://developers.cloudflare.com/email-routing/email-workers/runtime-api/
108
+ * ("Reject this email message by returning a permanent SMTP error back to
109
+ * the connecting client, including the given reason") and mirrored in
110
+ * workerd's own `types/defines/email.d.ts`.
111
+ */
105
112
  setReject: (reason: string) => void;
106
113
  /** Envelope `To`. */
107
114
  readonly to: string;
@@ -125,21 +132,54 @@ type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, con
125
132
  * proceeds.
126
133
  */
127
134
  type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
135
+ /**
136
+ * Opt-in durable sink for a failed `dispatch`. Hand the parsed message to
137
+ * something that owns the retry — a queue producer, a Durable Object, an alarm
138
+ * — and the handler ACCEPTS the SMTP session instead of bouncing, because the
139
+ * message is now owned rather than lost. Returning normally means "I have it";
140
+ * throwing means the hand-off itself failed and the message bounces with the
141
+ * generic reason (see {@link createInboundEmailHandler}).
142
+ *
143
+ * `error` is the dispatch failure, for classification/logging by the sink.
144
+ *
145
+ * NOTE: binary attachment `content` is an `ArrayBuffer`/`Uint8Array`, which
146
+ * survives structured clone (Cloudflare Queues, DO storage) but is corrupted by
147
+ * `JSON.stringify` — encode it yourself if the sink is JSON-bodied.
148
+ */
149
+ type InboundRetain<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>, error: unknown) => Promise<void> | void;
128
150
  /** Options for {@link createInboundEmailHandler}. */
129
151
  interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
130
152
  /** Routes the parsed message onward (e.g. {@link dispatchToLunoraFunction}). */
131
153
  dispatch: InboundDispatch<TEnv>;
132
154
  /**
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`.
155
+ * Called when `parse`, `verify`, or `dispatch` fails but with two DIFFERENT
156
+ * contracts, because only the first two decide the message's fate here:
157
+ *
158
+ * - `parse` / `verify` this hook DECIDES the outcome. The default
159
+ * ({@link rejectOnError}) rejects via `message.setReject`; supplying your own
160
+ * replaces that, so the message is accepted unless you reject it yourself.
161
+ * - `dispatch` — this hook is OBSERVABILITY ONLY. It is called for the side
162
+ * effect (log, alert, forward) and the outcome is then decided by `retain`
163
+ * (accept) or a generic reject, regardless of what it does; the built-in
164
+ * default is deliberately NOT applied there. A `setReject` from inside it
165
+ * still takes effect, which would bounce a message `retain` went on to
166
+ * accept — almost certainly not what you want. A throw from the hook itself
167
+ * is logged and swallowed so it cannot mask the original dispatch error.
168
+ *
169
+ * SECURITY: a reject reason is delivered to the (attacker-controlled) sender
170
+ * as a bounce, so the default reason is a fixed, generic string and the real
171
+ * error is logged server-side. Never pass internal error text to `setReject`.
139
172
  */
140
173
  onError?: (error: unknown, context: InboundDispatchContext<TEnv>) => Promise<void> | void;
141
174
  /** Parses raw bytes into an {@link InboundEmail} (e.g. `parseInboundEmail`). */
142
175
  parse: (raw: RawInboundEmail) => Promise<InboundEmail>;
176
+ /**
177
+ * Opt-in: take durable ownership of a message whose `dispatch` failed, so a
178
+ * transient fault (a shard 502, a briefly-absent admin token) is retried
179
+ * instead of bounced. Omit it and a dispatch failure bounces, as it always
180
+ * has. See {@link InboundRetain}.
181
+ */
182
+ retain?: InboundRetain<TEnv>;
143
183
  /**
144
184
  * Opt-in sender-authentication gate run before `dispatch`. SECURITY: inbound
145
185
  * `from` is spoofable and dispatch is privileged — supply this (gating on
@@ -152,8 +192,54 @@ type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: Forwardable
152
192
  /**
153
193
  * Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
154
194
  * (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`).
195
+ * (d) calls `dispatch(parsed, { message, env, ctx })`.
196
+ *
197
+ * The two failure classes are routed differently:
198
+ *
199
+ * - `parse` / `verify` (including a falsy `verify`) → `onError` (default: a
200
+ * generic `message.setReject`). A malformed or unauthenticated message fails
201
+ * the same way on every redelivery, so bouncing it is the honest answer.
202
+ * - `dispatch` (or its transport) → a custom `onError` is called for
203
+ * observability, then the message is handed to `retain` if one is configured
204
+ * (SMTP ACCEPTs — the retry is now owned elsewhere) and otherwise bounced with
205
+ * the same generic reason. A `retain` that throws bounces too.
206
+ *
207
+ * WHY THE RETRY IS ABSORBED IN-WORKER RATHER THAN SIGNALLED OVER SMTP — there is
208
+ * no transient-reject API and no inbound redelivery to appeal to:
209
+ *
210
+ * - `setReject` is documented as a PERMANENT SMTP error
211
+ * (https://developers.cloudflare.com/email-routing/email-workers/runtime-api/),
212
+ * with no "try later" variant.
213
+ * - Cloudflare does not document what an uncaught throw from `email()` does. The
214
+ * full Email Routing and Email Service docs corpora
215
+ * (`developers.cloudflare.com/email-routing/llms-full.txt`,
216
+ * `.../email-service/llms-full.txt`) say nothing about an unhandled exception,
217
+ * and describe NO redelivery mechanism for inbound Email Workers at all. The
218
+ * documented lifecycle
219
+ * (https://developers.cloudflare.com/email-service/concepts/email-lifecycle/)
220
+ * lists exactly three worker outcomes — `forward()`, `reply()`, `setReject()` —
221
+ * with no branch for "the worker threw". (Its 4xx-retry prose is about OUTBOUND
222
+ * delivery to the destination MTA, not about invoking the worker.)
223
+ * - The behaviour reported in practice is a PERMANENT in-session rejection:
224
+ * `521 5.3.0 Upstream error`, i.e. the same permanence as `setReject` but with
225
+ * an opaque reason instead of ours. See
226
+ * https://community.cloudflare.com/t/is-it-possible-to-return-a-transient-failure-from-an-email-worker/599938
227
+ * ("If the email function raises an exception, a permanent failure is returned
228
+ * to the client after the DATA command") — a question Cloudflare never answered
229
+ * — and https://community.cloudflare.com/t/email-worker-upstream-error/457228.
230
+ * Email Routing also states it forwards upstream SMTP errors back to the sender
231
+ * in-session rather than generating a bounce later
232
+ * (https://developers.cloudflare.com/email-service/reference/postmaster/#smtp-errors).
233
+ *
234
+ * So every SMTP-visible outcome is permanent, and the only way not to lose a
235
+ * legitimate message to a two-second shard 502 is to accept it and take durable
236
+ * ownership: that is `retain`. It stays opt-in — with no `retain`, a dispatch
237
+ * failure bounces exactly as before.
238
+ *
239
+ * A dispatch that KNOWS one of its own failures is permanent should call
240
+ * `context.message.setReject(...)` and return normally rather than throw, so it
241
+ * bounces without being handed to `retain` (see `@lunora/agent`'s inbound
242
+ * handler for both cases).
157
243
  */
158
244
  declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
159
245
  /** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
@@ -192,8 +278,10 @@ interface DispatchToLunoraFunctionOptions<TEnv = Record<string, unknown>> {
192
278
  * Build a {@link InboundDispatch} that posts an {@link RpcEnvelope} to the root
193
279
  * shard stub — the same admin-RPC-over-shard path the dev capture sink uses
194
280
  * (`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.
281
+ * mutation/action. Throws on a non-2xx RPC or a missing admin token both
282
+ * transient in principle, so the handler reports it through a custom `onError`
283
+ * and then hands the message to `retain` if one is configured, bouncing only
284
+ * when there is nowhere durable to put it (see {@link createInboundEmailHandler}).
197
285
  *
198
286
  * SECURITY: the RPC carries the admin bearer, so the target function runs with
199
287
  * RLS bypassed over fully attacker-controlled, spoofable input — see the module
@@ -1 +1 @@
1
- import{createInboundEmailHandler as r,dispatchToLunoraFunction as a}from"../packem_shared/createInboundEmailHandler-CTary3dz.mjs";import{parseInboundEmail as t}from"../packem_shared/parseInboundEmail-CqU52y3O.mjs";export{r as createInboundEmailHandler,a as dispatchToLunoraFunction,t as parseInboundEmail};
1
+ import{createInboundEmailHandler as r,dispatchToLunoraFunction as a}from"../packem_shared/createInboundEmailHandler-C6V47mz5.mjs";import{parseInboundEmail as t}from"../packem_shared/parseInboundEmail-CqU52y3O.mjs";export{r as createInboundEmailHandler,a as dispatchToLunoraFunction,t as parseInboundEmail};
@@ -0,0 +1 @@
1
+ import{LunoraError as d}from"@lunora/errors";import{D as h,p as l}from"./shard-C0aRRoVy.mjs";const f=r=>{let n="";for(let e=0;e<r.length;e+=32768)n+=String.fromCharCode(...r.subarray(e,e+32768));return btoa(n)},E="message could not be processed",u=(r,n)=>{console.error("@lunora/mail/inbound: dropping message —",r),n.message.setReject(E)},y=r=>{const n=r.onError??u;return async(t,e,c)=>{const a={ctx:c,env:e,message:t};let o;try{if(o=await r.parse(t.raw),r.verify&&await r.verify(o,a)===!1)throw new d("INTERNAL","@lunora/mail/inbound: sender verification rejected the message")}catch(i){await n(i,a);return}try{await r.dispatch(o,a)}catch(i){if(r.onError)try{await r.onError(i,a)}catch(s){console.error("@lunora/mail/inbound: onError threw while reporting a dispatch failure —",s)}if(r.retain)try{await r.retain(o,a,i);return}catch(s){console.error("@lunora/mail/inbound: retain failed to take ownership of the message —",s)}u(i,a)}}},m=r=>r.attachments.length===0?r:{...r,attachments:r.attachments.map(n=>{const{content:t}=n;if(typeof t=="string")return n;const e=t instanceof Uint8Array?t:new Uint8Array(t);return{...n,content:f(e),encoding:"base64"}})},A=r=>{const n=r.shardKey??h,t=r.resolveArgs??(e=>m(e));return async(e,c)=>{const a=r.adminToken??(typeof c.env.LUNORA_ADMIN_TOKEN=="string"?c.env.LUNORA_ADMIN_TOKEN:void 0);if(a===void 0||a==="")throw new d("INTERNAL","@lunora/mail/inbound: missing LUNORA_ADMIN_TOKEN — cannot authorize inbound dispatch to the shard RPC.");const o={args:t(e,c),functionPath:r.functionPath,shardKey:n};await l(r.shard,{adminToken:a,envelope:o,jurisdiction:r.jurisdiction,label:`@lunora/mail/inbound: dispatch to \`${r.functionPath}\``,shardKey:n})}};export{y as createInboundEmailHandler,A as dispatchToLunoraFunction};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/mail",
3
- "version": "1.0.0-alpha.55",
3
+ "version": "1.0.0-alpha.56",
4
4
  "description": "Email for Lunora: Resend adapter, TSX templates, and queue-backed sends",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -1 +0,0 @@
1
- import{LunoraError as i}from"@lunora/errors";import{D as c,p as d}from"./shard-C0aRRoVy.mjs";const u=n=>{let r="";for(let e=0;e<n.length;e+=32768)r+=String.fromCharCode(...n.subarray(e,e+32768));return btoa(r)},h="message could not be processed",f=(n,r)=>{console.error("@lunora/mail/inbound: dropping message —",n),r.message.setReject(h)},N=n=>{const r=n.onError??f;return async(t,e,s)=>{const a={ctx:s,env:e,message:t};try{const o=await n.parse(t.raw);if(n.verify&&await n.verify(o,a)===!1)throw new i("INTERNAL","@lunora/mail/inbound: sender verification rejected the message");await n.dispatch(o,a)}catch(o){await r(o,a)}}},l=n=>n.attachments.length===0?n:{...n,attachments:n.attachments.map(r=>{const{content:t}=r;if(typeof t=="string")return r;const e=t instanceof Uint8Array?t:new Uint8Array(t);return{...r,content:u(e),encoding:"base64"}})},g=n=>{const r=n.shardKey??c,t=n.resolveArgs??(e=>l(e));return async(e,s)=>{const a=n.adminToken??(typeof s.env.LUNORA_ADMIN_TOKEN=="string"?s.env.LUNORA_ADMIN_TOKEN:void 0);if(a===void 0||a==="")throw new i("INTERNAL","@lunora/mail/inbound: missing LUNORA_ADMIN_TOKEN — cannot authorize inbound dispatch to the shard RPC.");const o={args:t(e,s),functionPath:n.functionPath,shardKey:r};await d(n.shard,{adminToken:a,envelope:o,jurisdiction:n.jurisdiction,label:`@lunora/mail/inbound: dispatch to \`${n.functionPath}\``,shardKey:r})}};export{N as createInboundEmailHandler,g as dispatchToLunoraFunction};