Mailflare stored one recipient per message and never used the threading
headers: send accepted a single `to`, replies carried no In-Reply-To, and
`threadId` was always the message's own Message-ID, so nothing ever grouped.
Threading
- `resolveThreadId` (src/lib/email/threading.ts) looks up the parent named
by In-Reply-To/References among stored messages in the same mailbox and
reuses its thread; otherwise the message's own Message-ID seeds a new one.
Applied to inbound mail and to .eml/.mbox/IMAP import.
- Outbound replies write In-Reply-To and References and file the sent copy
under the parent's thread; a fresh send is keyed by the Message-ID
Cloudflare returns, which is what any reply will reference.
- New `GET /api/messages/[id]/thread` returns the conversation (drafts and
trash excluded). The reader shows earlier and later messages as collapsed
cards that expand in place; list rows show a "(n)" count.
Recipients
- `to`, `cc`, `bcc` accept a header string or an array (composer, /api/send,
/api/v1/send). Full To and Cc lines are stored on inbound messages so
reply-all can address everyone; Bcc is stored on sent mail only.
- Composer gains a chip input (comma, Enter, paste, blur commit; Backspace
edits the last chip; invalid entries highlighted) with Cc/Bcc toggles.
- Reply-all appears only when it would reach someone a plain reply would
not, and drops the mailbox's own addresses.
- Reader header shows every To/Cc/Bcc recipient; Sent rows read "Name, +2".
Schema: migration 0025 adds cc_addr, bcc_addr, in_reply_to,
references_header and two indexes; bootstrap SQL and MIGRATION_NAMES
updated. mbox export now writes Cc/In-Reply-To/References.
Verified against a fresh local D1 with the seeded demo data: a reply sent
through /api/send, an .eml reply imported with In-Reply-To pointing at the
Cloudflare-assigned Message-ID, and a reply-all from the composer all landed
in one 4-message thread. tsc (11) and eslint (96) unchanged from baseline.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGeWDVSkXtVEmDAN2AtB9h