TypeScript · Python · MCP

Let an agent sign itself up

Your agent needs an email address and there is no key in its environment. It can get one itself: one call solves a short proof of work and returns an inbox and a key. You claim the workspace when it suits you.

Written against
agentboxd (npm) 0.1 · agentboxd (PyPI) 0.1 · @agentboxd/mcp 0.1 · Node.js 22+
Checked
25 September 2026
Runnable example
examples/guides/agent-self-signup

Most agents get their API key from a person who signed up on the dashboard. Some can’t wait for that: an agent started in a fresh sandbox, a coding agent trying a tool for its user, a research agent that needs to receive one confirmation email. With self-signup the agent creates its own workspace, uses it at once, and a person takes ownership later. Until then it runs with tight sending limits, so a signup can’t turn into a spam cannon.

#Sign up from TypeScript

Agentboxd.signup() is a static method: it needs no key. It fetches a challenge, finds the proof of work (a few seconds of CPU with Node’s crypto), creates the workspace and returns a client that already uses the new key.

signup.ts
/**
 * An agent with no API key gives itself an email inbox, then asks its human to claim the workspace.
 *   npx tsx agent-self-signup/signup.ts you@example.com
 * Prints the key once: store it (e.g. as AGENTBOXD_API_KEY) before doing anything else.
 */
import { Agentboxd } from 'agentboxd';

const ownerEmail = process.argv[2]; // optional: your own address, to claim the workspace later

// Solves a short proof-of-work challenge (a few seconds of CPU), then creates the workspace.
const { client, api_key, inbox, restrictions, claim } = await Agentboxd.signup({
  agentName: 'research-agent',
  ownerEmail,
});

console.log(`AGENTBOXD_API_KEY=${api_key}  # shown once: store it now`);
console.log(`inbox: ${inbox.address} (${inbox.id})`);
console.log(`claim: ${claim.status}${claim.email ? ` to ${claim.email}` : ''}`);
console.log(`until claimed: ${restrictions.recipients_per_day} new recipients a day, no webhooks`);

// Receive mail right away: long-poll up to 60 s for the next email.
const mail = await client.messages.wait(inbox.id, { timeout: 60 });
if (mail) {
  console.log(`from ${mail.from}: ${mail.subject}`);
  // Replies in a thread someone started with the agent don't count toward the daily limit.
  await client.messages.reply(inbox.id, mail.id, { text: 'Thanks, got it.' });
}

// Where the workspace stands: claim status, limits, and when an idle unclaimed workspace is deleted.
const account = await client.account.get();
console.log(account.claim.status, account.claim.expires_at ?? '');
shell
npm install agentboxd
npx tsx signup.ts you@example.com

The key is printed once. Put it where your agent keeps secrets (an environment variable, a secret manager) before anything else: there is no way to see it again, and a lost key means a new signup.

#Sign up from Python

The Python SDK has the same call, sync and async. It returns a Signup with .client, .api_key, .inbox and the full answer in .result.

signup.py
"""An agent with no API key gives itself an email inbox, then asks its human to claim the workspace.

python agent-self-signup/signup.py you@example.com
Prints the key once: store it (e.g. as AGENTBOXD_API_KEY) before doing anything else.
"""

import sys

from agentboxd import Agentboxd

owner_email = sys.argv[1] if len(sys.argv) > 1 else None

# Solves a short proof-of-work challenge (a few seconds of CPU), then creates the workspace.
s = Agentboxd.signup(agent_name="research-agent", owner_email=owner_email)
print(f"AGENTBOXD_API_KEY={s.api_key}  # shown once: store it now")
print(f"inbox: {s.inbox['address']} ({s.inbox['id']})")
print(f"claim: {s.result['claim']['status']}")

mr = s.client
mail = mr.messages.wait(s.inbox["id"], timeout=60)
if mail:
    print(f"from {mail['from']}: {mail['subject']}")
    # Replies in a thread someone started with the agent don't count toward the daily limit.
    mr.messages.reply(s.inbox["id"], mail["id"], text="Thanks, got it.")

if owner_email is None:
    # Later, once your human agrees: they get a single-use link to claim the workspace.
    print("ask your human for their email, then: mr.account.request_claim(email)")
print(mr.account.get()["claim"])

#Sign up from an MCP client

Leave AGENTBOXD_API_KEY out of the MCP config. The server starts anyway and offers a signup tool; the model calls it, and the rest of the session uses the new key. By default the key is kept in memory only and the tool asks the model to show it to you. Add "--save-key=/path/to/agentboxd.env" to args if the server should keep it.

claude_desktop_config.json
{
  "mcpServers": {
    "agentboxd": {
      "command": "npx",
      "args": ["-y", "@agentboxd/mcp"]
    }
  }
}

Try a prompt like: “You have no email yet. Sign up for an Agentboxd inbox, tell me its address and key, then wait for my test email and reply to it.”

#What the agent can do before a claim

  • Receive anything, like every workspace: wait, verification codes, search and the event stream all work.
  • Reply without limit in threads that someone started by emailing the agent, to the people already in them.
  • Send new mail to at most 20 different recipients per UTC day. The 21st gets 429 unclaimed_recipient_limit.
  • No webhooks, custom domains or identity tokens until a person claims the workspace (403 unclaimed_workspace). Use the WebSocket stream instead of a webhook.
  • An unclaimed workspace with no activity for 30 days is deleted, with its mail.

#Claim the workspace

Pass ownerEmail at signup, or later call client.account.requestClaim(email) (request_claim in MCP). The person gets a single-use link from login@agentboxd.com. Opening it shows what they are claiming; confirming signs them in and makes them the owner. The workspace becomes a normal Free workspace: the limits lift, webhooks work, and the agent’s existing key keeps working.

Only ask for a claim with your user’s agreement and their own address. The email carries nothing the agent wrote, so it can’t be used to message strangers.

#Next steps