Skip to content

Add package email event subscriptions - #290

Merged
kentcdodds merged 8 commits into
mainfrom
cursor/package-email-event-subscriptions-06d9
Apr 30, 2026
Merged

kentcdodds merged 8 commits into
mainfrom
cursor/package-email-event-subscriptions-06d9

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Apr 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • rebase the package email subscription work onto the unified inbound email receipt model from Simplify inbound email receipt model #295
  • dispatch package-owned subscriptions after inbound email is stored and audited, using the unified stored receipt topic email.message.received
  • bundle and publish subscription handlers through the existing package runtime artifact pipeline, with metadata-first event payloads and runtime email lookup helpers for message bodies and attachments
  • keep email automation as a normal package subscription pattern rather than introducing a separate Kody-owned email handler primitive
  • fix email attachment reconstruction for attachments without filenames by normalizing PostalMime undefined values against stored null metadata, with focused regression coverage
  • preserve existing package export invocation idempotency hashes so pre-subscription export retries still replay correctly after the new subscription work lands
  • forward emailTools through the module-syntax runModuleWithRegistry path so runtime email helpers behave consistently across execute entry styles
  • harden inbound subscription dispatch so missing ExecutionContext does not let post-storage package-dispatch failures bubble back into the email ingestion path, and allow zero-byte attachments through email_attachment_get
  • add focused tests for manifest declarations, published bundling, stored-email dispatch, message/attachment lookup, attachment capability behavior, export idempotency compatibility, and module-runtime email helper forwarding

Validation

  • npx vitest run --project node-unit --project workers-unit packages/worker/src/package-registry/manifest.node.test.ts packages/worker/src/package-runtime/published-bundle-artifacts.node.test.ts packages/worker/src/email/inbound.workers.test.ts packages/worker/src/mcp/capabilities/email/email-domain.node.test.ts packages/worker/src/mcp/run-codemode-registry.node.test.ts packages/worker/src/mcp/capabilities/email/email-attachment-get.node.test.ts packages/worker/src/package-invocations/service.node.test.ts
  • npx vitest run --project workers-unit packages/worker/src/email/inbound.workers.test.ts && npx vitest run --project node-unit packages/worker/src/mcp/capabilities/email/email-attachment-get.node.test.ts
  • npx vitest run --project node-unit packages/worker/src/package-invocations/service.node.test.ts
  • npx vitest run --project node-unit packages/worker/src/mcp/run-codemode-registry.node.test.ts
  • npm run typecheck
  • npm run build
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added email attachment retrieval (on-demand decoded payloads) and codemode/runtime helpers (email.getMessage/email.getAttachment).
    • Enabled package subscriptions for email events (email.message.received) with asynchronous, idempotent dispatch to installed packages.
    • Metadata-first inbound handling: handlers receive message IDs and must fetch full message/attachments.
  • Documentation

    • Updated email primitives docs to document the new attachment capability and revised safety/processing model.
  • Tests

    • Added coverage for attachment retrieval, subscription dispatch, and codemode helper injection.

@coderabbitai

coderabbitai Bot commented Apr 30, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01cef4fe-d242-4ea4-bc17-47d35bb7b1fd

📥 Commits

Reviewing files that changed from the base of the PR and between 93dde78 and 1c30293.

📒 Files selected for processing (3)
  • docs/use/email-primitives.md
  • packages/worker/src/email/inbound.ts
  • packages/worker/src/mcp/capabilities/email/email-attachment-get.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/use/email-primitives.md
  • packages/worker/src/mcp/capabilities/email/email-attachment-get.ts

📝 Walkthrough

Walkthrough

Inbound email storage is now authoritative and triggers package subscription dispatches (email.message.received). Packages receive metadata-only payloads and must fetch message bodies or attachment bytes on-demand via new MCP capabilities (email_message_get, email_attachment_get). Legacy authored-package email config schemas were removed in favor of subscriptions.

Changes

