Skip to content

feat(email): dual-write non-inbound Mailbox state - #1124

Merged
kody-bot merged 11 commits into
mainfrom
cursor/mailbox-do-810a
Aug 1, 2026
Merged

kody-bot merged 11 commits into
mainfrom
cursor/mailbox-do-810a

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Aug 1, 2026 •

Copy link
Copy Markdown
Member

Summary

Wires the first live phase-2 Mailbox mirrors while keeping D1 authoritative:

  • outbound terminal message/thread/attachment/event graphs mirror after successful D1 mutations
  • provider lifecycle recorded, duplicate, and stale outcomes schedule full graph repair through queue waitUntil when a message is bound
  • classification dual-write is centralized in the email service
  • every DO RPC is capped at one second with distinct outcome telemetry
  • event repair uses one batch RPC, keeps newest events on truncation, and reports truncation

D1 remains the sole source of truth. This PR does not touch inbound delivery leases/finalization/effects, retention deletes, deletion fencing, or read authority.

Verification

Focused tests, local/CI/post-merge validation, production health/smoke checks, and independent reviews are green.

System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 0684b78 · Head: d747c2d

Classification: extends — live outbound/classification/provider paths compose D1-authoritative writes with the Mailbox shadow and parity stream.

Primitives touched

Primitive Group Impact
email assistant extends — outbound and classification graph mirrors
mailbox storage extends — bounded batch graph repair and live telemetry
email-delivery-queue storage extends — persisted provider outcomes schedule graph repair
app-ui surfaces composes — classification delegates to canonical service
email-blobs-r2 storage composes — blob authority unchanged

System map

D1 commits remain authoritative; afterward non-inbound paths repair Mailbox and emit outcome telemetry.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	email["email<br/>Email"]:::extended
	d1["d1-app-db<br/>D1 app database"]:::untouched
	queue["email-delivery-queue<br/>Email delivery event queue"]:::extended
	mailbox["mailbox<br/>Mailbox"]:::extended
	reporting["email-reporting<br/>Email reporting stream"]:::untouched
	email -->|"authoritative writes"| d1
	queue -->|"authoritative provider lifecycle"| d1
	email -->|"post-commit graph repair"| mailbox
	queue -->|"persisted outcome; waitUntil repair"| mailbox
	mailbox -->|"message + batch outcomes"| reporting
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Invariants

  • D1 responses and retries never depend on Mailbox success.
  • Message settles before one bounded event batch; newest 100 retained if truncated.
  • Provider subscription semantics remain unchanged.
  • Inbound durability, charging, R2 bytes, and delete ordering are untouched.

Conductor report

  • STATUS: done
  • What shipped: non-inbound live dual-write for outbound terminal graphs, persisted provider outcomes, and canonical classification; bounded telemetry
  • Risk self-assessment: medium — bounded best-effort side effects with unchanged D1 authority and response semantics
  • Merged/deployed: yes — PR #1124, merge commit, post-merge CI, production deploy
  • Sibling scope spill: none; no entitlements/UserMeter, deletion fencing, inbound lifecycle, scheduled lanes, or retention-lane edits
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added live Mailbox mirroring for outbound messages, delivery events, threads, attachments, and message classifications.
    • Added batched delivery-event synchronization with bounded processing and per-event results.
    • Added telemetry for mirror operations, including skipped, missing, timed-out, and failed outcomes.
    • Delivery-event processing now exposes parsed provider event details.
  • Bug Fixes

    • Mailbox synchronization failures and timeouts no longer block email delivery acknowledgements or outcomes.
    • Improved handling of missing records, duplicate events, and partial mirror results.
  • Documentation

    • Updated migration documentation to clarify current Mailbox rollout coverage and remaining work.

cursoragent and others added 9 commits August 1, 2026 07:25
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR wires non-inbound D1-to-Mailbox mirroring for outbound messages, provider delivery events, and classification updates. It adds bounded delivery-event batch RPCs, graph orchestration, timeout and telemetry outcomes, asynchronous queue integration, and migration documentation.

Changes

Mailbox live mirroring

Layer / File(s) Summary
Delivery-event batch contracts and persistence
packages/worker/src/email/mailbox-types.ts, packages/worker/src/email/mailbox-snapshot-repo.ts, packages/worker/src/email/mailbox-do.ts, packages/worker/src/email/*test*
Adds bounded batch inputs and results, message-scoped event loaders, transactional upserts, owner validation, retention scheduling, and rollback coverage.
Shared mirror execution and telemetry
packages/worker/src/email/mailbox-mirror.ts, packages/worker/src/email/mailbox-parity-events.ts, packages/worker/src/email/*test*
Adds timeout-safe RPC execution, missing and timeout outcomes, batch event mapping, and parity telemetry.
D1 graph orchestration and provider-event flow
packages/worker/src/email/mailbox-live-mirror.ts, packages/worker/src/email/delivery-events.ts, packages/worker/src/email/delivery-queue.ts, packages/worker/src/email/*test*
Mirrors message graphs in ordered, bounded batches. Provider events now expose providerEvent and schedule asynchronous graph mirroring.
Outbound and classification callers
packages/worker/src/email/outbound.ts, packages/worker/src/email/service.ts, packages/worker/src/email/repo.ts, packages/worker/src/app/handlers/account-email.ts, packages/worker/src/mcp/capabilities/email/*
Mirrors outbound terminal states and successful classification updates after D1 persistence. Callers pass the worker environment to the service layer.
Phase 2 migration documentation
docs/contributing/architecture/data-storage.md
Documents the wired non-inbound Phase 2 scope, batching, truncation, timeouts, telemetry, and remaining work.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OutboundOrClassifier
  participant D1
  participant MailboxLiveMirror
  participant Mailbox
  participant AnalyticsEngine
  OutboundOrClassifier->>D1: persist outbound state or classification
  OutboundOrClassifier->>MailboxLiveMirror: invoke graph mirroring
  MailboxLiveMirror->>D1: load authoritative message graph
  MailboxLiveMirror->>Mailbox: mirror message, thread, attachments, and event batch
  MailboxLiveMirror->>AnalyticsEngine: record mirror outcome
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the PR's main change: dual-writing non-inbound email state to Mailbox.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/mailbox-do-810a

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kody-bot
kody-bot marked this pull request as ready for review August 1, 2026 10:53
@github-actions

github-actions Bot commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1124.kody-a99.workers.dev

Worker: kody-pr-1124
D1: kody-pr-1124-db
KV: kody-pr-1124-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/src/email/delivery-queue.ts (1)

34-78: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Mailbox mirror never runs for two persisted-event outcomes: stale-but-inserted and post-dispatch-failure duplicate.

The mailbox graph repair only runs when result.outcome === 'recorded' (lines 66-75). Two situations bypass this even though D1 has already changed:

  1. recordProviderEmailDeliveryEvent returns stale both when nothing was inserted and when a new event row was inserted but did not win the "latest status" race (inserted === true, updatedLatestStatus === false). In the second case, a new email_delivery_events row exists in D1, but the queue handler treats it identically to the no-op case and never mirrors it.
  2. If dispatchEmailDeliverySubscriptionEvents throws after a successful recorded write, the outer catch retries the queue message before the mirror call executes. On retry, the same provider event now conflicts in D1 and resolves to duplicate (since message.deliveryStatus/deliveryStatusAt were already updated by the first attempt). duplicate never triggers the mirror, so that terminal delivery event is permanently absent from the Mailbox mirror.

Because mirrorMailboxMessageGraphFromD1 performs a full graph repair from D1 (not an incremental single-event mirror), extending the trigger to stale and duplicate outcomes closes both gaps at negligible extra cost, consistent with the mirror's best-effort design.

♻️ Proposed fix to also repair on stale/duplicate outcomes
 				case 'duplicate':
+				case 'stale':
 				case 'recorded': {
 					// Abuse evaluation runs before subscription dispatch (and
 					// also for replayed duplicates) so a crash or dispatch
 					// failure can never skip the pause; the pause write itself
 					// is idempotent.
 					await applyOutboundEmailAbusePause({
 						env,
 						userId: result.message.userId,
 						deliveryStatus: result.providerEvent.payload.delivery.status,
 						eventRecorded: result.outcome === 'recorded',
 					})
-					await dispatchEmailDeliverySubscriptionEvents({
-						env,
-						message: result.message,
-						providerEvent: result.providerEvent,
-						waitUntil,
-					})
-					if (result.outcome === 'recorded' && result.message != null) {
+					if (result.outcome !== 'stale') {
+						await dispatchEmailDeliverySubscriptionEvents({
+							env,
+							message: result.message,
+							providerEvent: result.providerEvent,
+							waitUntil,
+						})
+					}
+					if (result.message != null) {
 						waitUntil(
 							mirrorMailboxMessageGraphFromD1({
 								env,
 								db: env.APP_DB,
 								userId: result.message.userId,
 								messageId: result.message.id,
 							}),
 						)
 					}
 					queueMessage.ack()
 					break
 				}

Note: this keeps stale out of subscription dispatch (preserving existing dispatch semantics) while still catching it up via the mirror repair. Confirm the stale case should keep the pre-existing (non-mirror) behavior for dispatch before merging this diff verbatim.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/delivery-queue.ts` around lines 34 - 78, Extend the
mailbox mirror trigger in the queue handler to also run for `stale` and
`duplicate` outcomes, while preserving the existing stale-case behavior of
skipping subscription dispatch. Invoke `mirrorMailboxMessageGraphFromD1` when a
persisted event may have changed D1, using the existing message, userId, and
messageId guards; keep the current `recorded` mirroring and best-effort
`waitUntil` flow intact.
🧹 Nitpick comments (4)
packages/worker/src/email/mailbox-types.ts (1)

23-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider an independent constant for the batch bound.

mailboxUpsertDeliveryEventsMax derives from mailboxDefaultPageSize. The two limits control unrelated concerns: read pagination and the transactional write batch size. A future page-size tuning change would silently change the DO batch bound and the live-mirror truncation threshold (mailboxLiveMirrorMaxEvents). Define a dedicated numeric constant instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/mailbox-types.ts` around lines 23 - 24, The
mailboxUpsertDeliveryEventsMax limit incorrectly reuses mailboxDefaultPageSize,
coupling read pagination with transactional write batching and
mailboxLiveMirrorMaxEvents. Define mailboxUpsertDeliveryEventsMax as its own
explicit numeric constant in mailbox-types.ts, leaving mailboxDefaultPageSize
independent.
packages/worker/src/email/mailbox-snapshot-repo.ts (1)

129-155: 🚀 Performance & Scalability | 🔵 Trivial

Confirm an index supports this owner-scoped ordered lookup.

The query filters on message_id and user_id and orders by created_at DESC, id DESC. Without a composite index on email_delivery_events (user_id, message_id, created_at), D1 sorts the matching rows for every mirror call. Mirroring runs on outbound terminal paths, provider-event repair, and classification updates, so this query is on hot paths.

#!/bin/bash
# Inspect declared indexes on email_delivery_events across migrations and schema files.
fd -t f -e sql | xargs rg -n -i -C2 'email_delivery_events'
rg -n -i -C2 'email_delivery_events' --glob '*schema*'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/mailbox-snapshot-repo.ts` around lines 129 - 155,
Ensure the database schema declares a composite index for the lookup in
listEmailDeliveryEventMirrorProjectionsForMessage, covering user_id, message_id,
and created_at in the query’s filtering and ordering sequence. Add the index
through the project’s established migration or schema mechanism, preserving the
existing query behavior and tie-breaking by id.
packages/worker/src/email/mailbox-live-mirror.node.test.ts (1)

473-495: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a cross-owner isolation test for the graph mirror.

The suite covers missing messages, truncation, timeout, and error mapping. It does not cover a foreign owner. Seed msg-1 for user-aaa, then call mirrorMailboxMessageGraphFromD1 with userId: 'user-bbb'. Assert the result is { status: 'missing' } and that neither mirrorMessage nor upsertDeliveryEvents is called. This locks the per-user isolation contract for the new mirror path.

As per coding guidelines: "Every signed-in user must have a fully isolated personal assistant, including separate packages, jobs, secrets, values, memories, remote connectors, email inboxes, and durable storage."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/mailbox-live-mirror.node.test.ts` around lines 473
- 495, Add a cross-owner isolation test alongside the existing missing-message
test: seed msg-1 for user-aaa, invoke mirrorMailboxMessageGraphFromD1 with
user-bbb, and assert the response reports status missing. Also verify both
mirrorMessage and upsertDeliveryEvents are not called, preserving per-user
isolation for the graph mirror path.

Source: Coding guidelines

packages/worker/src/email/mailbox-live-mirror.ts (1)

146-158: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider loading attachments and the thread concurrently.

The message load, the attachment load, and the thread load run in sequence. The attachment load and the thread load do not depend on each other. Both run on outbound send paths, so each round trip adds latency to the caller. Await them together after the message resolves.

♻️ Proposed concurrent load
-		const attachments = await listEmailAttachmentsForMessage({
-			db: input.db,
-			messageId: input.messageId,
-		})
-
-		let thread = input.thread
-		if (thread === undefined && message.threadId) {
-			thread = await getEmailThreadById({
-				db: input.db,
-				userId: input.userId,
-				threadId: message.threadId,
-			})
-		}
+		const threadId = message.threadId
+		const [attachments, loadedThread] = await Promise.all([
+			listEmailAttachmentsForMessage({
+				db: input.db,
+				messageId: input.messageId,
+			}),
+			input.thread === undefined && threadId
+				? getEmailThreadById({
+						db: input.db,
+						userId: input.userId,
+						threadId,
+					})
+				: Promise.resolve(input.thread),
+		])
+		const thread = loadedThread
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/mailbox-live-mirror.ts` around lines 146 - 158,
Update the message-processing flow around listEmailAttachmentsForMessage and
getEmailThreadById so the independent attachment and thread loads start
concurrently after the message resolves, using a shared await rather than
sequential awaits. Preserve the existing conditional behavior for loading the
thread only when input.thread is undefined and message.threadId is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/contributing/architecture/data-storage.md`:
- Around line 759-770: Update the Analytics Engine budget statement in the “Live
graph orchestrator” documentation to say that each graph call performs at most
two writes, preserving the existing explanation that these are one message
outcome write and one batch outcome write.

In `@packages/worker/src/email/mailbox-live-mirror.ts`:
- Around line 170-179: When `eventsTruncated` is true in the mailbox delivery
event mirroring flow, emit a `console.warn` or existing parity telemetry signal
before returning the summary, including enough context to identify the affected
message and owner. Keep the existing event slicing and return behavior
unchanged, and anchor the change in the function containing `loadedEvents` and
`eventsTruncated` rather than the callers that ignore the summary.

In `@packages/worker/src/email/outbound-mailbox-mirror.workers.test.ts`:
- Around line 32-41: Update the outbound mailbox test stub to include the
missing upsertDeliveryEvents method alongside the existing RPC methods, and add
mailbox-mirror-delivery-event-batch-failed to the warning suppression used by
the relevant tests. Preserve the existing hang and throw behaviors so batch
timeout and error-path assertions exercise the intended
mirrorMailboxDeliveryEventSnapshots flow.

In `@packages/worker/src/email/outbound.ts`:
- Around line 652-658: Move best-effort mailbox mirroring off request threads by
adding an optional waitUntil/ExecutionContext flow: in
packages/worker/src/email/outbound.ts at lines 652-658 and terminals 697, 765,
and 801, accept it through SendEmailEnv/EmailSendInput and schedule
mirrorMailboxGraph() instead of awaiting it, ensuring it runs outside
withAccountWriteLease; in packages/worker/src/email/service.ts at lines 735-740,
add optional waitUntil to setEmailMessageClassification, schedule
mirrorMailboxMessageGraphFromD1 with it, and pass it from both the account-email
handler and email_message_classify capability.

---

Outside diff comments:
In `@packages/worker/src/email/delivery-queue.ts`:
- Around line 34-78: Extend the mailbox mirror trigger in the queue handler to
also run for `stale` and `duplicate` outcomes, while preserving the existing
stale-case behavior of skipping subscription dispatch. Invoke
`mirrorMailboxMessageGraphFromD1` when a persisted event may have changed D1,
using the existing message, userId, and messageId guards; keep the current
`recorded` mirroring and best-effort `waitUntil` flow intact.

---

Nitpick comments:
In `@packages/worker/src/email/mailbox-live-mirror.node.test.ts`:
- Around line 473-495: Add a cross-owner isolation test alongside the existing
missing-message test: seed msg-1 for user-aaa, invoke
mirrorMailboxMessageGraphFromD1 with user-bbb, and assert the response reports
status missing. Also verify both mirrorMessage and upsertDeliveryEvents are not
called, preserving per-user isolation for the graph mirror path.

In `@packages/worker/src/email/mailbox-live-mirror.ts`:
- Around line 146-158: Update the message-processing flow around
listEmailAttachmentsForMessage and getEmailThreadById so the independent
attachment and thread loads start concurrently after the message resolves, using
a shared await rather than sequential awaits. Preserve the existing conditional
behavior for loading the thread only when input.thread is undefined and
message.threadId is present.

In `@packages/worker/src/email/mailbox-snapshot-repo.ts`:
- Around line 129-155: Ensure the database schema declares a composite index for
the lookup in listEmailDeliveryEventMirrorProjectionsForMessage, covering
user_id, message_id, and created_at in the query’s filtering and ordering
sequence. Add the index through the project’s established migration or schema
mechanism, preserving the existing query behavior and tie-breaking by id.

In `@packages/worker/src/email/mailbox-types.ts`:
- Around line 23-24: The mailboxUpsertDeliveryEventsMax limit incorrectly reuses
mailboxDefaultPageSize, coupling read pagination with transactional write
batching and mailboxLiveMirrorMaxEvents. Define mailboxUpsertDeliveryEventsMax
as its own explicit numeric constant in mailbox-types.ts, leaving
mailboxDefaultPageSize independent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e02edb40-8778-472b-bc2d-3e97b3a5dd71

📥 Commits

Reviewing files that changed from the base of the PR and between 0684b78 and e9e9f18.

📒 Files selected for processing (26)
  • docs/contributing/architecture/data-storage.md
  • packages/worker/src/app/handlers/account-email.node.test.ts
  • packages/worker/src/app/handlers/account-email.ts
  • packages/worker/src/email/delivery-events.ts
  • packages/worker/src/email/delivery-events.workers.test.ts
  • packages/worker/src/email/delivery-queue.node.test.ts
  • packages/worker/src/email/delivery-queue.ts
  • packages/worker/src/email/mailbox-do.ts
  • packages/worker/src/email/mailbox-do.workers.test.ts
  • packages/worker/src/email/mailbox-live-mirror.node.test.ts
  • packages/worker/src/email/mailbox-live-mirror.ts
  • packages/worker/src/email/mailbox-mirror.node.test.ts
  • packages/worker/src/email/mailbox-mirror.ts
  • packages/worker/src/email/mailbox-parity-events.node.test.ts
  • packages/worker/src/email/mailbox-parity-events.ts
  • packages/worker/src/email/mailbox-snapshot-repo.node.test.ts
  • packages/worker/src/email/mailbox-snapshot-repo.ts
  • packages/worker/src/email/mailbox-types.ts
  • packages/worker/src/email/outbound-mailbox-mirror.workers.test.ts
  • packages/worker/src/email/outbound.ts
  • packages/worker/src/email/repo-body-limits.workers.test.ts
  • packages/worker/src/email/repo.ts
  • packages/worker/src/email/service-classification.node.test.ts
  • packages/worker/src/email/service.ts
  • packages/worker/src/mcp/capabilities/email/email-message-classify.node.test.ts
  • packages/worker/src/mcp/capabilities/email/email-message-classify.ts

Comment thread docs/contributing/architecture/data-storage.md Outdated
Comment thread packages/worker/src/email/mailbox-live-mirror.ts
Comment thread packages/worker/src/email/outbound-mailbox-mirror.workers.test.ts
Comment on lines +652 to +658
const mirrorMailboxGraph = () =>
mirrorOutboundMailboxMessageGraph({
env: input.env,
userId: input.userId,
messageId: message.id,
createdThread,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Best-effort Mailbox mirroring is awaited on request threads. Both callers await mirrorMailboxMessageGraphFromD1 and discard its summary. That call performs several D1 reads plus two sequential Mailbox RPCs, each capped at mailboxMirrorRpcTimeoutMs, so a slow Mailbox Durable Object adds the full budget to a user-visible response. Schedule the mirror instead of blocking on it.

  • packages/worker/src/email/outbound.ts#L652-L658: accept an ExecutionContext (or a waitUntil callback) in SendEmailEnv/EmailSendInput and schedule mirrorMailboxGraph() with it at the three terminals (Lines 697, 765, 801), so the mirror never runs inside withAccountWriteLease.
  • packages/worker/src/email/service.ts#L735-L740: accept an optional waitUntil in setEmailMessageClassification and schedule mirrorMailboxMessageGraphFromD1 on it; pass it from the account-email handler and the email_message_classify capability.
📍 Affects 2 files
  • packages/worker/src/email/outbound.ts#L652-L658 (this comment)
  • packages/worker/src/email/service.ts#L735-L740
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/email/outbound.ts` around lines 652 - 658, Move
best-effort mailbox mirroring off request threads by adding an optional
waitUntil/ExecutionContext flow: in packages/worker/src/email/outbound.ts at
lines 652-658 and terminals 697, 765, and 801, accept it through
SendEmailEnv/EmailSendInput and schedule mirrorMailboxGraph() instead of
awaiting it, ensuring it runs outside withAccountWriteLease; in
packages/worker/src/email/service.ts at lines 735-740, add optional waitUntil to
setEmailMessageClassification, schedule mirrorMailboxMessageGraphFromD1 with it,
and pass it from both the account-email handler and email_message_classify
capability.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants