Skip to content

feat: add @sendmux/ai-sdk and langchain-sendmux framework wrapper packages - #115

Merged
roshanjonah merged 6 commits into
mainfrom
agent/framework-wrappers
Jul 22, 2026
Merged

feat: add @sendmux/ai-sdk and langchain-sendmux framework wrapper packages#115
roshanjonah merged 6 commits into
mainfrom
agent/framework-wrappers

Conversation

@roshanjonah

@roshanjonah roshanjonah commented Jul 22, 2026

Copy link
Copy Markdown
Member

What

Two hand-written first-party wrapper packages so AI-agent frameworks get real Sendmux tools (no more third-party-MCP-only path):

  • @sendmux/ai-sdk (packages/ts/ai-sdk) — sendmux({ apiKey }) returns a Vercel AI SDK ToolSet (send_email, list_messages, reply) over @sendmux/sending + @sendmux/mailbox. ai + zod are peer deps.
  • langchain-sendmux (packages/python/langchain) — SendmuxToolkit(api_key=...).get_tools() returns LangChain tools over sendmux-sending + sendmux-mailbox.

Both back the integration tabs on the marketing site's quick-start bar.

Why hand-written (not generated)

They wrap existing generated surface clients; they have no OpenAPI surface of their own. Per the codegen/layout/staleness/surface gates, they are invisible (all driven by hardcoded lists or operation IDs), so no gate logic changed.

Wiring

  • Registered in .release-please-manifest.json + release-please-config.json (ts-ai-sdk / python-langchain, initial 0.1.0).
  • Added to scripts/publish-npm-ts.mjs, scripts/build-python-dists.mjs, scripts/prepare-pypi-publish.mjs.
  • Added to the publish-npm + publish-pypi job if: gates in .github/workflows/release-please.yml.
  • Added langchain_core to the root pyproject.toml mypy ignore-imports override (the only build-gate that type-checks the new Python package).
  • pnpm-lock.yaml regenerated for ai + zod.

Verified locally

  • @sendmux/ai-sdk: tsc build clean (needs exactOptionalPropertyTypes:false, scoped + commented, same as the generated surface packages — the ai SDK's ToolSet union is not exact-optional compatible).
  • langchain-sendmux: compileall + mypy --strict clean under the repo config.
  • verify:layout, verify:ts-public-api, verify:tree-shaking pass; all JSON valid.
  • API shapes verified against the real generated signatures (sendingSendEmail body, mailbox_send_message kwargs, hey-api option shape), not guessed.

Not published

Release stays via release-please and is user-gated. This PR only adds the packages + wiring.

Known gap

No unit tests yet, and neither package is in the central build-gate test suite (pytest packages/python/tests / no TS tests for ai-sdk). Follow-up: smoke tests asserting the 3 tools construct.

https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC

Greptile Summary

This PR adds first-party Sendmux wrappers for Vercel AI SDK and LangChain. The main changes are:

  • Three email and mailbox tools for each framework.
  • Release and publishing support for both packages.
  • Wrapper smoke tests and type-check configuration.
  • Regenerated clients with typed 413 responses.

Confidence Score: 5/5

This looks safe to merge.

  • The Python dependency floor now matches the available mailbox API.
  • Supplied idempotency keys reach the generated clients in both wrappers.
  • No blocking issue distinct from the existing review comments was found.

Important Files Changed

Filename Overview
packages/ts/ai-sdk/src/index.ts Adds Vercel AI SDK tools for sending, listing, and replying to messages.
packages/python/langchain/langchain_sendmux/toolkit.py Adds matching LangChain tools with alias-safe request construction.
packages/python/langchain/pyproject.toml Defines the new package and uses a compatible mailbox dependency floor.
packages/python/tests/test_langchain.py Adds construction and argument-schema tests for the LangChain tools.
scripts/test-ts-ai-sdk.mjs Adds a structural smoke test for the TypeScript tools.

Reviews (6): Last reviewed commit: "fix(mypy): do not follow into langchain_..." | Re-trigger Greptile

…kages

First-party wrappers so AI-agent frameworks get real Sendmux tools:

- @sendmux/ai-sdk (packages/ts/ai-sdk): sendmux({ apiKey }) returns a Vercel AI
  SDK ToolSet (send_email, list_messages, reply) over @sendmux/sending +
  @sendmux/mailbox. ai + zod as peers.
- langchain-sendmux (packages/python/langchain): SendmuxToolkit(api_key=).get_tools()
  returns LangChain tools over sendmux-sending + sendmux-mailbox.

Wiring: registered in .release-please-manifest.json + release-please-config.json,
npm/pypi publish scripts, and the publish-npm/publish-pypi workflow gates; added
langchain_core to the root mypy ignore-imports override.

Verified locally: tsc build, mypy --strict clean, verify:layout,
verify:ts-public-api, verify:tree-shaking, JSON valid, pnpm lockfile updated.
Not published - release is via release-please and remains user-gated.

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
@roshanjonah roshanjonah added the major Logic or behaviour change requiring AI review label Jul 22, 2026
Comment thread packages/python/langchain/pyproject.toml Outdated
Comment thread packages/ts/ai-sdk/src/index.ts
Comment thread packages/python/langchain/langchain_sendmux/toolkit.py Outdated
Comment thread packages/ts/ai-sdk/src/index.ts Outdated
Comment thread packages/python/langchain/langchain_sendmux/toolkit.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d994ca39e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

dependencies = [
"langchain-core>=1.0.0,<2",
"sendmux-sending>=1.3.0,<2.0.0",
"sendmux-mailbox>=1.3.0,<2.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Lower the mailbox dependency floor

When langchain-sendmux is released from this commit, this requirement cannot be satisfied by the repo's Python packages: sendmux-mailbox is still version 1.2.0 in both packages/python/mailbox/pyproject.toml and the release manifest, and the PyPI publish helper skips package versions that are already published, so a langchain-only release would upload a package that pip cannot install until some separate future mailbox 1.3.0 release exists. Either lower this floor to the current 1.2.0 API or release/bump the Python mailbox package alongside it.

Useful? React with 👍 / 👎.

…lbox floor

- langchain pyproject: sendmux-mailbox floor 1.3.0 -> 1.2.0 (published version;
  1.3.0 is unresolvable). [greptile P1]
- send_email + reply (both packages): accept an optional idempotency key and pass
  it through, so retried sends do not duplicate. [greptile P2]
- htmlFromText / _html_from_text: convert newlines to <br> so plain-text bodies
  keep line breaks in the generated HTML. [greptile P2]

Re-verified: tsc build, mypy --strict, compileall.

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
Comment on lines +72 to +77
.optional()
.describe(
"Optional key that makes a retried send idempotent for 24 hours",
),
}),
execute: async ({ to, subject, text, html, from, idempotencyKey }) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Retries Remain Non-Idempotent

