Skip to content

inbox: split forwarded-as-attachment emails into messages of their own; drop duplicate note copies - #13

Merged
cliftonc merged 1 commit into
mainfrom
feat/inbox-forwarded-attachments
Oct 24, 2026
Merged

cliftonc merged 1 commit into
mainfrom
feat/inbox-forwarded-attachments

Conversation

@cliftonc

Copy link
Copy Markdown
Contributor

To ship in 3.7.0.

inbox: forwarded-as-attachment email is split. Gmail's "Forward as attachment" sends one email with N message/rfc822 parts. Until now they were stored as .eml attachments, so consumers never saw them and knowledge ingestion skipped them. A part with no disposition was merged into the parent body instead.

  • inbox.parse uses postal-mime's forceRfc822Attachments, so every embedded message is a part of its own.
  • Each part becomes a child message with its own R2 prefix and its own inbox.parse. It carries the embedded From, Subject, Date and Message-ID, and the parent's SPF/DKIM/DMARC verdict, because its own signatures can't be checked once re-wrapped. forwarded_by records the forward's envelope sender.
  • The parent keeps the .eml rows, with expanded_message_id pointing at the child. Those rows are kept out of consumer attachments, knowledge and attachment_count, so nothing processes an email twice.
  • Dedupe: a unique (tenant_id, parent_message_id, source_sha256) index stops redelivery or reprocessing from creating a second child. An embedded Message-ID the tenant already has is linked instead of stored again.
  • Caps: depth 3 and 50 children per message. Past either cap the parts stay attachments, and past the children cap the message's error says how many.
  • Consumer payload (InboxParsedMessage), additive: parentMessageId, forwardedBy, forwardedMessageIds.
  • API and UI: summaries gain parentMessageId/forwardedBy/forwardedCount, and details gain parent/forwardedMessages. The page shows "Contains N forwarded emails" and "Forwarded inside …".
  • Schema: inbox_messages gains parent_message_id, forwarded_by, forward_depth, source_sha256 and forwarded_count. inbox_attachments gains expanded_message_id. All columns are nullable or defaulted, and the host generates the DDL.
  • Tests: tests/api/forwarded.test.ts covers two attached receipts becoming children with the right sender and subject, an inline rfc822 part, idempotent redelivery with consumers called once per child, Message-ID linking, the depth cap, the children cap and tenant isolation.

Duplicate note copies removed. plugins/{inbox,freeagent}/docs/plugins/<id>/upgrades/ were left behind by pnpm plugin export in 3.6.0. docs/upgrades/ is canonical, because the installer maps it to the host's docs/plugins/<id>/upgrades/. The README now says to delete the export's copy.

Run locally against kit 0.22.0: installing from this checkout, db:generate, pnpm gate and plugin check all pass. Generating a migration from an inbox 3.6.0 install gives six ADD COLUMNs, two FKs and one partial unique index.

🤖 Generated with Claude Code

Every message/rfc822 part (postal-mime forceRfc822Attachments) becomes a
child inbox message: own R2 prefix and parse job, the embedded From,
Subject, Date and Message-ID, the parent's auth verdict and forwarded_by.
The parent's .eml rows point at the child (expanded_message_id) and are
kept out of consumers and knowledge. Redelivery is idempotent through a
unique (tenant_id, parent_message_id, source_sha256) index; an embedded
Message-ID already received is linked, not stored again. Depth cap 3,
50 children per message. Consumers gain parentMessageId, forwardedBy and
forwardedMessageIds; the Inbox page links parent and children.

Also removes the duplicate docs/plugins/<id>/upgrades copies that
`plugin export` left inside inbox and freeagent in 3.6.0; docs/upgrades/
is the canonical home, and the README says to delete the copy after an
export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cliftonc
cliftonc merged commit 920089f into main Oct 24, 2026
14 checks passed
cliftonc added a commit that referenced this pull request Oct 24, 2026
inbox splits forwarded-as-attachment email: each embedded message/rfc822
part becomes a child message with its own sender, subject and parse job,
the forward's auth verdict and forwarded_by, deduplicated by Message-ID
and idempotent on redelivery (#13). Needs a host migration (six columns,
two FKs, one partial unique index). The duplicate docs/plugins/<id>/upgrades
copies in inbox and freeagent are gone. The other five plugins are version
stamps only. README install commands name the 3.7.0 tag.

Co-Authored-By: Claude Opus 5.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.

1 participant