Skip to content
Tutorials

Create Email Address Programmatically: A Practical Guide

| | 10 min read
Create Email Address Programmatically: A Practical Guide
Create Email Address Programmatically: A Practical Guide

Creating an email address programmatically sounds simple until you try to automate the full loop: generate an address, trigger an email (OTP, magic link, invite), then reliably read that email in code without brittle parsing or inbox collisions.

For AI agents and QA automation, “create email address” is really shorthand for something more specific:

  • Provision a routable inbox on demand (not a human mailbox).
  • Receive messages deterministically (webhook or polling, not “sleep 10 seconds”).
  • Consume content as structured data (JSON) so tools and agents can act safely.

This guide explains the main approaches, when each one works, and a practical implementation pattern you can drop into agent toolchains and test harnesses.

What it means to “create an email address programmatically”

In consumer email, an “email address” usually implies a long-lived account with credentials, UI access, recovery options, and ongoing identity. In automation, that model is often the wrong abstraction.

What you typically want is an inbox handle you can create and destroy, plus an address that routes into it. That inbox becomes a scoped container for messages in a single run, test, or agent session.

Why this distinction matters:

  • Isolation: one inbox per run avoids collisions.
  • Lifecycle control: you can expire and discard inboxes.
  • Least privilege: automation reads only what it needs, for a short time.

If you are designing the API or the internal interface for your test harness, model an inbox resource first, then attach an address to it.

Choose the right method based on your goal

There are several ways to generate or simulate email addresses. The best option depends on whether you need to actually receive mail, and whether the system under test must deliver over real email infrastructure.

Quick comparison

Method Can receive real emails? Good for CI and agents? Typical failure mode Best use case
Reserved domains (example.com) No Yes No inbox exists Unit tests, validation-only flows
Plus-addressing (name+tag@) Yes (if provider supports it) Sometimes Collisions, provider quirks Lightweight tagging in a real inbox
Provider aliases (Workspace, etc.) Yes Sometimes Setup overhead, limits Small teams, stable staging identity
Catch-all domain Yes Risky Misrouting, noisy inbox Controlled environments with strict routing
Local SMTP capture No (not real delivery) Yes Not representative of prod Dev and local integration tests
Disposable inbox API Yes Yes Vendor dependency Deterministic E2E tests, agent workflows

If your workflow must validate “email was delivered” and then extract an OTP/link, disposable inboxes (created via API) are often the most automation-friendly choice.

A simple flow diagram showing an AI agent or test runner creating a disposable inbox via API, the application sending a verification email to the generated address, and the inbox provider delivering the email as JSON via webhook (with polling as a fallback).

A practical, repeatable workflow (works for tests and AI agents)

Regardless of the backend, robust email automation follows the same five steps:

1) Provision an inbox (and get an address)

You want a function that returns:

  • address (the email address you can type into a form)
  • inbox_id (the handle you use to query or correlate messages)
  • metadata like expiry, if supported

Treat the inbox_id as the primary key in your system.

2) Trigger the email

This is your normal application behavior: signup, passwordless sign-in, invite, email verification, etc.

For reliability, add correlation when you can. Two common strategies:

  • Put a run_id in the user identifier (for example, the local part of the email).
  • Add an application-level correlation ID in the email content or headers (if you control sending).

3) Wait deterministically (webhook-first, polling fallback)

Avoid fixed sleeps. A deterministic wait looks like:

  • Start a timer with a hard timeout.
  • Prefer webhook delivery when available.
  • Fall back to polling with backoff.

This keeps tests fast when email arrives quickly and still stable when delivery is slow.

4) Parse as data, extract only what you need

Your automation usually needs one artifact:

  • an OTP code
  • a magic link URL
  • an invite token

Extract it from a stable representation. If you are feeding content into an LLM agent, provide a minimal JSON envelope (sender, subject, text body, extracted links/codes) rather than raw HTML.

5) Clean up

Short retention reduces risk and cost:

  • expire inboxes aggressively
  • avoid logging full message bodies
  • keep the raw email only when debugging

Common pitfalls (and how to avoid them)

Pitfall: “I created an address, but can’t reliably read the right email”

This happens with shared inboxes and plus-addressing patterns. Multiple runs write to the same mailbox, then your code has to “search” or “filter” in an eventually consistent pile.

Fix: create one inbox per run (or per test) and only query that inbox.

Pitfall: HTML scraping breaks your automation

Email HTML varies across templates, clients, and even A/B tests. Parsing it with regex is fragile.

Fix: prefer text/plain or normalized JSON output. If you must parse HTML, sanitize and keep the extraction logic conservative.

Pitfall: webhooks become a security hole

Inbound email is untrusted input, and webhook endpoints are often public.

Fix:

  • verify signed payloads when available
  • enforce timestamp tolerances to reduce replay risk
  • validate that extracted URLs match an allowlist of your domains

Implementation pattern: an “EmailAddressFactory” interface

If you support multiple environments (local dev, CI, staging), define a small interface and swap implementations.

A minimal contract:

  • createInbox(): { inbox_id, address }
  • waitForMessage(inbox_id, matcher, timeout_ms): message
  • extractVerificationArtifact(message): { otp?, url? }

This interface keeps your agent tools and tests stable even if you change providers.

Using a disposable inbox API (Mailhook) for programmatic email

Mailhook is built around programmable, disposable inboxes that deliver received emails as structured JSON, with webhook notifications and a polling API.

To avoid drifting from the actual product contract, use Mailhook’s canonical integration reference here: llms.txt.

High-level flow

  1. Create a disposable inbox via API.
  2. Use the returned address in your app flow.
  3. Receive the email as JSON via webhook (recommended) or poll.
  4. Extract your artifact (OTP or link).

Pseudocode example (agent-friendly)

The exact endpoints and schema are defined in llms.txt. The pattern below focuses on the control flow.

// Pseudocode (Node-style)

async function createEmailAddressForRun(runId) {
  // Call Mailhook to create a disposable inbox
  // Returns { inbox_id, address, ... }
  const inbox = await mailhook.createInbox({
    metadata: { run_id: runId },
    // optionally configure webhook target depending on your architecture
  });

  return { inbox_id: inbox.inbox_id, address: inbox.address };
}

async function waitForVerificationEmail(inbox_id, timeoutMs) {
  // Webhook-first: in many setups you would push webhook events to a queue,
  // then your agent/test waits on that queue.
  // Polling fallback: query inbox messages until matcher hits or timeout.

  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    const messages = await mailhook.listMessages({ inbox_id });

    const match = messages.find(m =>
      (m.subject || "").toLowerCase().includes("verify")
    );

    if (match) return match;

    await sleep(500); // small backoff; increase gradually in real code
  }

  throw new Error("Timed out waiting for verification email");
}

function extractArtifact(message) {
  // Prefer a normalized field or text/plain representation when provided.
  // Keep extraction minimal and deterministic.
  return {
    otp: maybeExtractOtp(message),
    url: maybeExtractFirstAllowedUrl(message, ["https://yourapp.example"]) 
  };
}

Webhook verification (don’t skip this)

Mailhook supports signed payloads for security. Make signature verification a default in your webhook handler, and fail closed if verification fails.

Because signature formats differ by provider and can change, refer to llms.txt for the exact headers and verification steps.

Shared domains vs custom domains

When you create an email address programmatically, you also need a domain strategy:

  • Shared domains are fast to start and great for ephemeral flows.
  • Custom domains help with tighter control and brand alignment, and can be useful when you need consistent sending reputations or stricter routing.

Mailhook supports both shared domains and custom domain support, which lets you pick the right posture per environment.

Batch processing: when one inbox is not enough

Agent systems and CI pipelines often run in parallel. In those cases, you want to provision many inboxes at once and process messages in batches.

Mailhook supports batch email processing, which is useful when:

  • you launch many signups concurrently
  • you run large test matrices
  • you need to drain messages efficiently for a set of inboxes

(Again, see llms.txt for the precise API contract.)

Reliability checklist for “create email address” automation

Use this as a final pre-flight before you blame your provider or your mail templates:

  • Use one inbox per run (or per test) to avoid collisions.
  • Prefer webhook-first, keep polling fallback for resilience.
  • Use explicit timeouts and log them as structured events.
  • Assert on intent (sender/subject/expected artifact), not full HTML.
  • Treat email as untrusted input, especially with LLM agents.
  • Verify signed webhooks when available.
  • Minimize retention and redact sensitive fields in logs.

If you want deeper dives on related reliability topics, these Mailhook posts are good companions:

Frequently Asked Questions

Can I create a real Gmail address programmatically? Creating consumer Gmail accounts automatically is not a reliable or recommended approach for automation. For tests and agents, it is better to provision disposable inboxes or use controlled domains and inbox APIs.

What’s the difference between plus-addressing and creating a new inbox? Plus-addressing creates many address variants that typically route into the same mailbox, which can cause collisions in parallel runs. A new disposable inbox provides isolation and simpler matching.

Should I use webhooks or polling to receive emails? Webhooks are usually faster and more efficient because delivery is event-driven. Polling is still useful as a fallback when webhooks are unavailable or temporarily failing.

How do I make email-based tests non-flaky? Use one inbox per test run, avoid fixed sleeps, set hard timeouts, and parse messages as structured data (or text/plain) rather than scraping HTML.

Is it safe to let an LLM agent read inbound emails? It can be, if you treat email as untrusted input, pass a minimal JSON envelope to the model, sanitize content, and validate any extracted URLs or codes before acting.

Try Mailhook for programmable temp inboxes

If your goal is to create an email address programmatically for AI agents, QA automation, or verification flows, Mailhook provides disposable inbox creation via API, structured JSON email output, real-time webhooks (with signed payloads), and a polling API.

email automation API testing AI agents webhooks disposable email

Related Articles