
Hmail
Hmail: HORSed email. Your agents read it; your secret unlock only with your approval
Demo
About
Hmail is a HORSed email: a Gmail connector for AI agents where your assistant can read your mail, but login codes, sign-in links and password-reset links stay locked until you approve each one with a live World ID Selfie Check. Why: agents are now connected to everything. In September 2026, a personal agent (Instinct) with access to its user's Gmail took a one-time Luma login code from his inbox and signed in without asking. Full access to an inbox means full access to every account that inbox can unlock. How it works: Your "house" runs on your own computer. It syncs Gmail read-only, checks each sender's DKIM signature, and holds codes and sign-in/reset links in a sealed vault (AES-256-GCM). Nothing is stored on our servers. Agents talk to the house through an MCP server gated by HORS (Human-Origin Resource Sharing, "CORS for agents"). Every call is signed and tied to the anonymous World ID human behind the agent, through World ID for Agents (AgentBook). Instead of sharing keys, the house shares functions, each with its own policy: search / read: only your own agents (same-human); a redaction net replaces codes with [held · site · kind] attachment: refused for mails that contain held codes use_code(site, reason): needs a held code from that exact site, and your face pair: a new assistant joins only after you approve a 6-digit confirm number When an agent needs a code, use_code returns an approval challenge. The link and QR also appear live on your house page. You do a Selfie Check in World App, bound to that site, that reason and that one request. Continuity: setup creates an IDKit session with a Selfie Check, and every release is proved inside that same session. The house checks the session id itself, so only the person who set up the house can approve, every time; a different face is refused. Proofs are verified server-side with World's v4 verify API (environment pinned), and each nullifier is accepted once. The code is sealed to the agent's one-time key and typed straight into the verified site tab in Chrome over DevTools. The agent never sees the digits; its transcript only shows [held]. ENSv2: your ENS name is your mailbox's address for agents. Setup mints <you>.hmail.eth on ENSv2 (Sepolia) in about a minute, with a live transaction timeline. Each house gets its own PermissionedResolver, and the house holds setter roles for exactly two ENSIP-26 records (agent-endpoint[mcp] and [web]). The issuer renounces its rights, and hmail.eth is sealed. The house rewrites its own endpoint every time it restarts, so "you.hmail.eth" always finds your house even though its tunnel address changes. The approval page resolves the name to reject phishing links. Onboarding for non-technical users: copy one setup prompt into a local AI assistant, then on your house page connect Gmail, pick a name and scan your face. Copy one message to any assistant (including cloud ones like Grok); it links itself to your World ID once and asks to join. Tested failure paths: declined, expired, different face, replayed approval, a code requested for another site (NO_HELD_CODE), and phishing approval links. More uses: print an attachment from a computer that isn't yours without typing a password, and scam emails that try to trick your agent into forwarding a code get nothing. Built with hors-sdk and hors-cli 0.1.0, MCP, World IDKit 4.3 (Selfie Check sessions), World ID for Agents (AgentKit/AgentBook), ENSv2 on Sepolia with viem, and Vercel for the web pages, RP signer and name issuer.
How it's made
Hmail is plain ESM JavaScript on Node 22 with no build step, in three parts: a local daemon (the "house"), a CLI for assistants, and a small web project on Vercel. The house (runs on your computer) An MCP server wrapped by hors-sdk (HORS 0.1.0, our own existing project). Each tool declares a policy: an origin (same-human or any-human), rules that return {deny, code, challenge}, and middleware. Rules share state with the handler through ctx.state. For example, the heldCode rule finds the code and the handler releases exactly that code. A redaction-net middleware scrubs anything code-like from every result. Unsigned tools/list publishes every policy, so any agent can see what it needs. Gmail: desktop OAuth with PKCE and a loopback redirect. The house does the token exchange itself, so tokens never touch our servers. It syncs read-only every 15 s, verifies DKIM with mailauth (aligned pass only), strips hidden text against prompt injection, and classifies login codes, sign-in links and reset links into a held store. State at rest is sealed with AES-256-GCM. A free cloudflared quick tunnel makes the house reachable. localhost:8390 redirects to the hosted house page with a per-run secret in the URL fragment, and the setup API also enforces the Origin header. World (partner tech) IDKit 4.3 Selfie Check in a session gave us exactly the continuity question we needed: "is it still the same person who set up the house?" Setup does createSession; every release is a fresh Selfie Check proved in that session. The signal binds house, site, nonce and sha256(reason, recipient key). The house polls World's bridge itself and verifies with the v4 verify API, with the environment pinned. It also checks the session id and accepts each nullifier once. The RP signing key lives only on Vercel. World ID for Agents (AgentBook, through HORS) tells the house which human is behind each assistant, with no accounts or API keys. hmail connect runs agentkit-cli register in agent mode and filters its output, so the assistant relays only the World App link. Hacky bits: presets throw for sessions, so we build the request with .constraints(CredentialRequest("selfie", { signal })). IDKit loads its WASM with fetch(file://), which Node lacks, so we shim fetch. @hono/node-server swaps the global Request/Response and silently breaks IDKit, so we set overrideGlobalObjects: false. World's human-in-the-loop package needs Vercel Workflow, so we wrote our own approval loop for a local daemon. ENSv2 (partner tech, Sepolia) The issuer (a Vercel function) mints <you>.hmail.eth in a UserRegistry under hmail.eth. It deploys a PermissionedResolver per house and grants the house setter roles for exactly two ENSIP-26 records (agent-endpoint[mcp] and [web]), then renounces its own rights. hmail.eth itself is sealed. The house rewrites its endpoint on every restart, so a free, changing tunnel URL gets a permanent, self-healing name that only the house can move. The approval page resolves the name to reject phishing links. The issuer streams each step as NDJSON to the house page, which draws a live timeline with Etherscan links. Transactions go out in three batches (deploy and fund, register with records and grants, renounce), with explicit nonces. Hacky bits: the load-balanced Sepolia RPC estimated gas against a stale node, so transactions failed. We now send fixed gas limits and retry reads until the chain catches up. A salt retry recovers half-finished deployments, and houses are funded only on fresh registration, so they can pay for their own record updates. Releasing a code The code is sealed to the assistant's one-time X25519 key (HKDF-SHA256, AES-256-GCM, with site and approval id as associated data). The CLI's typist types it over the Chrome DevTools Protocol into the one visible https://<site> tab. It only types into a code field (never an email box) and checks that the code landed before pressing Enter. The agent never sees the digits. Onboarding hack The whole setup is one prompt for a local assistant: npx -y -p node@22 -p github:cqlyj/hmail hmail up --background. Pulling node@22 as an npm package guarantees the right Node version on any machine. Pairing uses a 6-digit confirm number instead of codes. The approval QR is drawn locally with a vendored generator, because of a strict CSP.