Skip to content

feat(email): move internal reads to Mailbox - #1160

Merged
kentcdodds merged 14 commits into
mainfrom
cursor/mailbox-step3-internal-readers
Aug 2, 2026
Merged

kentcdodds merged 14 commits into
mainfrom
cursor/mailbox-step3-internal-readers

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Aug 2, 2026 •

Copy link
Copy Markdown
Member

Summary

Moves USER internal email metadata consumers and account lifecycle surfaces to fail-closed Mailbox reads:

  • inbound effects/package subscription payloads and stored-message usage
  • account export uses one authoritative Mailbox section; USER D1 projections remain deletion-only
  • canonical Mailbox R2 reference traversal powers inventory, export, deletion, and revalidation
  • owner-bound R2-before-metadata message deletion with durable resurrection tombstones
  • account deletion captures Mailbox blob refs before R2 cleanup and purge
  • system:email remains explicitly D1-only; provider reverse index remains D1
  • ADR 0002 records the externally-keyed event → thin D1 reverse-index pattern

No D1 email writes/tables are removed in this step.

System recap — extends Mailbox and account lifecycle (medium risk)

Mode: recap · Base: main @ 2b0b60e2 · Head: ea0cca9b

Classification: extends — moves existing USER internal/account reads onto Mailbox and hardens authoritative deletion; no primitive is added.

Primitives touched

Primitive Group Impact
mailbox storage extends — internal reads, authoritative deletion, tombstones, retention retry rotation
account-export assistant extends — one Mailbox graph and Mailbox-backed R2 bytes
email assistant extends — internal effects/subscription reads
email-blobs-r2 storage composes — canonical owner-safe Mailbox references
entitlements auth composes — Mailbox stored-message count

System map

USER internal reads and account lifecycle flow through Mailbox; system email and provider reverse lookup remain D1 exceptions.

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
  effects["email<br/>Email internals"]:::extended
  export["account-export<br/>Account data export"]:::extended
  mailbox["mailbox<br/>Mailbox"]:::extended
  r2["email-blobs-r2<br/>Email blob storage"]:::touched
  d1["d1-app-db<br/>Compatibility projections/system mail"]:::untouched
  effects -->|"USER message + attachment reads"| mailbox
  export -->|"metadata + blob reference pages"| mailbox
  mailbox -->|"canonical owner-safe keys; delete before metadata"| r2
  effects -->|"system:email only"| d1
  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

  • USER internal reads fail closed; no feature flag or D1 fallback.
  • Mailbox blob inventory is captured before account purge.
  • R2 deletion succeeds before message metadata tombstoning/deletion.
  • Tombstones fence queued live/parity resurrection and detach delivery events.
  • Retention runs one R2-backed message per DO turn; per-message retry eligibility prevents head-of-line blocking.
  • D1 projections remain account-deletion targets until step 5.

Verification

  • CI at ea0cca9b: all required checks pass; Bugbot and CodeRabbit pass with no unresolved threads
  • 130 initial focused tests plus deletion/tombstone/retention regression suites pass
  • Typecheck, formatting, lint, migration ledger, docs checks pass
  • Full validation was run; unrelated parallel resource timeouts passed on isolated rerun
  • Two independent whole-diff review rounds: account deletion, R2 traversal, admin deletion, tombstone resurrection, retention concurrency/HOL findings addressed; final review reports no high/medium findings

Conductor report

  • STATUS: done
  • Sequence step: 3 internal readers + account surfaces
  • Risk: medium, non-destructive; no D1 write-off/drop
  • Backup gate: verified sha256 7787f8c9… remains recorded
  • Merged/deployed: pending self-merge — PR #1160
  • Next: self-merge/deploy, production verification, then step 4 from a fresh branch

PR creation note: Cursor ManagePullRequest remained pinned to the prior run branch; this clean branch PR was created/updated with the authenticated Kent GitHub helper to preserve the conductor-mandated fresh-branch history.


Note

Medium Risk
Touches account deletion, export, and authoritative email metadata/R2 ordering on a staged migration; changes are bounded by retained D1 projections and fail-closed Mailbox RPCs rather than dropping D1 writers in this step.

