Skip to content

Frameworks and agents · Published 2026-09-29 · Updated 2026-09-30 · AgentiSend

Send email from LangChain

Give a LangChain agent one tool that posts to POST /emails with its own budgeted key, and stop when the reply is agent_budget_exceeded.

To send email from LangChain, give the agent one @tool whose function posts to POST /emails with the agent's own key. The key is sending_access and carries a budget. The file below is that tool and a loop of two invoke calls. It is run by the test suite against a local AgentiSend server: the first call sends, and the second message is refused with agent_budget_exceeded. The run does not call a hosted model.

Install

pip install langchain-core

The agentisend package is standard library only. It is not on the Python package index yet: until it is, install it from the SDK directory of a checkout, or copy its agentisend folder beside this file.

The tool and the loop

"""LangChain — one tool that posts to POST /emails, and a scripted agent loop.

The loop is the two calls a model would make. The first sends. The second is
a different message, so a key whose budget is one comes back as
agent_budget_exceeded. No model is called.

The `agentisend` package is not on the Python package index. The suite puts
its directory on PYTHONPATH. Install LangChain with `pip install langchain-core`.

AGENTISEND_API_KEY is the agent's key, with sending_access and a budget.
MAIL_FROM is an address on a domain you have verified.
AGENTISEND_BASE_URL is optional.
"""

from __future__ import annotations

import json
import os

from langchain_core.tools import tool

from agentisend import AgentiSend, AgentiSendError, idempotency_key

agentisend = AgentiSend()
TO = "langchain-py@example.com"
UPDATE = {
    "to": TO,
    "subject": "Ticket 4182 — we are looking into it",
    "text": "Someone from support will reply within the hour.",
    "purpose": "ticket-4182-update",
}
CLOSED = {
    "to": TO,
    "subject": "Ticket 4182 — closed",
    "text": "The ticket is closed.",
    "purpose": "ticket-4182-closed",
}


@tool
def send_email(to: str, subject: str, text: str, purpose: str) -> dict:
    """Send one email to a person who asked for it.

    Returns the message id, or a refusal with a code and a fix. When the fix
    says a person decides, stop and report it: calling again will not change
    the answer.
    """
    try:
        sent = agentisend.send_email(
            {"from": os.environ["MAIL_FROM"], "to": to, "subject": subject, "text": text},
            idempotency=idempotency_key(purpose, to),
        )
    except AgentiSendError as err:
        return {"sent": False, "code": err.code, "fix": err.fix}
    return {"sent": True, "id": sent["id"]}


def run() -> None:
    """Two tool calls, then stop when the second is refused."""
    for call in (UPDATE, CLOSED):
        result = send_email.invoke(call)
        print(json.dumps(result))
        if result.get("sent") is False:
            return


if __name__ == "__main__":
    run()
examples/langchain-python/tool.py, run by the test suite against a local AgentiSend server

agent_budget_exceeded means the period's ceiling is spent. The fix says a person raises it, in the console, so the agent stops instead of calling again.

The key

POST /api-keys with sending_access mints the key. PATCH /limits/keys/{id} sets budget_per_period and period. The same calls from Python are executed in Send email from Python.

Next