Skip to content

feat(email): expose sendEmail threading as a public option - #678

Merged
Brooooooklyn merged 1 commit into
mainfrom
feat/send-email-threading
Nov 3, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
feat/send-email-threading

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Fixes #665

What

sendEmail gets a typed threading option, so a reply sent later (from a queue job, after the inbound handler returned) stays in the same thread.

await sendEmail({
  from: 'support@acme.dev',
  to: ticket.from,
  subject: `Re: ${ticket.subject}`,
  text: 'Fixed.',
  threading: { inReplyTo: ticket.messageId, references: `${ticket.references} ${ticket.messageId}` },
});
sendEmail({ threading })          replyEmail(message)
        │                                 │
        └──────► normalizeThreading ◄─────┘   (one channel, same rules)
                        │
          threading.ts: threadingId / threadingReferences
                        │
                    buildMime ──► In-Reply-To / References
headers: { 'In-Reply-To' | 'References' } ──► dropped (still reserved)

Answers to the issue's questions

# Question Answer
1 Naming Keep the threading object. It already exists in normalizeEmail.
2 Validation Same rules as replyEmail: an id with a control character or too long for one line is left out; references keeps the newest ids within 8 KiB and falls back to inReplyTo; references defaults to inReplyTo. The email still sends. Only a non-string value returns MIME_ERROR.
3 Proxy No change. The proxy forwards the MIME bytes as they are, and already counts these as built-in headers.
4 Spoofing Does not apply. From is still pinned, and the caller only threads its own outbound message. headers still can't set the two names, so there is one checked path.

Also fixed

The line-length check (fitsHeaderLine) now follows foldHeader's quote rules. Before, a stray " in an id passed the check but could not be folded, so the send failed with MIME_ERROR. This also covers reply subjects, which share the check.

Changes

  • runtime/email/threading.ts (new): shared id checks moved out of mime.ts / inbound.ts.
  • mime.ts: normalizeThreading checks the option; inReplyTo is trimmed of SP/HTAB only, matching Headers.
  • types.ts: SendEmailOptions.threading with JSDoc.
  • Docs: options row and a "Reply later" section in docs/guide/email/sending.md; a link from receiving.md.
  • Tests: email-mime.test.ts, email-send.test.ts.

Verification

  • vp test run on the email unit tests: 432 passed.
  • vp run test --base origin/main: 12,093 passed.
  • Typecheck, lint, fmt clean.

🤖 Generated with Claude Code

Add `threading: { inReplyTo?, references? }` to SendEmailOptions so a reply
sent later (for example from a queue job) continues the thread.

The ids are cleaned with the same rules replyEmail uses: a bad id is left
out instead of failing the send, References keeps the newest ids within
8 KiB and falls back to In-Reply-To. Only non-string values return
MIME_ERROR. `headers` still drops In-Reply-To and References.

The shared checks move to threading.ts. The line-length check now follows
foldHeader's quote rules, so a stray `"` in an id no longer makes the
send fail with MIME_ERROR.

Fixes #665

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T15:21:11.099766Z 962bc55 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-send-email-threading-void-cloud.voidzero-docs.workers.dev (commit 962bc55)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://867a61f8-void-cloud.voidzero-docs.workers.dev 962bc55 2026-11-02T15:25:39.171Z Visit the dashboard ↗

@Brooooooklyn
Brooooooklyn merged commit 04d2da6 into main Nov 3, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/send-email-threading branch November 3, 2026 02:47
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.

feat(email): expose sendEmail threading (In-Reply-To/References) as a public option

1 participant