Agent Self-Signup

Let your AI agent sign up for any service automatically using browser automation and mails-agent.

Many services require email verification during signup. By combining browser automation (Playwright or Puppeteer) with mails0's verification code extraction, your agent can complete the entire signup flow without human intervention.

The Flow

Step 1 -- Bootstrap a mailbox

Run mails bootstrap to create a random 72-hour receive-only address without browser approval or exposing its key.

Step 2 -- Navigate to signup page

Use Playwright or Puppeteer to open the target service's registration page.

Step 3 -- Fill in the email

Enter the claimed mailbox address into the email field.

Step 4 -- Submit the form

Click the signup button and wait for the verification email to arrive.

Step 5 -- Extract the verification code

Use mails code --timeout 60 to wait for and extract the code automatically.

Step 6 -- Enter the code

Fill the verification code into the confirmation page. Account created.

Full Example: Playwright + mails-agent CLI

import json
import subprocess
from pathlib import Path
from playwright.sync_api import sync_playwright

subprocess.run(["mails", "bootstrap"], check=True)
config = json.loads((Path.home() / ".mails" / "config.json").read_text())
MAILBOX = config["mailbox"]

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    # Step 1: Navigate to the signup page
    page.goto("https://example.com/signup")

    # Step 2: Fill in the signup form
    page.fill('input[name="email"]', MAILBOX)
    page.fill('input[name="password"]', "SecureP@ss123")
    page.fill('input[name="name"]', "AI Agent")

    # Step 3: Submit the form
    page.click('button[type="submit"]')
    page.wait_for_url("**/verify**")

    # Step 4: Wait for the verification code (blocks up to 60s)
    result = subprocess.run(
        ["mails", "code", "--to", MAILBOX, "--timeout", "60"],
        capture_output=True, text=True
    )
    code = result.stdout.strip()
    print(f"Got verification code: {code}")

    # Step 5: Enter the code
    page.fill('input[name="code"]', code)
    page.click('button[type="submit"]')

    # Step 6: Verify success
    page.wait_for_url("**/dashboard**")
    print("Signup complete!")
    browser.close()

Full Example: Playwright + Python SDK

import json
import subprocess
from pathlib import Path
from playwright.sync_api import sync_playwright
from mails_agent import MailsClient

subprocess.run(["mails", "bootstrap"], check=True)
config = json.loads((Path.home() / ".mails" / "config.json").read_text())
MAILBOX = config["mailbox"]
client = MailsClient(api_key=config["api_key"])

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    # Navigate and fill signup form
    page.goto("https://example.com/signup")
    page.fill('input[name="email"]', MAILBOX)
    page.fill('input[name="password"]', "SecureP@ss123")
    page.click('button[type="submit"]')
    page.wait_for_url("**/verify**")

    # Wait for verification code via SDK
    code_response = client.wait_for_code(to=MAILBOX, timeout=60)
    code = code_response.code
    print(f"Got code: {code}")

    # Enter code and complete signup
    page.fill('input[name="code"]', code)
    page.click('button[type="submit"]')
    page.wait_for_url("**/dashboard**")
    print("Signup complete!")
    browser.close()

Full Example: Puppeteer + curl

const fs = require("fs");
const os = require("os");
const path = require("path");
const puppeteer = require("puppeteer");
const { execFileSync } = require("child_process");

const API = "https://api.mails0.com";
execFileSync("mails", ["bootstrap"], { stdio: "inherit" });
const config = JSON.parse(
  fs.readFileSync(path.join(os.homedir(), ".mails", "config.json"), "utf8")
);
const TOKEN = config.api_key;
const MAILBOX = config.mailbox;

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  // Navigate and fill form
  await page.goto("https://example.com/signup");
  await page.type('input[name="email"]', MAILBOX);
  await page.type('input[name="password"]', "SecureP@ss123");
  await page.click('button[type="submit"]');
  await page.waitForNavigation();

  // Wait for verification code via API
  const resp = await fetch(
    `${API}/v1/code?to=${MAILBOX}&timeout=60`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );
  const { code } = await resp.json();
  console.log(`Got code: ${code}`);

  // Enter code
  await page.type('input[name="code"]', code);
  await page.click('button[type="submit"]');
  await page.waitForNavigation();
  console.log("Signup complete!");

  await browser.close();
})();

Using the CLI only (no browser automation)

If the signup flow is API-based (no browser needed), you can do everything with curl and the mails CLI:

# 1. Create or reuse a safe temporary mailbox
mails bootstrap

# Set this to the mailbox shown by the command
MAILBOX="[email protected]"

# 2. Register via API
curl -X POST https://example.com/api/register \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"$MAILBOX\", \"password\": \"SecureP@ss123\"}"

# 3. Wait for verification code
CODE=$(mails code --to "$MAILBOX" --timeout 60)
echo "Verification code: $CODE"

# 4. Verify the account
curl -X POST https://example.com/api/verify \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"$MAILBOX\", \"code\": \"$CODE\"}"

How code extraction works

The /v1/code endpoint and mails code CLI command use pattern matching to extract verification codes from incoming emails. They support:

The --timeout flag makes the request block until a matching email arrives (long-polling), so you do not need to implement retry logic yourself.

Headless Agent Self-Registration

Use the official CLI or MCP server for headless mailbox creation. They keep a persistent idempotency key, store the API key locally with owner-only permissions, and reuse an active mailbox instead of creating duplicates.

# CLI
npm install -g mails-agent
mails bootstrap

# MCP: start without credentials, then call
create_temporary_mailbox {}

Do not automate /v1/claim/confirm. Permanent named mailboxes require human approval through mails claim <name>. Temporary mailboxes are receive-only, expire after 72 hours, and cannot create additional mailboxes.

Tips for reliability