Skip to content

Add get_email_metadata — headers-only Email/get for least-privilege flows#68

Merged
MadLlama25 merged 1 commit into
MadLlama25:mainfrom
Richyread:pr/get-email-metadata
Jul 6, 2026
Merged

Add get_email_metadata — headers-only Email/get for least-privilege flows#68
MadLlama25 merged 1 commit into
MadLlama25:mainfrom
Richyread:pr/get-email-metadata

Conversation

@Richyread

Copy link
Copy Markdown
Contributor

The existing get_email tool returns the full Email object — textBody, htmlBody, bodyValues, attachments — which is great for "show me this email" workflows but a hammer-too-large for skills that just need to classify or route based on headers.

This shows up most acutely in privacy-sensitive flows: a skill working through a customer-mail mailbox might be specifically forbidden from ingesting message bodies (GDPR / least-privilege rules), but still needs the basic envelope data — sender, subject, date, mailbox membership — to do its job. With only get_email available, the skill has to either (a) take the full payload and remember to discard the body in its prompt logic, or (b) work around using advanced_search filters. Both approaches put the privacy guarantee in the wrong place: in the caller's discipline rather than the tool surface.

get_email_metadata is a tool-level guarantee. It calls Email/get with a strict properties allowlist:

id, threadId, mailboxIds, keywords, receivedAt, sentAt, subject,
from, to, cc, bcc, replyTo, messageId, inReplyTo, references,
size, hasAttachment

There's no bodyProperties, no fetchTextBodyValues, no fetchHTMLBodyValues — the JMAP server simply never sends body content in this tool's path. hasAttachment is a Boolean (yes/no); filenames and content types deliberately aren't surfaced because filenames sometimes carry PII (Smith Invoice June 2026.pdf-style leakage). If a caller does need attachment metadata or to download attachments, get_email_attachments and download_attachment cover that explicitly.

Useful side benefit: this tool returns mailboxIds, which the existing get_email omits. That makes get_email_metadata the right call for "verify this email landed in the expected folder after I moved it" checks — handy in tests and skill self-assessment paths.

Smoke-tested: response carries the 17 expected keys and none of textBody / htmlBody / bodyValues / body / preview / attachments. The existing get_email on the same email returns attachments, bodyValues, htmlBody, textBody — confirming the contrast. All 176 existing unit tests still pass.


This PR was drafted with Claude Opus 4.7; I reviewed and tested each commit before opening.

…lows

The existing get_email tool returns the full Email object — textBody,
htmlBody, bodyValues, attachments — which is great for "show me this
email" workflows but a hammer-too-large for skills that just need to
classify or route based on headers.

This shows up most acutely in privacy-sensitive flows: a skill working
through a customer-mail mailbox might be specifically forbidden from
ingesting message bodies (GDPR / least-privilege rules), but still
needs the basic envelope data — sender, subject, date, mailbox
membership — to do its job. With only get_email available, the skill
has to either (a) take the full payload and remember to discard the
body in its prompt logic, or (b) work around using advanced_search
filters. Both approaches put the privacy guarantee in the wrong place:
in the caller's discipline rather than the tool surface.

get_email_metadata is a tool-level guarantee. It calls Email/get with
a strict properties allowlist:

  id, threadId, mailboxIds, keywords, receivedAt, sentAt, subject,
  from, to, cc, bcc, replyTo, messageId, inReplyTo, references,
  size, hasAttachment

There's no bodyProperties, no fetchTextBodyValues, no
fetchHTMLBodyValues — the JMAP server simply never sends body content
in this tool's path. hasAttachment is a Boolean (yes/no); filenames
and content types deliberately aren't surfaced because filenames
sometimes carry PII ("Smith Invoice June 2026.pdf"-style leakage). If
a caller does need attachment metadata or to download attachments,
get_email_attachments and download_attachment cover that explicitly.

Useful side benefit: this tool returns mailboxIds, which the existing
get_email omits. That makes get_email_metadata the right call for
"verify this email landed in the expected folder after I moved it"
checks — handy in tests and skill self-assessment paths.

Smoke-tested: response carries the 17 expected keys and none of
textBody / htmlBody / bodyValues / body / preview / attachments. The
existing get_email on the same email returns attachments, bodyValues,
htmlBody, textBody — confirming the contrast. All 176 existing unit
tests still pass.
MadLlama25 pushed a commit that referenced this pull request Jul 6, 2026
…gs for advanced_search, search_emails, list_emails, and get_thread

PR #68 added get_email_metadata as a header-only sibling of get_email,
so callers in privacy-sensitive flows (customer-mail least-privilege
scans, GDPR-bound workflows, anything that mustn't ingest message
bodies) can pick up envelope data without any body content riding
along. The tool surface itself does the enforcing — the JMAP server
is simply never asked for body fields, so it never sends any. That's
a much stronger guarantee than asking the caller to remember to
discard body content after the fact.

The other search and thread tools weren't part of that pass and still
include 'preview' in their JMAP property request. 'preview' is a
~200-character body excerpt, so every result of every search/list/
thread call quietly surfaces body content to the caller. That gap
showed up while wiring a customer-mail scan against the new tooling:
the skill had to add a "discard preview before reasoning" guard at
the top of every code path, which works but puts the privacy
guarantee back into the caller's discipline rather than the tool.

This PR closes that gap by adding four parallel tools alongside the
existing ones (additive — nothing renamed, no breaking changes):

  advanced_search_metadata  — same filters as advanced_search
  search_emails_metadata    — same query as search_emails
  list_emails_metadata      — same listing as list_emails
  get_thread_metadata       — same thread enumeration as get_thread

Each one mirrors its sibling's input schema and behaviour exactly,
including get_thread's accept-an-email-id-and-resolve-the-thread
flexibility. The only difference is the JMAP property allowlist,
which is reduced to a fixed set of header-only fields:

  id, threadId, subject, from, to, [cc,] replyTo, receivedAt,
  hasAttachment, keywords

(cc is included where the existing siblings included it.) No
preview, no textBody, no htmlBody, no bodyValues. Anyone using the
existing tools keeps working unchanged; anyone wanting the
privacy-safe path reaches for the *_metadata variant by name.

Tool descriptions: each new tool's description names its sibling
explicitly, lists the property allowlist, and says when to pick it.
The goal is that someone scanning the tool list sees not just
"another search" but "the privacy-safe version of advanced_search"
without having to read the source.

Tests: five new tests in jmap-client-extra.test.ts pin the
no-body-content invariant for each *_metadata method, plus one that
confirms advancedSearchMetadata preserves the AND-conjunction filter
logic when both isUnread and isPinned are set (the most intricate
piece of advancedSearch's behaviour). All 181 tests pass — 176
pre-existing, 5 new.

Bumped to 1.10.0 across package.json, manifest.json, and the Server
constructor in src/index.ts (minor — additive feature).
@MadLlama25 MadLlama25 merged commit d1e5662 into MadLlama25:main Jul 6, 2026
@MadLlama25 MadLlama25 mentioned this pull request Jul 6, 2026
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