@lunora/mail 1.0.0-alpha.61 → 1.0.0-alpha.63
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 +5 -54
- package/dist/inbound/index.d.ts +5 -54
- package/dist/inbound/index.mjs +1 -1
- package/dist/index.d.mts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{capture-transport.d-8CDAv51Z.d.mts → capture-transport.d-BD67fxbB.d.mts} +6 -2
- package/dist/packem_shared/{capture-transport.d-8CDAv51Z.d.ts → capture-transport.d-BD67fxbB.d.ts} +6 -2
- package/dist/packem_shared/createCaptureSink-CeV0DSsT.mjs +1 -0
- package/dist/packem_shared/createInboundEmailHandler-Bh10YPyJ.mjs +1 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/createCaptureSink-wbDNGuA-.mjs +0 -1
- package/dist/packem_shared/createInboundEmailHandler-vXSNL5mZ.mjs +0 -1
package/dist/inbound/index.d.mts
CHANGED
|
@@ -193,8 +193,11 @@ type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, con
|
|
|
193
193
|
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
194
194
|
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
195
195
|
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
196
|
-
* `email.authentication`, an allow-list, etc.
|
|
197
|
-
*
|
|
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.
|
|
198
201
|
*/
|
|
199
202
|
type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
|
|
200
203
|
/**
|
|
@@ -254,58 +257,6 @@ interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
|
|
|
254
257
|
}
|
|
255
258
|
/** The `email(message, env, ctx)` callback the factory returns. */
|
|
256
259
|
type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: ForwardableEmailMessageLike, env: TEnv, context: unknown) => Promise<void>;
|
|
257
|
-
/**
|
|
258
|
-
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
259
|
-
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
260
|
-
* (d) calls `dispatch(parsed, { message, env, ctx })`.
|
|
261
|
-
*
|
|
262
|
-
* The two failure classes are routed differently:
|
|
263
|
-
*
|
|
264
|
-
* - `parse` / `verify` (including a falsy `verify`) → `onError` (default: a
|
|
265
|
-
* generic `message.setReject`). A malformed or unauthenticated message fails
|
|
266
|
-
* the same way on every redelivery, so bouncing it is the honest answer.
|
|
267
|
-
* - `dispatch` (or its transport) → a custom `onError` is called for
|
|
268
|
-
* observability, then the message is handed to `retain` if one is configured
|
|
269
|
-
* (SMTP ACCEPTs — the retry is now owned elsewhere) and otherwise bounced with
|
|
270
|
-
* the same generic reason. A `retain` that throws bounces too.
|
|
271
|
-
*
|
|
272
|
-
* WHY THE RETRY IS ABSORBED IN-WORKER RATHER THAN SIGNALLED OVER SMTP — there is
|
|
273
|
-
* no transient-reject API and no inbound redelivery to appeal to:
|
|
274
|
-
*
|
|
275
|
-
* - `setReject` is documented as a PERMANENT SMTP error
|
|
276
|
-
* (https://developers.cloudflare.com/email-routing/email-workers/runtime-api/),
|
|
277
|
-
* with no "try later" variant.
|
|
278
|
-
* - Cloudflare does not document what an uncaught throw from `email()` does. The
|
|
279
|
-
* full Email Routing and Email Service docs corpora
|
|
280
|
-
* (`developers.cloudflare.com/email-routing/llms-full.txt`,
|
|
281
|
-
* `.../email-service/llms-full.txt`) say nothing about an unhandled exception,
|
|
282
|
-
* and describe NO redelivery mechanism for inbound Email Workers at all. The
|
|
283
|
-
* documented lifecycle
|
|
284
|
-
* (https://developers.cloudflare.com/email-service/concepts/email-lifecycle/)
|
|
285
|
-
* lists exactly three worker outcomes — `forward()`, `reply()`, `setReject()` —
|
|
286
|
-
* with no branch for "the worker threw". (Its 4xx-retry prose is about OUTBOUND
|
|
287
|
-
* delivery to the destination MTA, not about invoking the worker.)
|
|
288
|
-
* - The behaviour reported in practice is a PERMANENT in-session rejection:
|
|
289
|
-
* `521 5.3.0 Upstream error`, i.e. the same permanence as `setReject` but with
|
|
290
|
-
* an opaque reason instead of ours. See
|
|
291
|
-
* https://community.cloudflare.com/t/is-it-possible-to-return-a-transient-failure-from-an-email-worker/599938
|
|
292
|
-
* ("If the email function raises an exception, a permanent failure is returned
|
|
293
|
-
* to the client after the DATA command") — a question Cloudflare never answered
|
|
294
|
-
* — and https://community.cloudflare.com/t/email-worker-upstream-error/457228.
|
|
295
|
-
* Email Routing also states it forwards upstream SMTP errors back to the sender
|
|
296
|
-
* in-session rather than generating a bounce later
|
|
297
|
-
* (https://developers.cloudflare.com/email-service/reference/postmaster/#smtp-errors).
|
|
298
|
-
*
|
|
299
|
-
* So every SMTP-visible outcome is permanent, and the only way not to lose a
|
|
300
|
-
* legitimate message to a two-second shard 502 is to accept it and take durable
|
|
301
|
-
* ownership: that is `retain`. It stays opt-in — with no `retain`, a dispatch
|
|
302
|
-
* failure bounces exactly as before.
|
|
303
|
-
*
|
|
304
|
-
* A dispatch that KNOWS one of its own failures is permanent should call
|
|
305
|
-
* `context.message.setReject(...)` and return normally rather than throw, so it
|
|
306
|
-
* bounces without being handed to `retain` (see `@lunora/agent`'s inbound
|
|
307
|
-
* handler for both cases).
|
|
308
|
-
*/
|
|
309
260
|
declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
|
|
310
261
|
/** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
|
|
311
262
|
interface RpcEnvelope {
|
package/dist/inbound/index.d.ts
CHANGED
|
@@ -193,8 +193,11 @@ type InboundDispatch<TEnv = Record<string, unknown>> = (email: InboundEmail, con
|
|
|
193
193
|
* Opt-in sender-verification gate. Runs after `parse` and before `dispatch` with
|
|
194
194
|
* the parsed message. Return `false` (or throw) to reject the message before it
|
|
195
195
|
* reaches the privileged dispatch — use it to enforce DKIM/SPF/DMARC via
|
|
196
|
-
* `email.authentication`, an allow-list, etc.
|
|
197
|
-
*
|
|
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.
|
|
198
201
|
*/
|
|
199
202
|
type InboundVerify<TEnv = Record<string, unknown>> = (email: InboundEmail, context: InboundDispatchContext<TEnv>) => Promise<boolean | void> | boolean | void;
|
|
200
203
|
/**
|
|
@@ -254,58 +257,6 @@ interface InboundEmailHandlerOptions<TEnv = Record<string, unknown>> {
|
|
|
254
257
|
}
|
|
255
258
|
/** The `email(message, env, ctx)` callback the factory returns. */
|
|
256
259
|
type InboundEmailHandler<TEnv = Record<string, unknown>> = (message: ForwardableEmailMessageLike, env: TEnv, context: unknown) => Promise<void>;
|
|
257
|
-
/**
|
|
258
|
-
* Build the `email(message, env, ctx)` handler. It (a) reads `message.raw`,
|
|
259
|
-
* (b) parses it via `parse`, (c) runs the optional `verify` gate, then
|
|
260
|
-
* (d) calls `dispatch(parsed, { message, env, ctx })`.
|
|
261
|
-
*
|
|
262
|
-
* The two failure classes are routed differently:
|
|
263
|
-
*
|
|
264
|
-
* - `parse` / `verify` (including a falsy `verify`) → `onError` (default: a
|
|
265
|
-
* generic `message.setReject`). A malformed or unauthenticated message fails
|
|
266
|
-
* the same way on every redelivery, so bouncing it is the honest answer.
|
|
267
|
-
* - `dispatch` (or its transport) → a custom `onError` is called for
|
|
268
|
-
* observability, then the message is handed to `retain` if one is configured
|
|
269
|
-
* (SMTP ACCEPTs — the retry is now owned elsewhere) and otherwise bounced with
|
|
270
|
-
* the same generic reason. A `retain` that throws bounces too.
|
|
271
|
-
*
|
|
272
|
-
* WHY THE RETRY IS ABSORBED IN-WORKER RATHER THAN SIGNALLED OVER SMTP — there is
|
|
273
|
-
* no transient-reject API and no inbound redelivery to appeal to:
|
|
274
|
-
*
|
|
275
|
-
* - `setReject` is documented as a PERMANENT SMTP error
|
|
276
|
-
* (https://developers.cloudflare.com/email-routing/email-workers/runtime-api/),
|
|
277
|
-
* with no "try later" variant.
|
|
278
|
-
* - Cloudflare does not document what an uncaught throw from `email()` does. The
|
|
279
|
-
* full Email Routing and Email Service docs corpora
|
|
280
|
-
* (`developers.cloudflare.com/email-routing/llms-full.txt`,
|
|
281
|
-
* `.../email-service/llms-full.txt`) say nothing about an unhandled exception,
|
|
282
|
-
* and describe NO redelivery mechanism for inbound Email Workers at all. The
|
|
283
|
-
* documented lifecycle
|
|
284
|
-
* (https://developers.cloudflare.com/email-service/concepts/email-lifecycle/)
|
|
285
|
-
* lists exactly three worker outcomes — `forward()`, `reply()`, `setReject()` —
|
|
286
|
-
* with no branch for "the worker threw". (Its 4xx-retry prose is about OUTBOUND
|
|
287
|
-
* delivery to the destination MTA, not about invoking the worker.)
|
|
288
|
-
* - The behaviour reported in practice is a PERMANENT in-session rejection:
|
|
289
|
-
* `521 5.3.0 Upstream error`, i.e. the same permanence as `setReject` but with
|
|
290
|
-
* an opaque reason instead of ours. See
|
|
291
|
-
* https://community.cloudflare.com/t/is-it-possible-to-return-a-transient-failure-from-an-email-worker/599938
|
|
292
|
-
* ("If the email function raises an exception, a permanent failure is returned
|
|
293
|
-
* to the client after the DATA command") — a question Cloudflare never answered
|
|
294
|
-
* — and https://community.cloudflare.com/t/email-worker-upstream-error/457228.
|
|
295
|
-
* Email Routing also states it forwards upstream SMTP errors back to the sender
|
|
296
|
-
* in-session rather than generating a bounce later
|
|
297
|
-
* (https://developers.cloudflare.com/email-service/reference/postmaster/#smtp-errors).
|
|
298
|
-
*
|
|
299
|
-
* So every SMTP-visible outcome is permanent, and the only way not to lose a
|
|
300
|
-
* legitimate message to a two-second shard 502 is to accept it and take durable
|
|
301
|
-
* ownership: that is `retain`. It stays opt-in — with no `retain`, a dispatch
|
|
302
|
-
* failure bounces exactly as before.
|
|
303
|
-
*
|
|
304
|
-
* A dispatch that KNOWS one of its own failures is permanent should call
|
|
305
|
-
* `context.message.setReject(...)` and return normally rather than throw, so it
|
|
306
|
-
* bounces without being handed to `retain` (see `@lunora/agent`'s inbound
|
|
307
|
-
* handler for both cases).
|
|
308
|
-
*/
|
|
309
260
|
declare const createInboundEmailHandler: <TEnv = Record<string, unknown>>(options: InboundEmailHandlerOptions<TEnv>) => InboundEmailHandler<TEnv>;
|
|
310
261
|
/** The `RpcEnvelope` shape the runtime's `/_lunora/rpc` path consumes. */
|
|
311
262
|
interface RpcEnvelope {
|
package/dist/inbound/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createInboundEmailHandler as n,dispatchToLunoraFunction as r}from"../packem_shared/createInboundEmailHandler-
|
|
1
|
+
import{createInboundEmailHandler as n,dispatchToLunoraFunction as r}from"../packem_shared/createInboundEmailHandler-Bh10YPyJ.mjs";import{authenticatesFrom as t,parseInboundEmail as i}from"../packem_shared/authenticatesFrom-xpKAFMRN.mjs";export{t as authenticatesFrom,n as createInboundEmailHandler,r as dispatchToLunoraFunction,i as parseInboundEmail};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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 Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-
|
|
1
|
+
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-BD67fxbB.mjs";
|
|
2
|
+
export { type C as CapturedMail, type Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-BD67fxbB.mjs";
|
|
3
3
|
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-DVADjmEJ.mjs";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
@@ -80,7 +80,7 @@ declare const shouldCaptureMail: (env: MailEnv) => boolean;
|
|
|
80
80
|
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
81
81
|
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
82
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.
|
|
83
|
+
* never fails for lack of somewhere to record — but it says so first (see below).
|
|
84
84
|
*/
|
|
85
85
|
declare const createCaptureSink: (env: MailEnv, rootShard?: string, jurisdiction?: DurableObjectJurisdiction) => MailboxSink;
|
|
86
86
|
/**
|
|
@@ -122,12 +122,33 @@ declare const toQueuedPayload: (options: SendOptions) => QueuedSend;
|
|
|
122
122
|
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
123
123
|
* handler.
|
|
124
124
|
*
|
|
125
|
+
* DEDUPE IS THE CONSUMER'S JOB, and this helper does not do it. The payload's
|
|
126
|
+
* `idempotencyKey` was minted once at enqueue time so it survives redelivery, but
|
|
127
|
+
* nothing downstream reads it: no transport forwards it to the provider (Resend
|
|
128
|
+
* dedupes on an `Idempotency-Key` REQUEST header, which the provider client offers
|
|
129
|
+
* no hook for — its `headers` field becomes message headers in the body). A
|
|
130
|
+
* consumer that only acks is correct for the failure the ack covers and sends a
|
|
131
|
+
* duplicate for the one it does not: the provider accepted the message and the
|
|
132
|
+
* worker died before acking.
|
|
133
|
+
*
|
|
134
|
+
* The recipe below NARROWS that window; it does not close it, and nothing on
|
|
135
|
+
* this path can. The mark is written after the provider accepted the message, so
|
|
136
|
+
* a crash in between still redelivers and resends, and a KV read is eventually
|
|
137
|
+
* consistent — a redelivery seconds later can miss a mark that was written.
|
|
138
|
+
* Delivery is at-least-once end to end. An app that must not double-send needs a
|
|
139
|
+
* strongly-consistent mark taken BEFORE the send (a Durable Object or D1 row
|
|
140
|
+
* claimed by this key, released on failure), and even then the send-then-crash
|
|
141
|
+
* window is only ever traded for a send-that-may-not-have-happened one.
|
|
142
|
+
*
|
|
125
143
|
* ```ts
|
|
126
144
|
* export default {
|
|
127
145
|
* queue: async (batch, env) => {
|
|
128
146
|
* const mailer = createMailer({ apiKey: env.RESEND_API_KEY, from: "..." });
|
|
129
147
|
* for (const message of batch.messages) {
|
|
148
|
+
* const { idempotencyKey } = message.body;
|
|
149
|
+
* if (await env.SENT.get(idempotencyKey)) { message.ack(); continue; }
|
|
130
150
|
* await consumeQueuedSend(mailer, message.body);
|
|
151
|
+
* await env.SENT.put(idempotencyKey, "1", { expirationTtl: 86_400 });
|
|
131
152
|
* message.ack();
|
|
132
153
|
* }
|
|
133
154
|
* },
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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 Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-
|
|
1
|
+
import { M as MailTransport, L as LunoraMailOptions, a as Mailer, b as MailboxSink, S as SendOptions } from "./packem_shared/capture-transport.d-BD67fxbB.js";
|
|
2
|
+
export { type C as CapturedMail, type Q as QueueLike, type c as SendPayload, d as createCaptureTransport } from "./packem_shared/capture-transport.d-BD67fxbB.js";
|
|
3
3
|
import { D as DurableObjectJurisdiction } from "./packem_shared/shard.d-DVADjmEJ.js";
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
/**
|
|
@@ -80,7 +80,7 @@ declare const shouldCaptureMail: (env: MailEnv) => boolean;
|
|
|
80
80
|
* root-shard inbox via the reserved `recordMail` admin RPC — the same
|
|
81
81
|
* worker→root-shard path the runtime uses for auth events. Best-effort: without
|
|
82
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.
|
|
83
|
+
* never fails for lack of somewhere to record — but it says so first (see below).
|
|
84
84
|
*/
|
|
85
85
|
declare const createCaptureSink: (env: MailEnv, rootShard?: string, jurisdiction?: DurableObjectJurisdiction) => MailboxSink;
|
|
86
86
|
/**
|
|
@@ -122,12 +122,33 @@ declare const toQueuedPayload: (options: SendOptions) => QueuedSend;
|
|
|
122
122
|
* to a configured `Mailer.send()`. Use this inside your Worker's `queue()`
|
|
123
123
|
* handler.
|
|
124
124
|
*
|
|
125
|
+
* DEDUPE IS THE CONSUMER'S JOB, and this helper does not do it. The payload's
|
|
126
|
+
* `idempotencyKey` was minted once at enqueue time so it survives redelivery, but
|
|
127
|
+
* nothing downstream reads it: no transport forwards it to the provider (Resend
|
|
128
|
+
* dedupes on an `Idempotency-Key` REQUEST header, which the provider client offers
|
|
129
|
+
* no hook for — its `headers` field becomes message headers in the body). A
|
|
130
|
+
* consumer that only acks is correct for the failure the ack covers and sends a
|
|
131
|
+
* duplicate for the one it does not: the provider accepted the message and the
|
|
132
|
+
* worker died before acking.
|
|
133
|
+
*
|
|
134
|
+
* The recipe below NARROWS that window; it does not close it, and nothing on
|
|
135
|
+
* this path can. The mark is written after the provider accepted the message, so
|
|
136
|
+
* a crash in between still redelivers and resends, and a KV read is eventually
|
|
137
|
+
* consistent — a redelivery seconds later can miss a mark that was written.
|
|
138
|
+
* Delivery is at-least-once end to end. An app that must not double-send needs a
|
|
139
|
+
* strongly-consistent mark taken BEFORE the send (a Durable Object or D1 row
|
|
140
|
+
* claimed by this key, released on failure), and even then the send-then-crash
|
|
141
|
+
* window is only ever traded for a send-that-may-not-have-happened one.
|
|
142
|
+
*
|
|
125
143
|
* ```ts
|
|
126
144
|
* export default {
|
|
127
145
|
* queue: async (batch, env) => {
|
|
128
146
|
* const mailer = createMailer({ apiKey: env.RESEND_API_KEY, from: "..." });
|
|
129
147
|
* for (const message of batch.messages) {
|
|
148
|
+
* const { idempotencyKey } = message.body;
|
|
149
|
+
* if (await env.SENT.get(idempotencyKey)) { message.ack(); continue; }
|
|
130
150
|
* await consumeQueuedSend(mailer, message.body);
|
|
151
|
+
* await env.SENT.put(idempotencyKey, "1", { expirationTtl: 86_400 });
|
|
131
152
|
* message.ack();
|
|
132
153
|
* }
|
|
133
154
|
* },
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createCaptureTransport as a}from"./packem_shared/createCaptureTransport-CKc6NpZR.mjs";import{createCloudflareTransport as t}from"./packem_shared/createCloudflareTransport-DpfCxn16.mjs";import{default as u}from"./packem_shared/createMailer-CjQmT7Ln.mjs";import{createCaptureSink as f,createMailerFromEnv as l,shouldCaptureMail as m}from"./packem_shared/createCaptureSink-
|
|
1
|
+
import{createCaptureTransport as a}from"./packem_shared/createCaptureTransport-CKc6NpZR.mjs";import{createCloudflareTransport as t}from"./packem_shared/createCloudflareTransport-DpfCxn16.mjs";import{default as u}from"./packem_shared/createMailer-CjQmT7Ln.mjs";import{createCaptureSink as f,createMailerFromEnv as l,shouldCaptureMail as m}from"./packem_shared/createCaptureSink-CeV0DSsT.mjs";import{consumeQueuedSend as s,toQueuedPayload as c}from"./packem_shared/consumeQueuedSend-CRfALnCU.mjs";import{default as i}from"./packem_shared/renderEmail-BOUnr6i3.mjs";import{default as M}from"./packem_shared/createResendTransport-DDkOitck.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};
|
package/dist/packem_shared/{capture-transport.d-8CDAv51Z.d.mts → capture-transport.d-BD67fxbB.d.mts}
RENAMED
|
@@ -39,8 +39,12 @@ interface SendOptions {
|
|
|
39
39
|
* redelivery. Only meaningful for `mailer.queue()` — `mailer.send()` ignores it.
|
|
40
40
|
* When omitted, `queue()` generates one at enqueue time so it survives redelivery
|
|
41
41
|
* (a key minted in the consumer would change on every retry, defeating the point).
|
|
42
|
-
* Not forwarded to the mail provider
|
|
43
|
-
*
|
|
42
|
+
* Not forwarded to the mail provider, and no transport can: Resend dedupes on an
|
|
43
|
+
* `Idempotency-Key` REQUEST header, and the provider client exposes no hook for
|
|
44
|
+
* one (its own `headers` field becomes message headers in the JSON body). So a
|
|
45
|
+
* consumer that wants to collapse redeliveries MUST dedupe against its own store
|
|
46
|
+
* using this key — see `consumeQueuedSend` for the shape, and for why that is a
|
|
47
|
+
* narrowed at-least-once rather than exactly-once.
|
|
44
48
|
*/
|
|
45
49
|
idempotencyKey?: string;
|
|
46
50
|
react?: ReactElement;
|
package/dist/packem_shared/{capture-transport.d-8CDAv51Z.d.ts → capture-transport.d-BD67fxbB.d.ts}
RENAMED
|
@@ -39,8 +39,12 @@ interface SendOptions {
|
|
|
39
39
|
* redelivery. Only meaningful for `mailer.queue()` — `mailer.send()` ignores it.
|
|
40
40
|
* When omitted, `queue()` generates one at enqueue time so it survives redelivery
|
|
41
41
|
* (a key minted in the consumer would change on every retry, defeating the point).
|
|
42
|
-
* Not forwarded to the mail provider
|
|
43
|
-
*
|
|
42
|
+
* Not forwarded to the mail provider, and no transport can: Resend dedupes on an
|
|
43
|
+
* `Idempotency-Key` REQUEST header, and the provider client exposes no hook for
|
|
44
|
+
* one (its own `headers` field becomes message headers in the JSON body). So a
|
|
45
|
+
* consumer that wants to collapse redeliveries MUST dedupe against its own store
|
|
46
|
+
* using this key — see `consumeQueuedSend` for the shape, and for why that is a
|
|
47
|
+
* narrowed at-least-once rather than exactly-once.
|
|
44
48
|
*/
|
|
45
49
|
idempotencyKey?: string;
|
|
46
50
|
react?: ReactElement;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCaptureTransport as l}from"./createCaptureTransport-CKc6NpZR.mjs";import"./createCloudflareTransport-DpfCxn16.mjs";import n from"./createMailer-CjQmT7Ln.mjs";import{LunoraError as u}from"@lunora/errors";import{D as c,p as E}from"./shard-CRhAKK93.mjs";import"./consumeQueuedSend-CRfALnCU.mjs";import"./renderEmail-BOUnr6i3.mjs";import"./createResendTransport-DDkOitck.mjs";const p="__lunora_admin__:recordMail",N=/^(?:dev(?:elopment)?|local(?:host)?|test)$/iu,_=["CF_ENV","ENVIRONMENT","NODE_ENV","WORKER_ENV"],f=(e,t)=>{const r=e[t];if(typeof r!="string"||r==="")throw new u("INTERNAL",`@lunora/mail: missing env var \`${t}\` — set it in .dev.vars (and \`wrangler secret put ${t}\` for secrets).`);return r},m=e=>{const t=e.LUNORA_MAIL_CAPTURE;if(typeof t=="string"){const r=t.toLowerCase();if(r==="1"||r==="true")return!0;if(r==="0"||r==="false")return!1;console.warn(`@lunora/mail: unrecognized LUNORA_MAIL_CAPTURE value "${t}" — expected "1"/"true" or "0"/"false"; falling back to environment detection.`)}return _.some(r=>{const o=e[r];return typeof o=="string"&&N.test(o)})};let d=!1;const A=(e,t=c,r)=>({record:async o=>{const i=e.SHARD,a=typeof e.LUNORA_ADMIN_TOKEN=="string"?e.LUNORA_ADMIN_TOKEN:void 0;if(i===void 0||a===void 0)return d||(d=!0,console.warn("@lunora/mail: capturing mail but there is nowhere to record it — the `SHARD` binding and/or `LUNORA_ADMIN_TOKEN` is missing, so every captured message is discarded. Set both to see mail in the studio inbox, or set LUNORA_MAIL_CAPTURE=0 to deliver for real.")),{id:"uncaptured"};try{return{id:(await E(i,{adminToken:a,envelope:{args:o,functionPath:p},jurisdiction:r,label:"@lunora/mail: recording captured mail",shardKey:t})).result?.id??"captured"}}catch(s){return console.error("@lunora/mail: failed to record captured mail into the studio inbox —",s),{id:"uncaptured"}}}}),h=(e,t={})=>{const r=f(e,"MAIL_FROM");if(m(e))return n({from:r,transport:l(A(e,t.rootShard,t.jurisdiction))});if(t.cloudflareSend)return n({cloudflareSend:t.cloudflareSend,from:r});const o=typeof e.RESEND_API_KEY=="string"?e.RESEND_API_KEY:void 0;if(o!==void 0&&o!=="")return n({apiKey:o,from:r});throw new u("INTERNAL","@lunora/mail: no transport configured — provide `cloudflareSend` (a SEND_EMAIL binding) or RESEND_API_KEY, or run in a dev environment to capture.")};export{A as createCaptureSink,h as createMailerFromEnv,m as shouldCaptureMail};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as d}from"@lunora/errors";import{D as h,p as l}from"./shard-CRhAKK93.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)},m=r=>r===!0||r===void 0,A=r=>{const n=r.onError??u;return async(t,e,c)=>{const a={ctx:c,env:e,message:t};let i;try{if(i=await r.parse(t.raw),r.verify){const o=await r.verify(i,a);if(!m(o))throw new d("INTERNAL","@lunora/mail/inbound: sender verification rejected the message")}}catch(o){await n(o,a);return}try{await r.dispatch(i,a)}catch(o){if(r.onError)try{await r.onError(o,a)}catch(s){console.error("@lunora/mail/inbound: onError threw while reporting a dispatch failure —",s)}if(r.retain)try{await r.retain(i,a,o);return}catch(s){console.error("@lunora/mail/inbound: retain failed to take ownership of the message —",s)}u(o,a)}}},y=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"}})},N=r=>{const n=r.shardKey??h,t=r.resolveArgs??(e=>y(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 i={args:t(e,c),functionPath:r.functionPath,shardKey:n};await l(r.shard,{adminToken:a,envelope:i,jurisdiction:r.jurisdiction,label:`@lunora/mail/inbound: dispatch to \`${r.functionPath}\``,shardKey:n})}};export{A as createInboundEmailHandler,N as dispatchToLunoraFunction};
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CapturedMail } from "./packem_shared/capture-transport.d-
|
|
1
|
+
import { C as CapturedMail } from "./packem_shared/capture-transport.d-BD67fxbB.mjs";
|
|
2
2
|
import 'react';
|
|
3
3
|
/** Minimal `fetch` projection so a test can inject a stub. */
|
|
4
4
|
type FetchLike = (input: string, init?: {
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CapturedMail } from "./packem_shared/capture-transport.d-
|
|
1
|
+
import { C as CapturedMail } from "./packem_shared/capture-transport.d-BD67fxbB.js";
|
|
2
2
|
import 'react';
|
|
3
3
|
/** Minimal `fetch` projection so a test can inject a stub. */
|
|
4
4
|
type FetchLike = (input: string, init?: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/mail",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.63",
|
|
4
4
|
"description": "Email for Lunora: Resend adapter, TSX templates, and queue-backed sends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
57
|
+
"@lunora/errors": "1.0.0-alpha.31",
|
|
58
58
|
"@react-email/render": "2.1.0",
|
|
59
59
|
"@visulima/email": "2.1.8",
|
|
60
60
|
"postal-mime": "2.7.6"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{createCaptureTransport as d}from"./createCaptureTransport-CKc6NpZR.mjs";import"./createCloudflareTransport-DpfCxn16.mjs";import n from"./createMailer-CjQmT7Ln.mjs";import{LunoraError as c}from"@lunora/errors";import{D as s,p as l}from"./shard-CRhAKK93.mjs";import"./consumeQueuedSend-CRfALnCU.mjs";import"./renderEmail-BOUnr6i3.mjs";import"./createResendTransport-DDkOitck.mjs";const E="__lunora_admin__:recordMail",p=/^(?:dev(?:elopment)?|local(?:host)?|test)$/iu,f=["CF_ENV","ENVIRONMENT","NODE_ENV","WORKER_ENV"],_=(t,e)=>{const r=t[e];if(typeof r!="string"||r==="")throw new c("INTERNAL",`@lunora/mail: missing env var \`${e}\` — set it in .dev.vars (and \`wrangler secret put ${e}\` for secrets).`);return r},N=t=>{const e=t.LUNORA_MAIL_CAPTURE;if(typeof e=="string"){const r=e.toLowerCase();if(r==="1"||r==="true")return!0;if(r==="0"||r==="false")return!1;console.warn(`@lunora/mail: unrecognized LUNORA_MAIL_CAPTURE value "${e}" — expected "1"/"true" or "0"/"false"; falling back to environment detection.`)}return f.some(r=>{const o=t[r];return typeof o=="string"&&p.test(o)})},m=(t,e=s,r)=>({record:async o=>{const i=t.SHARD,a=typeof t.LUNORA_ADMIN_TOKEN=="string"?t.LUNORA_ADMIN_TOKEN:void 0;if(i===void 0||a===void 0)return{id:"uncaptured"};try{return{id:(await l(i,{adminToken:a,envelope:{args:o,functionPath:E},jurisdiction:r,label:"@lunora/mail: recording captured mail",shardKey:e})).result?.id??"captured"}}catch(u){return console.error("@lunora/mail: failed to record captured mail into the studio inbox —",u),{id:"uncaptured"}}}}),L=(t,e={})=>{const r=_(t,"MAIL_FROM");if(N(t))return n({from:r,transport:d(m(t,e.rootShard,e.jurisdiction))});if(e.cloudflareSend)return n({cloudflareSend:e.cloudflareSend,from:r});const o=typeof t.RESEND_API_KEY=="string"?t.RESEND_API_KEY:void 0;if(o!==void 0&&o!=="")return n({apiKey:o,from:r});throw new c("INTERNAL","@lunora/mail: no transport configured — provide `cloudflareSend` (a SEND_EMAIL binding) or RESEND_API_KEY, or run in a dev environment to capture.")};export{m as createCaptureSink,L as createMailerFromEnv,N as shouldCaptureMail};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as d}from"@lunora/errors";import{D as h,p as l}from"./shard-CRhAKK93.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};
|