惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

M
MIT News - Artificial intelligence
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Last Week in AI
Last Week in AI
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
The Cloudflare Blog
IT之家
IT之家
雷峰网
雷峰网
小众软件
小众软件
博客园 - 叶小钗
博客园 - 聂微东
爱范儿
爱范儿
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
V
Visual Studio Blog
博客园 - 【当耐特】
V
V2EX
博客园_首页
T
Tailwind CSS Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Give a LangChain Agent a Real Inbox
Qasim Muhammad · 2026-06-16 · via DEV Community

Qasim Muhammad

An engineer on a small team wires up a LangChain agent over a weekend: it reads the inbox, summarizes threads, drafts replies. The demo kills. Then Monday's planning meeting asks the question that kills the demo back: whose inbox does this run on in production? Nobody wants the bot reading their mail, the shared support@ account is on someone's personal OAuth grant, and legal has opinions about both.

The clean answer is to give the agent its own mailbox. Nylas Agent Accounts — currently in beta — are hosted email-and-calendar identities you create with a single API call. No OAuth flow, no refresh token, no human's inbox anywhere in the loop. The quickstart gets you a live address in under 5 minutes:

import os, requests

BASE = "https://api.us.nylas.com"
HEADERS = {
    "Authorization": f"Bearer {os.environ['NYLAS_API_KEY']}",
    "Content-Type": "application/json",
}

resp = requests.post(
    f"{BASE}/v3/connect/custom",
    headers=HEADERS,
    json={"provider": "nylas",
          "settings": {"email": "assistant@your-application.nylas.email"}},
)
GRANT_ID = resp.json()["data"]["id"]

That GRANT_ID is the agent's identity. Everything below builds on it.

Mailbox operations as LangChain tools

The mailbox surface maps naturally onto tools. Three cover most agent behavior — read the inbox, read one message in full, send:

from langchain_core.tools import tool

@tool
def list_messages(limit: int = 10) -> str:
    """List the newest messages in the agent's own inbox. Returns JSON
    with subject, from, snippet, and message IDs."""
    r = requests.get(
        f"{BASE}/v3/grants/{GRANT_ID}/messages",
        headers=HEADERS, params={"limit": limit}, timeout=30,
    )
    return r.text if r.ok else f"Error: {r.text}"

@tool
def read_message(message_id: str) -> str:
    """Fetch a single message, including its full body."""
    r = requests.get(
        f"{BASE}/v3/grants/{GRANT_ID}/messages/{message_id}",
        headers=HEADERS, timeout=30,
    )
    return r.text if r.ok else f"Error: {r.text}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email from the agent's own address. Confirm recipient,
    subject, and body with the user before calling."""
    r = requests.post(
        f"{BASE}/v3/grants/{GRANT_ID}/messages/send",
        headers=HEADERS, timeout=30,
        json={"subject": subject, "body": body, "to": [{"email": to}]},
    )
    return "Email sent." if r.ok else f"Error: {r.text}"

Two design choices here come straight from the LLM-agent tooling recipe in the cookbook. First, the default limit of 10: a hundred messages of JSON will flood your context window, so cap aggressively in the schema and let the agent ask for more. Second, error strings go back to the model rather than raising — LLMs are surprisingly good at deciding what to do with "Error: ..." output if you let it through ("Looks like that message ID doesn't exist — let me list the inbox again").

The account has a calendar too — every Agent Account ships with a primary one — so a fourth tool makes the agent schedule-aware:

@tool
def list_events() -> str:
    """List events on the agent's own calendar. Returns JSON with
    titles, times, and participants."""
    r = requests.get(
        f"{BASE}/v3/grants/{GRANT_ID}/events",
        headers=HEADERS, params={"calendar_id": "primary"}, timeout=30,
    )
    return r.text if r.ok else f"Error: {r.text}"

The same pattern extends to POST /v3/grants/{grant_id}/events for creating meetings and the send-rsvp endpoint for answering invitations — all on the identity the agent already owns.

Wiring them into an agent

From here it's standard LangChain. Bind the tools to a model and let the loop run:

from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic

agent = create_react_agent(
    ChatAnthropic(model="claude-sonnet-4-5"),
    tools=[list_messages, read_message, send_email, list_events],
)

result = agent.invoke({
    "messages": [("user", "Did anyone reply about the contract? "
                          "If so, summarize and draft a thank-you.")]
})

The agent may chain several tool calls — list, then read the relevant message, then send — before producing its final answer. That's the same multi-step pattern as any tool loop; what's different is that send_email fires from an address the agent owns, and replies come back to that same address, so the conversation accumulates in the agent's inbox rather than evaporating.

From chat loop to autonomous correspondent

The version above still waits for a human prompt. The upgrade path is event-driven: register a message.created webhook and invoke the agent whenever mail arrives. Registration is one call, straight from the quickstart:

requests.post(
    f"{BASE}/v3/webhooks",
    headers=HEADERS,
    json={
        "trigger_types": ["message.created"],
        "callback_url": "https://yourapp.example.com/webhooks/nylas",
    },
)

Then the webhook handler becomes the agent's trigger. The payload carries the message's grant_id, subject, sender, and snippet under data.object, which is exactly enough to compose a prompt:

from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/nylas")
def on_mail():
    payload = request.get_json()
    if payload.get("type") == "message.created":
        msg = payload["data"]["object"]
        agent.invoke({"messages": [(
            "user",
            f"New email arrived (id: {msg['id']}) from "
            f"{msg['from'][0]['email']}: {msg['subject']}. "
            "Read it and respond appropriately.",
        )]})
    return "", 200

Inbound message in, tool calls in the middle, reasoned reply out — a genuinely autonomous correspondent. And because the reply goes out from the agent's own address, the human's answer comes back to the same mailbox and fires the same webhook: the loop sustains itself.

The CLI shortcut, if you'd rather not write tools

The cookbook recipe takes an even lazier route worth knowing about: shell out to the Nylas CLI instead of calling REST. Subprocess wrappers around nylas email list --json and nylas email send --yes give you 16 tools across six providers in under 50 lines of Python — versus roughly 300 lines of boilerplate for a hand-rolled Gmail OAuth integration alone. Two flags carry the load: --yes, because without it send commands wait on a confirmation prompt an agent loop will never answer, and --json, because structured output is what the model can actually parse. One caveat for multi-tenant setups: the CLI operates on whichever grant is active in nylas auth list, so run a per-tenant CLI process or pass --api-key explicitly. Same tool-calling pattern, different transport.

Guardrails before you let it loose

A tool description saying "confirm with the user before sending" is a suggestion, not a control. The platform-level controls live in policies and rules: daily send quotas, outbound rules that block sends to wrong domains before they ever reach a provider, inbound rules that reject junk at SMTP so prompt-injection-bearing spam never enters the model's context. Belt, meet suspenders.

Start small: provision a trial-domain account, paste the three tools above into your existing LangChain project, and email your agent something. What's the first message you'd want software answering on its own?