Cohort / File(s) Summary
Documentation
docs/use/email-primitives.md
Updated safety model to describe subscription-driven, metadata-first flow and added email_attachment_get as a capability.
Inbound Email Handler
packages/worker/src/email/inbound.ts, packages/worker/src/email/inbound.workers.test.ts
Handler signature extended to require BUNDLE_ARTIFACTS_KV and APP_BASE_URL; stores inbound messages, records delivery event, and dispatches subscription events (uses ctx.waitUntil when available). Tests cover attachment reconstruction and subscription invocation.
Email Repository
packages/worker/src/email/repo.ts
Added getEmailMessageWithAttachmentsById and getEmailAttachmentById to return attachment metadata and decoded/base64 content (parses raw MIME when available).
Email Subscription Dispatch
packages/worker/src/email/package-subscriptions.ts
New dispatcher dispatchInboundEmailSubscriptionEvents builds typed event envelopes, loads saved packages/manifests, matches email.message.received subscriptions, and invokes package subscriptions with idempotency keys.
MCP Email Attachment Capability
packages/worker/src/mcp/capabilities/email/email-attachment-get.ts, packages/worker/src/mcp/capabilities/email/email-attachment-get.node.test.ts
Added read-only email_attachment_get capability returning metadata plus data_base64; handler enforces ownership and not-found error handling; tests validate success and missing cases.
MCP Email Domain
packages/worker/src/mcp/capabilities/email/domain.ts, packages/worker/src/mcp/capabilities/email/email-domain.node.test.ts
Registered new attachment-get capability in the email domain; updated test to expect it in the exported capabilities.
Codemode Registry & Runtime
packages/worker/src/mcp/run-codemode-registry.ts, packages/worker/src/mcp/run-codemode-registry.node.test.ts
Added emailTools option and injected email_message_get/email_attachment_get helpers and an email prelude into the codemode execution context; added skipCapabilityRegistry flag; tests validate provider wiring.
Package Invocation Service
packages/worker/src/package-invocations/service.ts, packages/worker/src/package-invocations/service.node.test.ts
Refactored to a shared saved-package module executor supporting both exports and subscriptions; added invokePackageSubscription; integrated email tools into runtime invocation context; added idempotency-replay test for legacy request_hash.
Package Manifest & Schema
packages/worker/src/package-registry/types.ts, packages/worker/src/package-registry/manifest.node.test.ts
Removed authored-package email schemas and related exported types; added test validating kody.subscriptions projection for email topics.
Package Runtime Artifacts
packages/worker/src/package-runtime/subscription-artifacts.ts, packages/worker/src/package-runtime/published-bundle-artifacts.ts, packages/worker/src/package-runtime/published-bundle-artifacts.node.test.ts
Added subscription topic normalization and subscription: artifact naming; artifact generation now builds/persists module bundles for each subscription handler; tests verify bundle generation.
Package Runtime Typecheck
packages/worker/src/repo/checks.ts
Typecheck targets now include subscription handlers (added listPackageSubscriptions handling).

Sequence Diagram

sequenceDiagram
    actor MailSender
    participant Inbound as Inbound Handler
    participant EmailDB as Email DB
    participant Dispatcher as Subscription Dispatcher
    participant PackageDB as Package DB
    participant Runtime as Package Runtime
    participant Repo as Repo Functions

    MailSender->>Inbound: forward message
    Inbound->>EmailDB: store message + attachments
    Inbound->>Dispatcher: trigger dispatch (waitUntil)
    Dispatcher->>PackageDB: load saved packages & manifests
    Dispatcher->>PackageDB: find subscriptions for email.message.received
    Dispatcher->>Runtime: invokePackageSubscription(params: message metadata)
    Runtime->>Repo: getEmailMessageWithAttachmentsById(messageId)
    Repo->>EmailDB: query message + attachment records
    Runtime->>Repo: getEmailAttachmentById(attachmentId)
    Repo->>EmailDB: fetch rawMime & parse part -> return data_base64
    Runtime-->>Dispatcher: subscription result
    Dispatcher-->>Inbound: dispatch complete
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Poem

🐰 I hopped through bytes and MIME so neat,
Stored the mail and sounded the beat,
A ping to packages, metadata-first tune,
Fetch the body later — hop, fetch, and swoon,
Subscriptions dance beneath the moon. 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add package email event subscriptions' accurately summarizes the main objective of the pull request, which focuses on implementing email event subscriptions for packages.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/package-email-event-subscriptions-06d9

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@kentcdodds
kentcdodds marked this pull request as ready for review April 30, 2026 16:36
@github-actions

github-actions Bot commented Apr 30, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-290.kentcdodds.workers.dev

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

Mocks:

Comment thread packages/worker/src/mcp/capabilities/email/email-attachment-get.ts Outdated
Comment thread packages/worker/src/email/package-subscriptions.ts Outdated
Comment thread packages/worker/src/package-invocations/service.ts Outdated
Comment thread packages/worker/src/email/inbound.workers.test.ts Outdated
Comment thread packages/worker/src/package-invocations/service.ts
cursoragent and others added 4 commits April 30, 2026 17:48
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>
@cursor
cursor Bot force-pushed the cursor/package-email-event-subscriptions-06d9 branch from 4bef71f to ad154cc Compare April 30, 2026 17:52
Comment thread packages/worker/src/email/repo.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/src/package-invocations/service.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 37f40bc. Configure here.

