LangChain Email Verification Tool for AI Agents

By , founder of InboxPolicy · Updated July 20, 2026

This guide uses a small LangChain tool wrapper around POST /v1/decide, authenticated with x-api-key, or an MCP connection exposing decide_send. The API returns five actions, confidence, status, signals, cache state, and cost for pre-send branching.

Wrap the REST endpoint

The integration below depends only on the documented REST contract. This avoids coupling the workflow to a vendor-specific LangChain package.

The worked path is to wrap the real REST route. The API base URL is https://api.inboxpolicy.com, the single-address send-decision endpoint is POST /v1/decide, and authentication is an x-api-key header (a present invalid key returns 401; an absent key receives an HTTP 402 x402 challenge). The MCP server is a separate transport that maps decide_send to the same REST /v1/decide endpoint. For the framework-neutral primer on REST, MCP, x402, and agent branching, see email verification for AI agents.

The five actions a LangChain tool should branch on

POST /v1/decide returns exactly five actions — call them actions or send-decision verdicts, not a new enum:

The full response includes request_id, email, action, reason, confidence, status, retry_at, signals (catch_all, disposable, free, role_account), from_cache, and cost. Note that retry_at is included by the route even though it is not listed in the OpenAPI response schema. Branch your agent on the action field; do not fabricate a status-to-action mapping.

A practical distinction: /v1/decide can return avoid for malformed input without billing, while /v1/verify instead raises 422 for a syntax failure. Keep that explicit. Verification eligible cache hits for the same normalized address and strictness within 72 hours when force_refresh is false are free and expose from_cache; force_refresh bypasses the lookup.

Worked example: wrap POST /v1/decide as a LangChain StructuredTool

This uses the API-key path, which is the fully source-grounded path for the example. The request fields match the route's verify schema (email, strictness, force_refresh), and the response fields are assembled by the route. Reuse the same idempotency-key when the same workflow retries — one concurrent execution is stored/replayed, while conflicting or in-flight reuse can return 409.

import json
import os
from typing import Literal
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

# LangChain API: verify this wrapper syntax against your installed version.
from langchain_core.tools import StructuredTool

Strictness = Literal["aggressive", "default", "strict"]


def inboxpolicy_decide(
    email: str,
    idempotency_key: str,
    strictness: Strictness = "default",
    force_refresh: bool = False,
) -> dict:
    """Return InboxPolicy's send-decision response for one email address.

    InboxPolicy contract: POST https://api.inboxpolicy.com/v1/decide
    with JSON {email, strictness?, force_refresh?}.
    """
    payload = {
        "email": email,
        "strictness": strictness,
        "force_refresh": force_refresh,
    }
    request = Request(
        "https://api.inboxpolicy.com/v1/decide",
        method="POST",
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "content-type": "application/json",
            "x-api-key": os.environ["INBOXPOLICY_API_KEY"],
            # Reuse this exact key when the same workflow retries.
            "idempotency-key": idempotency_key,
        },
    )

    try:
        with urlopen(request, timeout=15) as response:
            return json.loads(response.read().decode("utf-8"))
    except (HTTPError, URLError) as exc:
        detail = exc.read().decode("utf-8", errors="replace") if isinstance(exc, HTTPError) else str(exc)
        raise RuntimeError(f"InboxPolicy request failed: {detail}") from exc


# Verify StructuredTool syntax against the installed LangChain version.
inboxpolicy_tool = StructuredTool.from_function(
    func=inboxpolicy_decide,
    name="inboxpolicy_decide_send",
    description=(
        "Before sending an email, call InboxPolicy. Branch on the returned action: "
        "send, send_with_caution, review, retry_later, or avoid. "
        "Do not send as a side effect of this tool."
    ),
)

Why this example is honest: it uses only InboxPolicy fields and headers present in the route and docs. It does not claim a vendor SDK, a LangChain adapter, an outbound email side effect, or Python x402 support. A caller should supply a stable workflow-specific idempotency_key; it should not generate a new key for every retry. The StructuredTool.from_function syntax and the langchain_core.tools import are not pinned or verified against a specific LangChain version; verify them against the version you run — verify them against the version you run; the InboxPolicy contract is the HTTP call, not this wrapper API.

Authenticating the call: x-api-key

The API-key path authenticates with an x-api-key header supplied through INBOXPOLICY_API_KEY. The worked wrapper assumes that key is already provisioned; purchasing and provisioning are outside this integration example. Wallet usage pricing for the separate x402 REST path is documented on the docs page.

This example does not implement the keyless x402 flow. The keyless path starts with a 402 challenge, then uses a PAYMENT-SIGNATURE header; the server settles before returning a billable product response. Keyless x402 requires an EVM wallet funded with USDC on Base and does not require an account or API key. The repository's documented x402 quickstart is JavaScript for any Python helper the repo does not ship — do not imply the wrapper above implements x402.