The model can omit the optional idempotencyKey, so the normal tool call still sends without an Idempotency-Key header. If Sendmux accepts the request but the response is lost, an agent retry can send the same email again. Generate and retain a key for each logical tool invocation instead of relying on model input. The same issue affects reply.

Fix in Claude Code

Comment on lines +66 to +73
def send_email(
to: str,
subject: str,
text: str,
html: Optional[str] = None,
var_from: Optional[str] = None,
idempotency_key: Optional[str] = None,
) -> Any:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Retries Remain Non-Idempotent

Because idempotency_key is optional and defaults to None, LangChain does not require the model to provide it. If Sendmux accepts a request but the tool loses the response, an agent retry can issue another unkeyed request and deliver a duplicate email. Generate and retain a key for each logical tool invocation instead of relying on model input. The same issue affects reply.

Fix in Claude Code

Pre-existing drift on main: the OpenAPI source documents 413 "Request body
too large" responses on many mailbox + management endpoints, but the committed
generated clients (go, php, python, ts) and the MCP embedded spec were not
regenerated. `verify:sdk-staleness` fails on this for every PR.

Regenerated via `pnpm drift:check`; additive only (adds 413 error types /
decoders / validators), no surface removed. Fixes at the source-of-truth layer
per repo policy. Unrelated to the wrapper packages in this PR; separated here
so it can be split out if preferred.

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
EmailSendRequest.var_from carries alias "from" (a Python keyword) with
validate_by_alias, so keyword construction (var_from=...) is rejected by mypy
against the real generated model and would not populate by alias at runtime.
Build EmailSendRequest and SendMailboxMessageBody from dicts via model_validate
instead, using the API alias keys.

Verified with the real sendmux-sending / sendmux-mailbox installed: mypy --strict
clean and a runtime construct smoke (var_from / to resolve correctly).

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
…he gate

- scripts/test-ts-ai-sdk.mjs: asserts sendmux() returns a ToolSet with the three
  expected tools, each with description/inputSchema/execute. Added to the root
  `build` chain as `test:ts-ai-sdk` (runs after `pnpm -r build` produces dist).
- packages/python/tests/test_langchain.py: asserts SendmuxToolkit.get_tools()
  exposes send_email/list_messages/reply with args, that idempotency_key is
  surfaced, and a regression that EmailSendRequest builds via the "from" alias
  (the var_from bug). Runs in the central `pytest packages/python/tests`.
- scripts/check-python.mjs: install packages/python/langchain editable so the
  test can import it; toolkit stays mypy-clean against the real langchain_core.

Verified locally: node test-ts-ai-sdk, pytest (4 passed), mypy --strict clean
against the real sendmux-sending / sendmux-mailbox / langchain-core.

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
check-python now installs langchain-sendmux (for the toolkit tests), which pulls
langchain-core and its transitive langsmith into the venv. `mypy packages/python`
follows imports into that tree and hits an INTERNAL ERROR in langsmith's
generated openapi client. Set follow_imports=skip for langchain_core/langchain/
langsmith so mypy treats them as Any and never opens langsmith's source. The
toolkit is still fully type-checked against the generated sendmux models.

Claude-Session: https://claude.ai/code/session_01WhnFZNB5xF8MQnnqdiYNXC
@roshanjonah
roshanjonah merged commit 1884e8d into main Jul 22, 2026
4 checks passed
@roshanjonah
roshanjonah deleted the agent/framework-wrappers branch July 22, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major Logic or behaviour change requiring AI review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant