Integration Guide
LlamaIndex
Add postal mail tools to any LlamaIndex agent. Use FunctionTool to wrap REST calls for outbound mail, forwarded digital/OCR context, and account-enabled physical Open & Scan.
New mailbox.bot-issued physical receiving addresses and associated package receiving: Not live yet · Launch ETA: mid/late September. Reservations are open, but a reservation does not assign or activate an address. Assigned-PMB examples below apply only to accounts that already have receiving enabled.
Install
pip install llama-index llama-index-llms-openai requestsNo mailbox.bot SDK to install — it’s a REST API. The tools below use requests to call it directly.
Authentication
Get an API key from your dashboard after signing up. Use an agent-scoped key (sk_agent_*) for single-agent setups or a member key (sk_live_*) for multi-agent.
import os
API_KEY = os.environ["MAILBOX_BOT_API_KEY"] # sk_agent_*
BASE = "https://mailbox.bot/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
AGENT_ID = os.environ["MAILBOX_BOT_AGENT_ID"] # from dashboardTool Definitions
from llama_index.core.tools import FunctionTool
import requests
# Fetch MAILBOX.md version (required for outbound mail)
def get_md_version() -> str:
r = requests.get(f"{BASE}/agents/{AGENT_ID}/instructions", headers=HEADERS)
return str(r.json()["version"])
MD_VERSION = get_md_version()
def list_physical_inbound() -> str:
"""List letters, flats, and parcels held at the assigned PMB."""
r = requests.get(f"{BASE}/inbound-items", headers=HEADERS)
return str(r.json()) if r.ok else f"Error: {r.text}"
def request_open_and_scan(inbound_item_id: str, expected_version: int) -> str:
"""Request the only supported physical action after member approval."""
r = requests.post(
f"{BASE}/inbound-items/{inbound_item_id}/actions",
headers={**HEADERS, "Idempotency-Key": f"open-scan-{inbound_item_id}-{expected_version}"},
json={"action_type": "open_and_scan", "expected_version": expected_version},
)
return str(r.json()) if r.ok else f"Error: {r.text}"
def get_scan_results(inbound_item_id: str) -> str:
"""Read completed scans for one physical inbound item."""
r = requests.get(f"{BASE}/inbound-items/{inbound_item_id}/scans", headers=HEADERS)
return str(r.json()) if r.ok else f"Error: {r.text}"
def send_letter(recipient_name: str, recipient_line1: str,
recipient_city: str, recipient_state: str,
recipient_zip: str, pdf_path: str) -> str:
"""Print and mail a physical letter. Provide a local PDF path.
The facility prints, stuffs, stamps, and mails it."""
with open(pdf_path, "rb") as f:
r = requests.post(
f"{BASE}/mail",
headers={**HEADERS, "X-Mailbox-MD-Version": MD_VERSION},
files={"document": ("letter.pdf", f, "application/pdf")},
data={
"recipient_name": recipient_name,
"recipient_line1": recipient_line1,
"recipient_city": recipient_city,
"recipient_state": recipient_state,
"recipient_zip": recipient_zip,
"mail_class": "first_class",
},
)
if r.status_code == 201:
return f"Letter queued for mailing. ID: {r.json()['outbound_mail']['id']}"
return f"Error: {r.text}"
# Wrap each function as a LlamaIndex tool
list_physical_inbound_tool = FunctionTool.from_defaults(fn=list_physical_inbound)
request_open_and_scan_tool = FunctionTool.from_defaults(fn=request_open_and_scan)
get_scan_results_tool = FunctionTool.from_defaults(fn=get_scan_results)
send_letter_tool = FunctionTool.from_defaults(fn=send_letter)Wire Into an Agent
from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
tools = [
list_physical_inbound_tool, request_open_and_scan_tool,
get_scan_results_tool, send_letter_tool,
]
llm = OpenAI(model="gpt-4o")
agent = ReActAgent.from_tools(
tools,
llm=llm,
system_prompt="""Keep forwarded digital/OCR context separate from physical custody.
For assigned-PMB items, request only open_and_scan, only after member approval,
and always include the current item version. Infer no other physical action.""",
verbose=True,
)
# Run it
response = agent.chat("List physical inbound items and ask before requesting Open & Scan.")
print(response)You can also use FunctionCallingAgent if your LLM supports native function calling:
from llama_index.core.agent.function_calling import FunctionCallingAgent
agent = FunctionCallingAgent.from_tools(
tools,
llm=llm,
system_prompt="You manage postal mail for the organization.",
verbose=True,
)
response = agent.chat("Check for physical inbound items and report what needs review.")Webhook-Driven (Event-Based)
Instead of polling, configure a webhook in your dashboard to get notified the moment mail arrives. Process the webhook payload and invoke your agent.
# Flask webhook handler example
from flask import Flask, request, jsonify
import hmac, hashlib, os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["MAILBOX_BOT_WEBHOOK_SECRET"]
@app.route("/webhook/mail", methods=["POST"])
def handle_mail_webhook():
sig_header = request.headers.get("X-Mailbox-Signature", "")
body = request.get_data(as_text=True)
# Signature format: whsk_prefix:t=<timestamp>,v1=<hmac-hex>
_, _, sig_data = sig_header.partition(":")
params = dict(p.split("=", 1) for p in sig_data.split(",") if "=" in p)
ts, v1 = params.get("t", ""), params.get("v1", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(), f"{ts}.{body}".encode(), hashlib.sha256
).hexdigest()
if not v1 or not hmac.compare_digest(v1, expected):
return "invalid signature", 401
event = request.json
if event["event_type"] == "inbound.received":
agent.chat(
f"Physical mail arrived. Inbound item ID: {event['inbound_item_id']}. "
"Report it; request only Open & Scan when the member approves."
)
return jsonify({"ok": True})API Reference
These examples keep live forwarded digital/OCR context separate from the assigned-account physical-custody contract. New mailbox.bot-issued receiving addresses remain reservation-only until launch.