Comment thread packages/worker/src/mcp/run-codemode-registry.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@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/mcp/run-codemode-registry.ts (1)

431-438: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Forward emailTools when routing module-syntax code.

Line 431 delegates to runModuleWithRegistry, but emailTools is not passed, so module-syntax executions invoked via runCodemodeWithRegistry lose email helper support.

Proposed fix
  if (hasTopLevelModuleSyntax(moduleSource)) {
    return runModuleWithRegistry(env, callerContext, moduleSource, params, {
      executorExports: options?.executorExports,
      additionalTools: options?.additionalTools,
      storageTools: options?.storageTools,
      serviceTools: options?.serviceTools,
      packageContext: options?.packageContext,
+     emailTools: options?.emailTools,
      executorTimeoutMs: options?.executorTimeoutMs,
    })
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/run-codemode-registry.ts` around lines 431 - 438, The
call in runCodemodeWithRegistry that delegates to runModuleWithRegistry is
missing forwarding of emailTools, so module-syntax executions lose email helper
support; update the options passed to runModuleWithRegistry to include
emailTools: options?.emailTools (alongside executorExports, additionalTools,
storageTools, serviceTools, packageContext, executorTimeoutMs) so
runModuleWithRegistry receives and can use the email helper utilities.
🧹 Nitpick comments (1)
packages/worker/src/mcp/run-codemode-registry.node.test.ts (1)

1092-1256: ⚡ Quick win

Add a regression test for runCodemodeWithRegistry(..., { emailTools }) with module syntax.

Current additions validate runBundledModuleWithRegistry and runModuleWithRegistry, but not the module-routing branch inside runCodemodeWithRegistry. A focused test here would catch dropped option forwarding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/run-codemode-registry.node.test.ts` around lines 1092
- 1256, Add a regression test that mirrors the "runModuleWithRegistry injects
email helpers for module syntax" case but calls runCodemodeWithRegistry (with
module-style code string) to verify emailTools are forwarded for the
module-routing branch; spy/mock getCapabilityRegistryForContext and
createExecuteExecutor (as in the other tests), mock buildKodyModuleBundle if
needed, capture providerFns from the executor, invoke runCodemodeWithRegistry
with an emailTools object (getMessage/getAttachment), assert the result and that
providerFns.email_message_get and providerFns.email_attachment_get resolve to
the expected values, and restore mocks (use the same symbols:
runCodemodeWithRegistry, buildKodyModuleBundle, getCapabilityRegistryForContext,
createExecuteExecutor, providerFns, email_message_get, email_attachment_get).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/use/email-primitives.md`:
- Around line 31-34: Add the missing capability name email_attachment_get to the
primary capabilities list so it appears alongside the other email primitives
(e.g., email_message_get, import { email } from 'kody:runtime') in the top
capabilities section; update the capabilities bullet list to include
"email_attachment_get" for discoverability and ensure wording/formatting matches
the existing entries (same casing and style).

In `@packages/worker/src/email/inbound.ts`:
- Around line 169-173: The handler currently awaits dispatchPromise when _ctx is
falsy, which can let subscription-dispatch failures bubble up and cause the
inbound ingestion to fail despite storage succeeding; change the logic around
_ctx and dispatchPromise so dispatchPromise is not awaited directly when _ctx is
absent—instead attach a non-throwing error handler (e.g.,
dispatchPromise.catch(err => processLogger.error('subscription dispatch failed',
err))) or otherwise swallow/log errors so the handler returns success; update
the block that references _ctx and dispatchPromise in inbound.ts to call
_ctx.waitUntil(dispatchPromise) when _ctx exists and to attach a .catch(...) (or
equivalent safe handling) to dispatchPromise when _ctx is missing so failures do
not propagate.

In `@packages/worker/src/email/inbound.workers.test.ts`:
- Around line 462-465: The test is building the manifest snapshot KV key with
publishedCommit but the key builder buildPublishedSourceManifestSnapshotKvKey
expects the versionHash; update the call in the test to pass the versionHash
value (e.g., versionHash: 'commit-1' or the variable holding the version hash)
along with sourceId so the generated key matches the real snapshot key and the
subscription dispatch assertion becomes reliable.

In `@packages/worker/src/mcp/capabilities/email/email-attachment-get.ts`:
- Around line 42-45: The guard in email-attachment-get that currently uses a
truthiness check on attachment.contentBase64 will reject legitimate zero-byte
attachments; update the check in the code path (the block that throws the Error
using args.attachment_id) to explicitly test for null/undefined (e.g.,
attachment.contentBase64 == null or attachment.contentBase64 === undefined)
instead of using !attachment.contentBase64 so empty string payloads are allowed
to pass through and return successfully.

---

Outside diff comments:
In `@packages/worker/src/mcp/run-codemode-registry.ts`:
- Around line 431-438: The call in runCodemodeWithRegistry that delegates to
runModuleWithRegistry is missing forwarding of emailTools, so module-syntax
executions lose email helper support; update the options passed to
runModuleWithRegistry to include emailTools: options?.emailTools (alongside
executorExports, additionalTools, storageTools, serviceTools, packageContext,
executorTimeoutMs) so runModuleWithRegistry receives and can use the email
helper utilities.

---

Nitpick comments:
In `@packages/worker/src/mcp/run-codemode-registry.node.test.ts`:
- Around line 1092-1256: Add a regression test that mirrors the
"runModuleWithRegistry injects email helpers for module syntax" case but calls
runCodemodeWithRegistry (with module-style code string) to verify emailTools are
forwarded for the module-routing branch; spy/mock
getCapabilityRegistryForContext and createExecuteExecutor (as in the other
tests), mock buildKodyModuleBundle if needed, capture providerFns from the
executor, invoke runCodemodeWithRegistry with an emailTools object
(getMessage/getAttachment), assert the result and that
providerFns.email_message_get and providerFns.email_attachment_get resolve to
the expected values, and restore mocks (use the same symbols:
runCodemodeWithRegistry, buildKodyModuleBundle, getCapabilityRegistryForContext,
createExecuteExecutor, providerFns, email_message_get, email_attachment_get).
🪄 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: d49d6eda-2d2d-47da-ac7c-f5dd0b4ec2df

📥 Commits

Reviewing files that changed from the base of the PR and between d8b34a7 and 93dde78.

📒 Files selected for processing (19)
  • docs/use/email-primitives.md
  • packages/worker/src/email/inbound.ts
  • packages/worker/src/email/inbound.workers.test.ts
  • packages/worker/src/email/package-subscriptions.ts
  • packages/worker/src/email/repo.ts
  • packages/worker/src/mcp/capabilities/email/domain.ts
  • packages/worker/src/mcp/capabilities/email/email-attachment-get.node.test.ts
  • packages/worker/src/mcp/capabilities/email/email-attachment-get.ts
  • packages/worker/src/mcp/capabilities/email/email-domain.node.test.ts
  • packages/worker/src/mcp/run-codemode-registry.node.test.ts
  • packages/worker/src/mcp/run-codemode-registry.ts
  • packages/worker/src/package-invocations/service.node.test.ts
  • packages/worker/src/package-invocations/service.ts
  • packages/worker/src/package-registry/manifest.node.test.ts
  • packages/worker/src/package-registry/types.ts
  • packages/worker/src/package-runtime/published-bundle-artifacts.node.test.ts
  • packages/worker/src/package-runtime/published-bundle-artifacts.ts
  • packages/worker/src/package-runtime/subscription-artifacts.ts
  • packages/worker/src/repo/checks.ts
💤 Files with no reviewable changes (1)
  • packages/worker/src/package-registry/types.ts

Comment thread docs/use/email-primitives.md
Comment thread packages/worker/src/email/inbound.ts
Comment on lines +462 to +465
buildPublishedSourceManifestSnapshotKvKey({
sourceId,
publishedCommit: 'commit-1',
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use versionHash when building the manifest snapshot KV key.

Line 464 uses publishedCommit, but the snapshot key builder is keyed by versionHash. This can seed the wrong key and make the subscription dispatch assertion less reliable.

Suggested fix
  bundleKv.set(
  	buildPublishedSourceManifestSnapshotKvKey({
  		sourceId,
- 		publishedCommit: 'commit-1',
+ 		versionHash: 'commit-1',
  	}),
  	JSON.stringify({
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
buildPublishedSourceManifestSnapshotKvKey({
sourceId,
publishedCommit: 'commit-1',
}),
buildPublishedSourceManifestSnapshotKvKey({
sourceId,
versionHash: 'commit-1',
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/email/inbound.workers.test.ts` around lines 462 - 465,
The test is building the manifest snapshot KV key with publishedCommit but the
key builder buildPublishedSourceManifestSnapshotKvKey expects the versionHash;
update the call in the test to pass the versionHash value (e.g., versionHash:
'commit-1' or the variable holding the version hash) along with sourceId so the
generated key matches the real snapshot key and the subscription dispatch
assertion becomes reliable.

Comment thread packages/worker/src/mcp/capabilities/email/email-attachment-get.ts Outdated
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit 61967ab into main Apr 30, 2026
9 checks passed
@kentcdodds
kentcdodds deleted the cursor/package-email-event-subscriptions-06d9 branch April 30, 2026 20:09
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