Skip to content

Add attachments to emailSend, matching emailReply - #2296

Merged
kody-bot merged 1 commit into
mainfrom
cursor/email-send-attachments-deaf
Sep 14, 2026
Merged

kody-bot merged 1 commit into
mainfrom
cursor/email-send-attachments-deaf

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Intent

Let emailSend attach files the same way emailReply already does, without opening a new send channel or quota.

Why

Kent approved notify-self sends with optional attachments. Destinations still gate recipients. Size stays on the existing email_message_bytes cap. No new entitlement dimension.

Summary

  • emailSend accepts optional attachments (filename, content_type, content_base64, max 10)
  • Shared Zod schema and mapper with emailReply — no forked validation
  • One MIME message, same attachments for every allowed to
  • Still one email_sends_per_day charge per successful send
  • Oversize or unsafe filenames fail before the provider call
  • Docs in docs/use/email-primitives.md mirror the reply attachment note

Testing

  • email-send.node.test.ts: no attachments unchanged, attachments forwarded, invalid shape / empty list / 11 files rejected
  • outbound.workers.test.ts: attachments under cap, oversize email_message_bytes, path-traversal filename, multi-to one MIME + one daily charge
  • Pre-push: test:node (3226) and test:workers (364) passed

System changes

Medium risk: this extends the emailSend contract. Notify-self and destination gating are unchanged. Leave merge for Cole/Kent after CI.

System recap — extends email (medium risk)

Mode: recap · Base: main @ 29ceea9d · Head: 63e008aa

Classification: extends — emailSend gains the same optional attachment contract as emailReply. Destination gating, one daily send charge, and notify-self are unchanged.

Primitives touched

Primitive Group Impact
email assistant extends — emailSend accepts attachments, shared schema/mapper with emailReply, filename guard

Change flow

emailSend validates attachments with the shared reply schema, then sendOutboundEmail builds one MIME message for every allowed to and fails oversize before the provider.

sequenceDiagram
	actor Caller
	participant email as email
	participant entitlements as entitlements
	Caller->>email: emailSend subject, text or html, optional attachments
	email->>email: shared schema, max 10, map to outbound attachments
	email->>email: destinations gate to only, same files for every recipient
	alt attachments present
		email->>entitlements: email_message_bytes on bodies plus decoded files
	end
	email->>entitlements: one email_sends_per_day charge
	email->>email: one MIME send via binding or REST
Loading

Before / after

// before emailSend
{ to?, subject, text?, html?, reply_to? }

// after emailSend (same as emailReply attachments)
{ to?, subject, text?, html?, reply_to?, attachments?: Array<{
  filename, content_type, content_base64
}> }

Invariants

Per-user isolation and notify-self still hold: attachments never expand who can receive, and unverified or unknown to still fails the whole send.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Email sending now supports up to 10 file attachments using filename, content type, and base64 content.
    • Attachments are delivered to all eligible verified destinations in a single message.
    • Email replies use the same attachment handling and validation.
  • Bug Fixes
    • Unsafe attachment filenames, including path traversal patterns and null bytes, are now rejected.
  • Documentation
    • Added guidance on attachment limits, storage, retrieval, and message-size requirements.

Share the capability attachment schema and mapper with emailReply, keep
one MIME message (and one daily send charge) for every allowed to, and
fail oversize or unsafe filenames before the provider call.

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

coderabbitai Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 63a9b447-0dde-457b-aa51-7ae2f9ddd8d8

📥 Commits

Reviewing files that changed from the base of the PR and between 29ceea9 and 63e008a.

📒 Files selected for processing (7)
  • docs/use/email-primitives.md
  • packages/worker/src/email/outbound.ts
  • packages/worker/src/email/outbound.workers.test.ts
  • packages/worker/src/mcp/capabilities/email/email-reply.ts
  • packages/worker/src/mcp/capabilities/email/email-send.node.test.ts
  • packages/worker/src/mcp/capabilities/email/email-send.ts
  • packages/worker/src/mcp/capabilities/email/shared.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds attachment support to emailSend, reuses shared attachment handling in emailReply, rejects unsafe outbound filenames, tests multi-recipient delivery and input validation, and documents attachment limits and storage behavior.

Changes

Email attachments

Layer / File(s) Summary
Attachment contracts and capability wiring
packages/worker/src/mcp/capabilities/email/shared.ts, packages/worker/src/mcp/capabilities/email/email-send.ts, packages/worker/src/mcp/capabilities/email/email-reply.ts, packages/worker/src/mcp/capabilities/email/email-send.node.test.ts, docs/use/email-primitives.md
Shared validation requires attachment filenames, content types, base64 content, and the configured maximum count. emailSend accepts and converts attachments. emailReply uses the same schema and converter. Tests cover valid and invalid input. Documentation describes delivery, storage, and message-size constraints.
Outbound filename safety and delivery validation
packages/worker/src/email/outbound.ts, packages/worker/src/email/outbound.workers.test.ts
Outbound preparation rejects . and .., path separators, null bytes, and path-traversal filenames. Worker tests cover one MIME message sent to multiple verified destinations and unsafe filename rejection.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant MCPCaller
  participant emailSendCapability
  participant sendOutboundEmail
  participant EMAILBinding
  MCPCaller->>emailSendCapability: Send attachment input
  emailSendCapability->>emailSendCapability: Validate and convert attachments
  emailSendCapability->>sendOutboundEmail: Pass outbound attachments
  sendOutboundEmail->>EMAILBinding: Send one MIME message to allowed destinations
  EMAILBinding-->>MCPCaller: Return sent message summary
Loading

Possibly related PRs

  • kentcdodds/kody#701: Adds the shared outbound attachment model and handling reused by this PR.

Merge Risk: ⚪ Minimal · up to 63e00

Attachment support includes shared validation, outbound safety checks, delivery coverage, and documentation without an identified merge-blocking issue.

🚥 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%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 6 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely states the main change: adding attachments to emailSend to match emailReply.
Description check ✅ Passed The description includes all required sections: Intent, Why, Summary, Testing, and System changes. It provides specific implementation details, preserved behavior, tests, and risk context.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 6 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ 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/email-send-attachments-deaf

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

@kentcdodds
kentcdodds marked this pull request as ready for review September 14, 2026 03:05
@kentcdodds

Copy link
Copy Markdown
Owner Author

bugbot run

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 63e008a. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

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

Worker: kody-pr-2296
Platform worker: kody-pr-2296-platform (https://kody-pr-2296-platform.kody-a99.workers.dev)
Runtime worker: kody-pr-2296-runtime (https://kody-pr-2296-runtime.kody-a99.workers.dev)
D1: kody-pr-2296-db
KV: kody-pr-2296-oauth-kv

Mocks:

@kody-bot
kody-bot merged commit 052ab1c into main Sep 14, 2026
20 checks passed
@kody-bot
kody-bot deleted the cursor/email-send-attachments-deaf branch September 14, 2026 03:15
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