Skip to content

Add per-mailbox "also forward a copy to" setting - #10

Open
Gnosh wants to merge 2 commits into
hieunc229:mainfrom
Gnosh:feat/mailbox-forward-copy
Open

Add per-mailbox "also forward a copy to" setting#10
Gnosh wants to merge 2 commits into
hieunc229:mainfrom
Gnosh:feat/mailbox-forward-copy

Conversation

@Gnosh

@Gnosh Gnosh commented Aug 23, 2026

Copy link
Copy Markdown

Summary

  • Adds an optional forwardTo address per mailbox, settable at creation and via mailbox settings ("Also forward a copy to")
  • On inbound mail, worker.ts's email() handler now calls the real ForwardableEmailMessage.forward() API to send a safety-net copy to that address, alongside Mailflare's normal store/parse pipeline
  • Includes a separate fix: forces a Webpack build instead of Turbopack (Next.js 16's default), since @opennextjs/cloudflare 1.19.11 doesn't fully inline Turbopack's server chunks into the deployed Worker bundle — this surfaced as a ChunkLoadError/"handler is not a function" 500 on every request

Why this design

The codebase already had a "forward" routing action in its type system (RoutingDecision.action), but it was unreachable dead code — resolveInboundAddress() never returns it, and where it's referenced in inbound.ts it's just a console.info no-op. There was no way to keep an external copy of a mailbox's mail without abandoning Mailflare's own Cloudflare Email Routing rule entirely.

One constraint shaped the implementation: ForwardableEmailMessage.forward() is only valid for the duration of the email() handler invocation that received the message — it can't be called later from the queue consumer that does the actual store/parse work. So the forward has to happen in email() itself, right after the message is durably captured to R2 and enqueued, rather than waiting on the full async processing pipeline to succeed. I think that's actually a better property for a "safety net" copy anyway — it isn't gated on Mailflare's own processing logic being bug-free.

Test plan

  • Deployed to a live Cloudflare Workers instance, D1 migration applied
  • Set a mailbox's "Also forward a copy to" address in the Settings UI
  • Sent a real external test email to that mailbox's address
  • Confirmed the message appeared in the Mailflare inbox and a copy arrived at the forwarding address
  • npx tsc --noEmit shows no new type errors introduced by this change (two pre-existing errors in unrelated files remain, untouched by this PR)

🤖 Generated with Claude Code

Bud-bit and others added 2 commits August 23, 2026 00:53
Turbopack (Next.js 16's default bundler) produces server chunks that
@opennextjs/cloudflare 1.19.11 cannot fully inline into the deployed
worker bundle, causing ChunkLoadError/"handler is not a function" at
runtime on every request. Webpack output builds and deploys cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mailflare currently only supports storing inbound mail (the "forward"
routing action exists in the type system but resolveInboundAddress()
never returns it, and inbound.ts's handling of it is a no-op log
statement). There's no way to keep an external safety-net copy of a
mailbox's mail without moving off Mailflare's own routing rule.

This adds an optional forwardTo address per mailbox:
- New nullable mailboxes.forward_to column (migration 0011)
- Settable via mailbox creation (POST /api/mailboxes) and update
  (PATCH /api/mailboxes/[id]), validated as an email address
- Surfaced in the mailbox settings UI ("Also forward a copy to")
- Wired into worker.ts's email() handler via a new
  resolveForwardTarget() helper, calling the real
  ForwardableEmailMessage.forward() API

Note on placement: the forward() call has to happen inside email()
itself, not the queue consumer that does the actual store/parse work
- Cloudflare only allows forward() for the duration of the handler
invocation that received the message, so it can't be deferred to
async queue processing. Forwarding therefore happens right after the
message is durably captured to R2 + enqueued, rather than waiting on
the full processing pipeline to succeed - which is arguably a safer
property for a "safety net" copy (the external forward isn't gated on
Mailflare's own processing succeeding).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.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.

2 participants