Skip to content

connector-imap + minimal service-mailbox: shared-mailbox (forward/BCC) ingest into sys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871

Open

Description

@objectstack-fleet

Filing gate: ④ — a per-layer child of the coordination node #8998. Part of #8998.

Blocked-by: #22870

Provenance: same as #22870.

This card carries phase 1's ingest half. It waits on #22870, which publishes the object (sys_mail_message) and the association seam this card writes to and calls.

Scope

Taken from the design record. ⛔ Nothing here re-decides it.

  • packages/connectors/connector-imap: protocol and auth only, following the existing connector convention (connector-rest, connector-openapi, connector-mcp, connector-slack). The account + credential abstraction is shaped for the per-user mailbox now. The shared mailbox is its first implementor (decision 4), so per-user sync later extends it rather than rewriting it.

  • packages/services/service-mailbox, minimal:

    • folder cursor and UID state;
    • incremental fetch, dedupe and threading;
    • attachment extraction;
    • bounce and auto-reply suppression.

    It writes sys_mail_message, calls the seam at ingest and stamps the association. Unmatched messages go to the pool, and the explicit re-match action retro-matches them (decisions 2 and 3).

  • Phase-1 privacy semantic: "forwarding is consent" (decision 5).

  • ⛔ Out of scope, as chained follow-ups filed once phase 1 proves out:

    • per-user IMAP sync;
    • CalDAV and calendar;
    • write-back;
    • the enterprise admin plane (tenant-wide delegation, bulk provisioning, sync health).

Why one card covers two packages: phase 1's connector has exactly one consumer (this service), and neither can be proven end to end alone. The lane may split it at claim time through pm:retriage.

Acceptance

An end-to-end test against an in-process IMAP server double:

  • A forwarded message ingested from the shared mailbox lands as one sys_mail_message row, with the association stamped by an app-supplied resolver.
  • A second fetch dedupes, and the row count stays 1.
  • An unmatched message lands in the pool, and the re-match action associates it once a matching record exists.
  • An auto-reply is suppressed.

Each clause has a negative control that fails when the behaviour is removed.

Who acts

The domain:services seat, size L, once #22870 lands. Clause-②: yes, because it adds two published packages.

Dedup

Same queries and readings as #22870. No mail-ingest, IMAP or calendar card exists besides #8998.

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling on #22870 recorded (comment 6116489638, batch #319 item 2, the maintainer's word verbatim 「同意」): Q1 A (a field-level mailAssociation: true on email fields), Q3 C staged, Q4 A (manage_mailboxes plus the mailbox-owner limb); Q2 → C3 and Q5 → A stand. Director seat, summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T02:09Z. ⛔ Not a claim. This card stays Blocked-by: #22870.

    What Q3 C staged means for this card's dispatch, for the domain:services lane:

    • In this card (phase 1, B): the seam's input is the outer headers plus the headers of a top-level message/rfc822 part (RFC 2046 §5.2.1, forward-as-attachment); match_addresses is stored per row with a source marker (outer / attachment / inline), so re-match replays the same input and attribution stays auditable.
    • A chained follow-up card, filed by this lane when this card is dispatched, Blocked-by: this card: inline-forward parsing for the "Forwarded message" blocks of Gmail, Outlook and Apple Mail only, one pin per client format; an unrecognised block is not guessed, the message pools; only a forward sent by an organisation user is parsed (D5's "forwarding is consent" is the door). The mainstream readings behind the staging are on spec + service-mailbox: sys_mail_message (the inbound mirror object) and the association seam — phase 1 of #8998 inbound mail, spec seat #22870 (6116341307).
    • Re-match stays additive (Q5 A); an explicit unlink is a later card.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions