Account registration is one of the highest-value automation targets for AI agents. Research agents that need access to paywalled data, evaluation agents that need to trial software, scraping agents that need authenticated sessions — all of them require the ability to create verified accounts. And all of them hit the same wall: email verification.

The Complete Pipeline

Automated account registration is a seven-step pipeline. Each step must succeed before the next can proceed:

  • Create inbox: provision a dedicated email address for this registration
  • Navigate signup: open the registration page with a headless browser
  • Fill form: enter name, email (the provisioned inbox), password, and any other required fields
  • Submit: click the signup button and wait for the verification prompt
  • Wait for OTP or magic link: block until the verification email arrives
  • Complete verification: enter the OTP or follow the magic link
  • Store credentials: save session state, cookies, and login details for reuse

Playwright + AgentMailr: Full Implementation

The following TypeScript implementation covers the complete pipeline, with fallback handling for both OTP codes and magic links:

import { chromium } from "playwright";
import AgentMailr from "agentmailr";

const client = new AgentMailr({ apiKey: process.env.AGENTMAILR_API_KEY });

async function registerAccount(signupUrl: string) {
  const inbox = await client.inboxes.create();
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();

  try {
    // Step 1: Navigate and fill signup form
    await page.goto(signupUrl);
    await page.fill('[name="email"], [type="email"]', inbox.address);
    await page.fill('[name="password"], [type="password"]', generatePassword());
    await page.click('[type="submit"], button:has-text("Sign up")');

    // Step 2: Wait for verification — try OTP first, fall back to magic link
    let verified = false;

    try {
      // OTP path
      const { otp } = await client.messages.waitForOTP({
        inboxId: inbox.id,
        timeout: 30_000,
      });
      const otpField = await page.waitForSelector(
        '[name="otp"], [name="code"], [placeholder*="code"]',
        { timeout: 10_000 }
      );
      await otpField.fill(otp);
      await page.click('[type="submit"]');
      verified = true;
    } catch {
      // Magic link path
      const email = await client.messages.wait({
        inboxId: inbox.id,
        timeout: 30_000,
      });
      if (email.links?.verification) {
        await page.goto(email.links.verification);
        verified = true;
      }
    }

    if (!verified) throw new Error("Verification failed");

    // Step 3: Save session state for reuse
    await page.context().storageState({ path: `sessions/${inbox.address}.json` });

    return { address: inbox.address, sessionPath: `sessions/${inbox.address}.json` };
  } finally {
    await browser.close();
    await client.inboxes.delete(inbox.id);
  }
}

Running at Scale

The pipeline above runs for a single account. To scale to hundreds of parallel registrations, wrap it in a concurrency manager:

import pLimit from "p-limit";

const limit = pLimit(20); // 20 concurrent browser sessions

const results = await Promise.all(
  Array.from({ length: 100 }, (_, i) =>
    limit(() => registerAccount("https://example.com/signup"))
  )
);

// 100 verified accounts, each with its own inbox,
// run in parallel with 20 at a time.
// No shared state. No OTP collisions. No ToS risk.

Each concurrent registration gets its own inbox. OTPs are routed to the correct agent automatically. The concurrency limit prevents browser resource exhaustion while still achieving high throughput.

Start Free

AgentMailr provides the email infrastructure for the complete registration pipeline. Free to start, no credit card required.