Overview
Step 3 cutover: USER internal consumers (inbound effects, package payloads, signed-in stored_email_messages usage, account export) route through mailbox-internal-read.ts with no feature flag or D1 fallback; system:email stays explicitly D1-backed.

Account export & R2: The four USER D1 email_* graph tables are excluded from export (includeInExport: false on data targets). The mailbox section and Mailbox.listBlobReferences are the sole USER graph and email-byte inventory; r2_object traversal is cursor v2 (legacy v1 cursors must restart without startAfter).

Deletion & lifecycle: Account deletion pages blob references, deletes canonical/defensive R2 keys, and only purges Mailbox after email cleanup completes with no warnings. USER admin delete_message uses owner-bound R2-before-metadata (deleteMessageWithBlobs); D1 projection cleanup follows. Deletion tombstones and parity purgeForParityRebuild block delayed mirror/parity from resurrecting deleted messages.

Mailbox DO: Retention processes one R2-backed message per turn with per-message hourly retry rows to avoid head-of-line blocking; mirrors and delivery writes detach tombstoned message_ids.

Docs/ADR updates describe step 3 ordering and the thin D1 reverse-index pattern for externally keyed events.

Reviewed by Cursor Bugbot for commit ea0cca9. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Account exports now include authoritative mailbox email data and associated file references.
    • Email usage counts now reflect current mailbox data.
    • Administrative email deletion supports authoritative messages and attachments.
    • Retention now retries failed file deletions and reports cleanup progress.
  • Bug Fixes

    • Improved account deletion reliability by verifying email files before mailbox cleanup.
    • Deleted messages no longer reappear through synchronization or delivery events.
  • Documentation

    • Updated storage architecture and data-placement guidance for mailbox ownership and external callbacks.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Mailbox becomes authoritative for USER email reads, exports, R2 references, deletion, and retention. D1 email tables remain compatibility projections and deletion targets. Tombstones and retry records fence stale writes and support retryable blob cleanup.

Changes

Mailbox-authoritative email migration

