What you need
- A domain you control, or a subdomain of it.
- A Mektup account and an API key (Dashboard, API keys).
- A public HTTPS URL for your agent to receive webhooks.
1. Create the domain, the mailbox and the webhook
export MEKTUP_API_KEY=mek_live_...
API=https://api.usemektup.com/v1
# 1. the domain (or a subdomain just for agents)
curl -X POST $API/domains -H "Authorization: Bearer $MEKTUP_API_KEY" \
-H "Content-Type: application/json" -d '{"domain": "agents.yourcompany.com"}'
# add the records it returns at your DNS host, then:
curl -X POST $API/domains/agents.yourcompany.com/verify -H "Authorization: Bearer $MEKTUP_API_KEY"
# 2. the agent's mailbox
curl -X POST $API/domains/agents.yourcompany.com/mailboxes \
-H "Authorization: Bearer $MEKTUP_API_KEY" -H "Content-Type: application/json" \
-d '{"localPart": "ada"}'
# 3. where new mail should go
curl -X PUT $API/domains/agents.yourcompany.com/mailboxes/ada/webhook \
-H "Authorization: Bearer $MEKTUP_API_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://your-agent.example.com/inbound"}'
# keep the "secret" from the response2. Write the agent
import hashlib, hmac, json, os, requests
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
app = FastAPI()
API = "https://api.usemektup.com/v1"
KEY = os.environ["MEKTUP_API_KEY"]
SECRET = os.environ["MEKTUP_WEBHOOK_SECRET"].encode()
@app.post("/inbound")
async def inbound(request: Request, tasks: BackgroundTasks):
raw = await request.body()
expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
got = request.headers.get("x-mektup-signature", "").removeprefix("sha256=")
if not hmac.compare_digest(expected, got):
raise HTTPException(401)
tasks.add_task(handle, json.loads(raw)) # answer Mektup at once
return {"ok": True}
def handle(event):
msg = event["message"]
# the whole conversation, oldest first
thread = requests.get(f"{API}/threads/{requests.utils.quote(msg['threadKey'], safe='')}",
params={"mailbox": event["mailbox"]},
headers={"Authorization": f"Bearer {KEY}"}).json()["messages"]
answer = my_model(thread, msg) # your LLM call
requests.post(f"{API}/emails", headers={"Authorization": f"Bearer {KEY}"}, json={
"from": event["mailbox"], "to": msg["from"],
"subject": "Re: " + (msg["subject"] or ""), "text": answer,
"inReplyTo": msg["id"], # same thread for the customer and in Mektup
})The handler answers Mektup at once and works in the background: the webhook times out after 8 seconds and is not retried. The message is stored either way, so a scheduled GET /v1/messages catches anything missed.
3. Let people see it
Open ada@agents.yourcompany.com in Mektup's webmail, or add it to a mail app with its IMAP and SMTP login. You see what came in and everything the agent sent, and can answer yourself. To make the agent ask first, create drafts instead of sending: the shared inbox.
4. Or skip the code with MCP
In Claude Code, Cursor or another MCP client, connect https://mcp.usemektup.com/mcp and ask the agent to set up its own mailbox: it calls create_domain, create_mailbox and set_mailbox_webhook itself. MCP setup.