feat: add @sendmux/ai-sdk and langchain-sendmux framework wrapper packages - #115
Conversation
…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
There was a problem hiding this comment.
💡 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", |
There was a problem hiding this comment.
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
| .optional() | ||
| .describe( | ||
| "Optional key that makes a retried send idempotent for 24 hours", | ||
| ), | ||
| }), | ||
| execute: async ({ to, subject, text, html, from, idempotencyKey }) => { |
There was a problem hiding this comment.
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.
| def send_email( | ||
| to: str, | ||
| subject: str, | ||
| text: str, | ||
| html: Optional[str] = None, | ||
| var_from: Optional[str] = None, | ||
| idempotency_key: Optional[str] = None, | ||
| ) -> Any: |
There was a problem hiding this comment.
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.
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
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 SDKToolSet(send_email,list_messages,reply) over@sendmux/sending+@sendmux/mailbox.ai+zodare peer deps.langchain-sendmux(packages/python/langchain) —SendmuxToolkit(api_key=...).get_tools()returns LangChain tools oversendmux-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
.release-please-manifest.json+release-please-config.json(ts-ai-sdk/python-langchain, initial0.1.0).scripts/publish-npm-ts.mjs,scripts/build-python-dists.mjs,scripts/prepare-pypi-publish.mjs.publish-npm+publish-pypijobif:gates in.github/workflows/release-please.yml.langchain_coreto the rootpyproject.tomlmypy ignore-imports override (the only build-gate that type-checks the new Python package).pnpm-lock.yamlregenerated forai+zod.Verified locally
@sendmux/ai-sdk:tscbuild clean (needsexactOptionalPropertyTypes:false, scoped + commented, same as the generated surface packages — theaiSDK'sToolSetunion is not exact-optional compatible).langchain-sendmux:compileall+mypy --strictclean under the repo config.verify:layout,verify:ts-public-api,verify:tree-shakingpass; all JSON valid.sendingSendEmailbody,mailbox_send_messagekwargs, 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:
Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Reviews (6): Last reviewed commit: "fix(mypy): do not follow into langchain_..." | Re-trigger Greptile