Skip to content

Connectors emit raw mail signals instead of classifying locally - #359

Merged
KrisBraun merged 8 commits into
mainfrom
mail-signals
Aug 3, 2026
Merged

Connectors emit raw mail signals instead of classifying locally#359
KrisBraun merged 8 commits into
mainfrom
mail-signals

Conversation

@KrisBraun

Copy link
Copy Markdown
Contributor

Email connectors previously derived a facets verdict ({format, automation, reach}) from message headers and then discarded the headers themselves. This adds NewLink.signals and the @plotday/twister/signals entry point so a connector reports what it observed instead, and the platform derives classification from that.

The practical benefit: classification can be improved without redeploying every connector, and it can take into account context a connector cannot see — such as the recipient's own relationship to a sender.

What changed

New SDK surfaceMailSignals on LinkSignals, carrying the headers connectors already parse: List-Id, List-Unsubscribe, Precedence, Auto-Submitted, Return-Path, Importance, sender address and name, To and Cc counts as separate values, reply state, subject, the trusted Authentication-Results value, and the provider's own category and flag vocabulary.

Gmail, Outlook and Apple Mail now emit signals rather than classifying. Each keeps exactly one judgement: selecting which Authentication-Results header to trust, since only the connector knows its provider's authserv-id.

Provider vocabularies stay verbatim. Outlook previously mapped Focused Inbox's bucket onto another provider's category name locally; it now reports focused / other as Microsoft names them, and the platform normalises. One translation instead of one per connector.

noteKey identifies which message the signals were read from, so a multi-message conversation classifies from the same message the headers came from rather than whichever note happens to be first.

Behaviour change worth calling out

Body length is no longer reported by connectors. It had been the length of whatever the body extractor returned — which prefers HTML — so it was markup length, tag bloat included. Since the classifier compares it against thresholds that separate long-form reading material from short notifications, heavily-templated transactional mail could cross the reading threshold on tags alone. The platform now measures the extracted text itself. Expect fewer messages typed as reading and more as notification; that is the intended correction.

Compatibility

link.facets continues to work unchanged for connectors that have not migrated, and for non-email sources whose signals do not fit the mail shape. When a link carries both, signals wins.

🤖 Generated with Claude Code

https://claude.ai/code/session_013nPrTKxi7VmhzefnX5MDK1

KrisBraunand others added 8 commits August 2, 2026 18:28
Connectors can emit the raw header and metadata signals they extract
instead of a finished facets verdict, so classification can be improved
without redeploying connectors. facets continues to work unchanged;
signals wins when both are present.
The Gmail product now reports the headers and provider categories it
observed and lets the platform derive classification, so classification
can improve without redeploying the connector. To and Cc counts are
emitted separately rather than summed.
…text
MailSignals.bodyLength let each mail connector report its own plain-text
body length for classification. In practice every connector passed the
extracted body's raw string length, which is markup length whenever the
body is HTML — so a short, heavily-templated email could look long enough
to read on tag bloat alone. The platform already holds the note content
and already has an HTML-to-text fallback path, so it now derives body
length itself and connectors no longer report it. gmailSignals() drops
its bodyLength parameter accordingly.
Also removes getMessageHtml(), an export left with no callers after an
earlier change moved link/CTA extraction server-side.
A handful of comments across connectors named files that only exist in
Plot's private core repository (not readable from this public repo), or
described private implementation details more specifically than a
reader here can act on. Reworded each to describe the platform behavior
a connector author needs to know, without the internal reference.
The Outlook mail product now reports observed headers, recipient
counts and the Focused Inbox bucket, and lets the platform derive
classification. The Focused/Other bucket is emitted in the
provider's own vocabulary rather than being mapped locally.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nPrTKxi7VmhzefnX5MDK1
The Apple Mail product now reports observed headers and recipient
counts and lets the platform derive classification.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nPrTKxi7VmhzefnX5MDK1
Replaces the facets-derivation guidance with the signals contract: report
what was observed and let the platform classify, so classification logic
can improve without a connector redeploy. Points the checklist/pitfalls
sweep at the same section (no stale references found elsewhere).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nPrTKxi7VmhzefnX5MDK1
A conversation link carries one note per message, but `LinkSignals` only
described one of them — the message a connector picked as the
conversation's classification parent. The platform had no way to tell
which, so body-derived classification (message length, links in the body)
read the link's first note instead. On an incremental sync that is often a
later reply, so a thread's classification could change every time someone
replied to it.
`LinkSignals.noteKey` closes that gap: set it to the `key` of the note
built from the message the signals came from, using the same expression
the note itself was keyed with. Omit it for single-note links; the
platform still falls back to the first note. When the key matches no note
in the link, the platform classifies from `link.preview`.
The Gmail, Outlook and iCloud Mail connectors now set it — Gmail's case is
the clearest: when an attendee-response message is folded onto its calendar
event, the conversation's first message no longer has a note at all.
@KrisBraun
KrisBraun merged commit c449afb into mainAug 3, 2026
1 check passed
@KrisBraun
KrisBraun deleted the mail-signals branch August 3, 2026 02:37
Sign up for freeto 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

@KrisBraun