Skip to content

Expose unauthenticated mail discovery and recorded authentication results - #86

Merged
shalim786 merged 3 commits into
mainfrom
codex/support-unauthenticated-visibility
Oct 26, 2026
Merged

shalim786 merged 3 commits into
mainfrom
codex/support-unauthenticated-visibility

Conversation

@shalim786

@shalim786 shalim786 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Messages labeled unauthenticated (or blocked) can be fetched by ID but cannot be discovered through the toolkit's message or thread listing tools, because those tools omit the API's explicit opt-ins. Message results also discard the API's recorded authentication verdicts.

Add optional includeUnauthenticated and includeBlocked to list_messages and list_threads (and include_unauthenticated to the Python list_threads). Return the public authenticationResults fields on get_message, on messages inside get_thread, and on list_messages/search_messages items.

The pinned SDK (0.5.46) passes the field through in snake_case, so the toolkit projects it to camelCase, keeping only string values of the keys MessageAuthenticationResultsSchema declares, and drops the raw copy. An SDK that types the field (camelCase) goes through the same projection. Delete the snake_case branch once the toolkit requires that SDK; the Fern definition already declares the field. Verdicts are strings, so a value the API adds later can't fail a read. The MCP output allowlist still excludes raw headers, tenant identifiers and diagnostic fields.

The descriptions carry the API's caveats: compare dkimDomain with the From domain before trusting a DKIM pass, and don't reject mail on a DMARC fail alone. They also define the unauthenticated label as the API does (no mechanism passed, or DMARC failed under a none policy). All four restricted-label opt-ins, in both toolkits, now say that labels alone doesn't include that mail and that each needs its label_*_read permission, without which the API leaves the mail out silently. The search tools say they always exclude blocked and unauthenticated mail. Defaults, labels, authentication checks and backend permission enforcement stay unchanged.

Breaking for non-MCP adapters (version 0.17.0): the base toolkit and the ai-sdk, LangChain and clawdbot adapters used to return the SDK's raw authentication_results key from get_message/get_thread. They now return only the projected authenticationResults, so code reading result.authentication_results gets undefined after upgrading.

Validation: all 799 Node tests and 62 Python tests pass. Tests cover:

  • real SDK query serialization
  • API-shaped authentication responses
  • an unknown verdict
  • the camelCase SDK shape
  • the non-MCP adapter
  • projected list/search items
  • every schema key mapping from its wire name
  • contract tests in both languages that fail if the SDK takes an include* opt-in the list tools don't expose

pnpm lint and pnpm build (including declaration generation) pass. Repository-wide tsc --noEmit still reports existing example-module, contract-test cast and tsup-config import errors outside this change.

Follow-ups:

  • The non-MCP adapters and the Python toolkit still return undeclared fields such as headers and organization_id. Stripping every adapter's result to its output schema in one shared place is a separate change.
  • Publish the toolkit and update the hosted MCP dependency/manifest before telling customers this is available. This PR does not deploy the hosted connector.

🤖 Generated with Claude Code

shalim786 and others added 2 commits October 25, 2026 17:22
- Verdicts are strings, not an enum, and the projection keeps only string
  values, so a verdict the API adds later cannot fail get_message/get_thread.
- Drop the raw authentication_results copy: only the MCP adapter strips
  undeclared fields, so the other adapters returned both shapes.
- Carry the API's caveats into the field descriptions (compare dkimDomain
  with From, do not reject on a DMARC fail alone) and mark when to delete
  the projection.
- Add includeBlocked to list_messages/list_threads, say both opt-ins need
  the matching label_*_read permission, and say search always excludes
  blocked and unauthenticated mail.
- Add include_unauthenticated to the Python list_threads.
- Test the camelCase branch, an unknown verdict, the non-MCP adapter, and
  that the tools expose every include* opt-in the SDK reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@duharry0915 duharry0915 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the claims against agentmail-api and the Fern definition. The blocked meaning, the label_*_read permission names, the silent exclusion without the permission, and search always excluding restricted mail all hold. Main gap: list/search message items carry authentication_results too and aren't covered (inline on getMessage). The rest are inline.

One note outside the diff: Python get_thread (python/src/agentmail_toolkit/functions.py) returns the SDK model as-is (extra='allow'), so each message's raw snake_case authentication_results passes through unprojected, including undocumented keys. With Node now projecting, the two toolkits disagree on the field's shape and on what gets filtered.

Comment thread node/src/functions.ts
Comment thread node/src/schemas.ts Outdated
Comment thread node/src/schemas.ts Outdated
Comment thread node/src/functions.ts
Comment thread node/src/functions.ts Outdated
Comment thread node/src/functions.ts Outdated
Comment thread node/src/functions.ts Outdated
Comment thread node/src/functions.ts Outdated
Comment thread python/src/agentmail_toolkit/schemas.py Outdated
Comment thread python/tests/test_functions.py
- list_messages and search_messages items carry authentication_results
  too: project them and declare authenticationResults on MessageItemSchema.
- Project the camelCase field an SDK that types it returns the same way,
  emit only keys with string values, and derive the keys from
  MessageAuthenticationResultsSchema so a new key needs one edit.
- Guard a thread or page that comes back without messages.
- Describe the unauthenticated label as the API defines it, and say on all
  four restricted-label opt-ins (Node and Python) that labels alone does
  not include them and that each needs its label_*_read permission.
- The Python opt-in test now compares against the SDK's list signature.
- Bump to 0.17.0: the non-MCP adapters no longer return the raw
  authentication_results key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shalim786

Copy link
Copy Markdown
Contributor Author

On the Python get_thread note: the snake_case name is Python's shape everywhere (the SDK models are snake_case: inbox_id, thread_id), so authentication_results matches the rest of that toolkit, not a separate disagreement. The unfiltered undeclared keys are real. They're the same gap as the Node non-MCP adapters: the Python toolkit has no output allowlist, so headers and other undeclared fields pass through too. I'm leaving that for the same follow-up that strips results to their output schema in one shared place, rather than adding a one-field filter to Python here.

@shalim786
shalim786 marked this pull request as ready for review October 26, 2026 18:31
@shalim786
shalim786 requested a review from duharry0915 October 26, 2026 18:31

@duharry0915 duharry0915 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified bf3b430 against the earlier comments: list/search items are projected, the typed-SDK path is filtered, descriptions match the API, and build, lint and all tests pass. Worth an issue for the deferred follow-up (strip every adapter's results, and Python's, to the output schema in one shared place) so it doesn't get lost.

@shalim786
shalim786 merged commit 5b67350 into main Oct 26, 2026
2 checks passed
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