Keyless x402 and idempotency are not interchangeable. The non-x402 path enters an idempotency wrapper, while the x402 path settles without that wrapper. For paid agent loops, explicitly design payment and retry handling, and avoid blindly signing a second payment after an indeterminate settlement.

Alternative transport: connect through MCP

If your LangChain deployment already has a verified MCP client, you can expose InboxPolicy's remote MCP server as a tool provider instead of writing a REST wrapper. The remote endpoint is https://mcp.inboxpolicy.com with an x-api-key header; the remote handler rejects a missing or invalid key before the MCP handshake. InboxPolicy itself does not provide the LangChain client or adapter in this repository — verify the client library and Streamable HTTP compatibility separately.

The MCP server exposes seven tools — decide_send, verify_email, estimate_credits, create_verify_batch, get_batch_status, get_batch_items, and get_usage — and decide_send maps to POST /v1/decide. For the MCP-specific guide (configuration and the full tool list), see MCP email verification for agents. Batch tools are source-verified but intentionally out of scope for this single-email example.

What this example deliberately does not do

Related guides

For the framework-neutral primer on REST, MCP, x402, and agent branching, see email verification for AI agents. For the MCP-specific alternative, especially if you do not need a REST wrapper, see MCP email verification for agents. For canonical endpoint, auth, quickstart, idempotency, x402, and MCP reference, see the docs.

Frequently asked questions

How do I integrate InboxPolicy email verification into a LangChain AI agent?

Wrap POST https://api.inboxpolicy.com/v1/decide as a LangChain tool, authenticate with the x-api-key header, and branch your agent on the returned action. Use a small tool wrapper around the REST endpoint, or an MCP connection exposing decide_send.

Is there an official InboxPolicy LangChain adapter or SDK?

This guide uses the supported HTTP and MCP contracts: wrap the REST endpoint or connect through MCP.

Which InboxPolicy endpoint should a LangChain tool call before sending an email?

POST /v1/decide is the single-address send-decision endpoint. The request body requires email and accepts strictness (aggressive, default, strict) and force_refresh. It returns one action plus reason, confidence, status, retry_at, signals, from_cache, and cost — everything a LangChain agent needs to branch before sending.

What does POST /v1/decide return for a LangChain agent to branch on?

Five actions: send, send_with_caution, review, retry_later, and avoid. The response also includes request_id, email, action, reason, confidence, status, retry_at, signals (catch_all, disposable, free, role_account), from_cache, and cost. Branch on the action field; do not fabricate a status-to-action mapping.

Can I connect LangChain to InboxPolicy over MCP instead of REST?

InboxPolicy runs a hosted MCP server at https://mcp.inboxpolicy.com that exposes decide_send, verify_email, estimate_credits, create_verify_batch, get_batch_status, get_batch_items, and get_usage, authenticated with x-api-key. decide_send maps to the same REST POST /v1/decide route. Whether a specific LangChain MCP client can consume this Streamable HTTP server is client-dependent — verify the client library and transport compatibility separately.

How do I authenticate the InboxPolicy call from a LangChain tool?

Send the x-api-key header. A missing or invalid key returns 401. The worked example in this guide uses the API-key path because it is the fully source-grounded path. The keyless x402 path (HTTP 402 challenge, then a PAYMENT-SIGNATURE header settled in USDC on Base) is documented separately and is not implemented by the Python wrapper shown here.

Does the LangChain example support keyless x402 payments?

No. The example is API-key-only and does not implement the keyless x402 flow. A keyless agent must handle the 402 challenge, sign a PAYMENT-SIGNATURE, and retry; the repository's documented quickstart is JavaScript. The payment client is a separate integration concern.

Should my LangChain tool send the email as a side effect of the verification call?

No. /v1/decide returns a send decision, it does not send anything. The tool description in the worked example explicitly says "do not send as a side effect of this tool." Your agent reads the action and then decides separately whether to send, hold for review, queue a retry, or avoid.

What is the difference between /v1/decide and /v1/verify for a LangChain agent?

/v1/decide returns a single action plus reason, confidence, status, retry_at, four normalized signal booleans, cache state, and cost. /v1/verify returns the full verification detail (status, deliverable, reachable, catch_all, disposable, free, role_account, confidence, mx_hosts, evidence, and more) and raises 422 for a syntax failure, while /v1/decide can return avoid for malformed input without billing. Use /v1/decide for send branching; use /v1/verify when you need the full verification detail (deliverable, reachable, MX, and evidence fields).

Get started, pay per call, no signup →