Layer / File(s) Summary
Internal Mailbox reads and integrations
packages/worker/src/email/*, packages/worker/src/entitlements/service.ts, packages/worker/src/app/*, packages/worker/src/mcp/capabilities/email/*
USER email reads, counts, attachments, exports, and blob references use owner-bound Mailbox operations. system:email continues to use D1.
Tombstones, retention, and Mailbox lifecycle
packages/worker/src/email/mailbox-*.ts
Mailbox adds deletion tombstones, retention retries, serialized R2-backed cleanup, owner-bound deletion RPCs, and tombstone-preserving parity purges.
Owner-bound deletion and account cleanup
packages/worker/src/admin/*, packages/worker/src/app/account-deletion.ts, packages/worker/src/email/repo.ts
Admin and account deletion capture authoritative Mailbox blob references, delete R2 objects before metadata cleanup, and retain retryable state when cleanup is incomplete.
Mailbox export and R2 inventory
packages/worker/src/account/*
Account export omits USER email projection tables and traverses Mailbox blob references with validated cursors. R2 inventory uses the same references.
Authority and migration documentation
docs/contributing/architecture/data-storage.md, docs/contributing/decisions/0002-data-placement.md
Documentation describes Mailbox authority, compatibility projections, retention retries, callback reverse indexes, and staged internal-read cutover.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant InternalRead
  participant Mailbox
  participant R2
  participant D1
  Caller->>InternalRead: request USER email data
  InternalRead->>Mailbox: owner-bound read or export
  Mailbox-->>InternalRead: authoritative metadata and blob references
  InternalRead->>R2: resolve or delete referenced objects
  R2-->>InternalRead: object result
  InternalRead->>D1: remove compatibility projection when required
  InternalRead-->>Caller: aggregate result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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.
Title check ✅ Passed The title clearly summarizes the primary change: moving internal email reads to Mailbox.
Description check ✅ Passed The description covers the change summary, testing evidence, system impact, risks, and migration boundaries.
✨ 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-step3-internal-readers

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.

cursoragent and others added 11 commits August 2, 2026 20:16
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>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds marked this pull request as ready for review August 2, 2026 21:07
@github-actions

github-actions Bot commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

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

Worker: kody-pr-1160
D1: kody-pr-1160-db
KV: kody-pr-1160-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: 2

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/account/r2-export.ts (1)

264-281: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Add a non-advancing pagination guard to prevent an infinite loop.

If page.references is empty while page.truncated is true, the code advances startAfter to page.nextStartAfter without checking whether it differs from the current startAfter. If Mailbox ever returns a non-advancing nextStartAfter in that state, listMailboxEmailObjectRefPage is called again with the identical startAfter, producing the identical result. The while (!cursor.current) loop in findNextRef then repeats synchronously without bound, hanging the request.

visitMailboxBlobReferencePages in packages/worker/src/account/mailbox-r2-references.ts guards this exact case by throwing when page.nextStartAfter == null || page.nextStartAfter === startAfter. Apply the same guard here.

🛠️ Proposed fix for the non-advancing pagination guard
 				const reference = page.references[0]
 				if (!reference) {
 					if (page.truncated && page.nextStartAfter != null) {
+						if (page.nextStartAfter === startAfter) {
+							throw new Error(
+								'Mailbox blob-reference pagination did not advance during R2 export.',
+							)
+						}
 						cursor = {
 							v: accountR2CursorVersion,
 							state: {
 								stage: 'mailbox_email_blob',
 								startAfter: page.nextStartAfter,
 							},
 						}
 						break
 					}
🤖 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/account/r2-export.ts` around lines 264 - 281, In the
empty-reference branch of the mailbox pagination loop, add the same
non-advancing cursor guard used by visitMailboxBlobReferencePages: before
updating cursor with page.nextStartAfter, throw when it is null or equals the
current startAfter. Only advance to the next page when the value is different,
preserving the existing done-cursor behavior otherwise.
🧹 Nitpick comments (3)
packages/worker/src/account/mailbox-r2-references.node.test.ts (1)

70-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for pageSize > 1 in listMailboxEmailObjectRefPage.

The current test only exercises pageSize: 1. Once listMailboxEmailObjectRefPage is fixed to track a per-item startAfter (see the companion comment on packages/worker/src/account/mailbox-r2-references.ts, lines 38-61), add a case with pageSize: 2 or higher that asserts each returned reference carries a distinct, correct source.startAfter value. This guards against the same regression recurring.

🤖 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/account/mailbox-r2-references.node.test.ts` around lines
70 - 98, Extend the test around listMailboxEmailObjectRefPage to request at
least two items and assert every returned reference has the correct distinct
source.startAfter value for its position. Preserve the existing resolution
assertions where applicable, and ensure the coverage would detect reuse of a
single startAfter across multiple references.
packages/worker/src/admin/mailbox-maintenance.node.test.ts (1)

103-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse canonicalMailboxMessageBlobReferences in the mock.

This block re-implements the production reference derivation from packages/worker/src/email/mailbox-retention.ts. If the production rules change, for example for the raw_mime direction condition or the attachment key equality check, the mock keeps the old behavior and the tests still pass.

Call the exported helper with the D1-derived attachment list instead.

♻️ Proposed fix
-			const blobReferences = [
-				...(message.direction === 'inbound'
-					? [
-							{
-								kind: 'raw_mime' as const,
-								key: emailRawMimeKey(userId, messageId),
-								messageId,
-								attachmentId: null,
-							},
-						]
-					: []),
-				...attachments.flatMap((attachment) => {
-					if (attachment.storageKind !== 'external') return []
-					const expected = emailAttachmentBlobKey(
-						userId,
-						messageId,
-						attachment.id,
-					)
-					return attachment.storageKey === expected
-						? [
-								{
-									kind: 'attachment' as const,
-									key: expected,
-									messageId,
-									attachmentId: attachment.id,
-								},
-							]
-						: []
-				}),
-			]
+			const blobReferences = canonicalMailboxMessageBlobReferences({
+				ownerId: userId,
+				messageId,
+				direction: message.direction,
+				attachments: attachments.map((attachment) => ({
+					id: attachment.id,
+					storage_key: attachment.storageKey,
+				})),
+			})

Add the import:

import { canonicalMailboxMessageBlobReferences } from '`#worker/email/mailbox-retention.ts`'
🤖 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/admin/mailbox-maintenance.node.test.ts` around lines 103
- 132, Replace the duplicated blobReferences derivation in the test setup with a
call to the exported canonicalMailboxMessageBlobReferences helper from
mailbox-retention, passing the D1-derived attachment list and required message
context. Add the helper import and remove the local direction, storageKind, and
storageKey checks so the mock follows production rules.
packages/worker/src/email/repo.ts (1)

1348-1355: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Fence the attachment delete on the owner too.

The ownership check at Line 1340 and the batch at Line 1348 are separate reads and writes. The message delete re-checks user_id, but the attachment delete uses only message_id. If ownership changes between the two steps, the attachment rows are still removed while the message row is retained.

The window is narrow, so this is defensive hardening rather than an active defect.

♻️ Proposed fix
 	await input.db.batch([
 		input.db
-			.prepare(`DELETE FROM email_attachments WHERE message_id = ?`)
-			.bind(input.messageId),
+			.prepare(
+				`DELETE FROM email_attachments
+				WHERE message_id = ?
+					AND EXISTS (
+						SELECT 1 FROM email_messages
+						WHERE id = ? AND user_id = ?
+					)`,
+			)
+			.bind(input.messageId, input.messageId, input.expectedUserId),
 		input.db
 			.prepare(`DELETE FROM email_messages WHERE id = ? AND user_id = ?`)
 			.bind(input.messageId, input.expectedUserId),
 	])
🤖 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/repo.ts` around lines 1348 - 1355, Update the
attachment DELETE in the batch within the message deletion flow to also
constrain the target by the expected owner, matching the ownership predicate
used by the email_messages DELETE. Bind the corresponding user identifier so
attachments are removed only when the message belongs to input.expectedUserId.
🤖 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 `@packages/worker/src/account/mailbox-r2-references.ts`:
- Around line 38-61: Update listMailboxEmailObjectRefPage so each mapped
reference’s source.startAfter identifies the item immediately preceding that
reference, rather than reusing input.startAfter for the entire page. Preserve
the source reference and pagination metadata, and ensure the cursor advances per
item so resolveMailboxEmailObjectRef revalidation with pageSize 1 selects the
matching reference for any page size.

In `@packages/worker/src/email/mailbox-do.ts`:
- Around line 972-983: Update purgeForParityRebuild to wrap the transactionSync
purge, alarm deletion, retentionAlarmArmed reset, and retentionIdleConfirmed
update inside the existing blockConcurrencyWhile input gate, matching purge’s
serialization behavior; return { ok: true } after the gated operation completes.

---

Outside diff comments:
In `@packages/worker/src/account/r2-export.ts`:
- Around line 264-281: In the empty-reference branch of the mailbox pagination
loop, add the same non-advancing cursor guard used by
visitMailboxBlobReferencePages: before updating cursor with page.nextStartAfter,
throw when it is null or equals the current startAfter. Only advance to the next
page when the value is different, preserving the existing done-cursor behavior
otherwise.

---

Nitpick comments:
In `@packages/worker/src/account/mailbox-r2-references.node.test.ts`:
- Around line 70-98: Extend the test around listMailboxEmailObjectRefPage to
request at least two items and assert every returned reference has the correct
distinct source.startAfter value for its position. Preserve the existing
resolution assertions where applicable, and ensure the coverage would detect
reuse of a single startAfter across multiple references.

In `@packages/worker/src/admin/mailbox-maintenance.node.test.ts`:
- Around line 103-132: Replace the duplicated blobReferences derivation in the
test setup with a call to the exported canonicalMailboxMessageBlobReferences
helper from mailbox-retention, passing the D1-derived attachment list and
required message context. Add the helper import and remove the local direction,
storageKind, and storageKey checks so the mock follows production rules.

In `@packages/worker/src/email/repo.ts`:
- Around line 1348-1355: Update the attachment DELETE in the batch within the
message deletion flow to also constrain the target by the expected owner,
matching the ownership predicate used by the email_messages DELETE. Bind the
corresponding user identifier so attachments are removed only when the message
belongs to input.expectedUserId.
🪄 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: 1fa81a7d-63bc-4a7e-be8b-0160462821b7

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0b60e and f88c9a9.

📒 Files selected for processing (54)
  • docs/contributing/architecture/data-storage.md
  • docs/contributing/decisions/0002-data-placement.md
  • packages/worker/src/account/data-targets.ts
  • packages/worker/src/account/export.node.test.ts
  • packages/worker/src/account/export.ts
  • packages/worker/src/account/mailbox-r2-references.node.test.ts
  • packages/worker/src/account/mailbox-r2-references.ts
  • packages/worker/src/account/r2-export.ts
  • packages/worker/src/account/r2-inventory.ts
  • packages/worker/src/account/user-owned-surfaces.node.test.ts
  • packages/worker/src/account/user-owned-surfaces.ts
  • packages/worker/src/admin/mailbox-maintenance.node.test.ts
  • packages/worker/src/admin/mailbox-maintenance.ts
  • packages/worker/src/admin/user-usage-data.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/account-email-data.node.test.ts
  • packages/worker/src/app/account-email-data.ts
  • packages/worker/src/app/account-usage-data.node.test.ts
  • packages/worker/src/app/handlers/account-email.node.test.ts
  • packages/worker/src/email/inbound-effects.ts
  • packages/worker/src/email/inbound-entitlements.workers.test.ts
  • packages/worker/src/email/inbound-mailbox-mirror.workers.test.ts
  • packages/worker/src/email/inbound.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/email/mailbox-delivery-events.ts
  • packages/worker/src/email/mailbox-do.ts
  • packages/worker/src/email/mailbox-do.workers.test.ts
  • packages/worker/src/email/mailbox-inbound-authority-guard.workers.test.ts
  • packages/worker/src/email/mailbox-inbound-ledger.ts
  • packages/worker/src/email/mailbox-inbound-ledger.workers.test.ts
  • packages/worker/src/email/mailbox-internal-read.node.test.ts
  • packages/worker/src/email/mailbox-internal-read.ts
  • packages/worker/src/email/mailbox-message-deletion-tombstones.ts
  • packages/worker/src/email/mailbox-mutations.ts
  • packages/worker/src/email/mailbox-mutations.workers.test.ts
  • packages/worker/src/email/mailbox-read-cutover.ts
  • packages/worker/src/email/mailbox-reconcile.node.test.ts
  • packages/worker/src/email/mailbox-reconcile.ts
  • packages/worker/src/email/mailbox-reconcile.workers.test.ts
  • packages/worker/src/email/mailbox-record-mappers.ts
  • packages/worker/src/email/mailbox-retention.ts
  • packages/worker/src/email/mailbox-retention.workers.test.ts
  • packages/worker/src/email/mailbox-schema.ts
  • packages/worker/src/email/mailbox-store.ts
  • packages/worker/src/email/mailbox-types.ts
  • packages/worker/src/email/package-subscriptions.node.test.ts
  • packages/worker/src/email/package-subscriptions.ts
  • packages/worker/src/email/repo.ts
  • packages/worker/src/entitlements/service.ts
  • packages/worker/src/mcp/capabilities/admin/admin-mailbox-maintenance.ts
  • packages/worker/src/mcp/capabilities/email/email-usage-get.ts
  • packages/worker/src/mcp/capabilities/email/email-usage-get.workers.test.ts
  • packages/worker/src/test-support/mailbox-reconcile.ts

Comment thread packages/worker/src/account/mailbox-r2-references.ts
Comment thread packages/worker/src/email/mailbox-do.ts
cursoragent and others added 2 commits August 2, 2026 21:25
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit 5c3fbc7 into main Aug 2, 2026
10 checks passed
@kentcdodds
kentcdodds deleted the cursor/mailbox-step3-internal-readers branch August 2, 2026 21:35
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.

2 participants