cluster:developer Email Verification Testing Temp Mail API QA Automation Node.js

How to Automate Email Verification Testing With a Temp Mail API

9 min read

How to Automate Email Verification Testing With a Temp Mail API

Automating email verification testing means proving the complete flow, not merely proving that an email request returned 200. A useful test creates an isolated address, submits a test registration, waits for the expected message, extracts the confirmation link, opens it in the same test context, and asserts that the account is active. A temp mail API provides the inbox boundary; your test still owns the timing, matching, security, and cleanup decisions.

This guide uses Inboxto's documented API and a controlled staging user. It does not target real customer accounts, bypass a platform's signup rules, or treat a public inbox as a safe place for production secrets. API access and limits can change, so check the current Inboxto API documentation before copying an endpoint into CI.

The verification flow to test

Model the flow as five explicit states:

  1. Address ready: the test has a unique recipient address it can query.
  2. Registration submitted: your staging application accepted the test user.
  3. Message received: the expected sender delivered a verification email to that address.
  4. Link consumed: the browser opened the exact confirmation URL and the server accepted it.
  5. Account active: the application reports the verified state through its UI or test API.

This separation makes failures useful. “No email” is different from “email arrived but the link was invalid,” and both are different from “the link worked but the account remained unverified.” For the inbox-side checks a human would make, see how to find a verification email in a temporary inbox.

Why a temp mail API is better than a shared inbox

A shared mailbox can make a first test pass while hiding race conditions. Parallel workers may read one another's messages, an old code may satisfy a broad subject assertion, and a later run may not be able to reproduce the same account state.

Use one isolated address per test or worker. Inboxto addresses are persistent rather than countdown-only, so a test identity can receive a later re-verification or password-reset message without being recreated. Persistence is useful for lifecycle tests; it does not mean that every message is retained forever. The current product page lists seven-day retention for regular free-plan messages and permanent storage for Starred messages, while Premium lists longer retention and API access. Treat the live product page as authoritative for current plan limits.

For the broader choices between a short-lived inbox, a persistent address, and a custom domain, read Inboxto for developers: API, custom domains, and email testing.

The smallest API contract

The public Inboxto documentation currently shows these operations for the workflow:

Test action API operation Why it matters
Create or reserve an address POST /v1/addresses Gives the test an isolated recipient
Find delivered mail GET /v1/messages?address=... Lets the test poll without opening a UI
Read one message GET /v1/messages/:id Returns the body and metadata needed for parsing
Read and delete latest mail POST /v1/messages/pop Useful for queue-like tests, but destructive

The API base URL shown in the docs is https://inboxto.app/api. Requests use a Bearer API key created in account settings. Keep that key in CI secret storage, not in source code, fixtures, URLs, or assertion output. The docs currently state a default limit of 100 requests per hour and include rate-limit headers, so polling must have a deadline and a sensible interval.

Language-agnostic pseudocode

The core algorithm is deliberately simple:

address = inbox.create(unique_address)
registration = app.register(staging_user, address)
assert registration.accepted

deadline = now + 60 seconds
delay = 2 seconds
message = none

while now < deadline:
    candidates = inbox.list(address, limit=10, unread=true)
    message = first candidate where
        recipient == address
        sender_domain == expected_sender_domain
        subject matches expected_verification_subject
        received_at >= registration.started_at

    if message exists:
        break

    sleep(delay + small_jitter)
    delay = min(delay * 1.5, 8 seconds)

assert message exists, report sanitized delivery state
detail = inbox.get(message.id)
link = extract_https_link(detail.html_body or detail.text_body, expected_service_host)
assert link exists

browser.open(link)
assert app.account_status() == "verified"

The important details are the bounded deadline, increasing delay, recipient check, sender check, timestamp check, and trusted destination check. A loose “first email containing six digits” rule is not a verification test; it is a race condition with a friendly name.

A concrete Node.js example

The following helper uses Node's built-in fetch. Replace triggerStagingSignup and the configured domain with your own authorized test environment. The address format must match a domain configured for the Inboxto account; do not copy the example domain literally.

const API_BASE = 'https://inboxto.app/api';
const API_KEY = process.env.INBOXTO_API_KEY;

if (!API_KEY) throw new Error('INBOXTO_API_KEY is required');

async function api(path, options = {}) {
  const response = await fetch(`${API_BASE}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      ...options.headers
    }
  });

  const body = await response.json();
  if (!response.ok) {
    throw new Error(`Inboxto API ${response.status}: ${body.message || body.error}`);
  }
  return body.data;
}

async function triggerStagingSignup(address) {
  // Call your own staging endpoint or browser flow here.
  return fetch('https://staging.example.test/signup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: address })
  });
}

const address = `verify-${Date.now()}@your-configured-domain.example`;
const startedAt = new Date();

await api('/v1/addresses', {
  method: 'POST',
  body: JSON.stringify({
    address,
    display_name: 'Verification test',
    type: 'custom',
    is_primary: false
  })
});

const signup = await triggerStagingSignup(address);
if (!signup.ok) throw new Error(`Staging signup failed: ${signup.status}`);

const deadline = Date.now() + 60_000;
let match;
let delay = 2_000;

while (Date.now() < deadline && !match) {
  const messages = await api(
    `/v1/messages?address=${encodeURIComponent(address)}&limit=10&unread=true`
  );

  match = messages.find((message) => {
    const received = Date.parse(message.received_at || '');
    return message.to === address &&
      received >= startedAt.getTime() &&
      /verify|confirm/i.test(message.subject || '') &&
      /@your-product\.test$/i.test(message.from || '');
  });

  if (!match) {
    await new Promise((resolve) => setTimeout(resolve, delay));
    delay = Math.min(Math.round(delay * 1.5), 8_000);
  }
}

if (!match) {
  throw new Error(JSON.stringify({
    address,
    stage: 'message-not-found-before-deadline'
  }));
}

const message = await api(`/v1/messages/${encodeURIComponent(match.id)}`);
const source = message.html_body || message.text_body || '';
const link = [...source.matchAll(/https:\/\/[^\s"'<>]+/g)]
  .map(([value]) => value.replace(/[),.;]+$/, ''))
  .find((value) => new URL(value).hostname === 'staging.example.test');

if (!link) throw new Error('Trusted verification link not found');
console.log({ messageId: message.id, receivedAt: message.received_at });
// Open `link` in the same browser context, then assert account status.

The sender check in a real project should use the exact sender domain documented by that project. The example intentionally avoids printing the message body, code, or link because CI logs are often visible to more people and retained longer than the test inbox.

Extract links defensively

Verification templates vary. A link may be in plain text, HTML, a button attribute, or a redirect wrapper. Your parser should:

  • validate the message recipient and expected sender before parsing;
  • require https unless your isolated test environment has a documented exception;
  • allow only the exact staging host or an explicit allowlist;
  • preserve the complete query string and fragment;
  • reject javascript: URLs, unrelated hosts, and malformed values; and
  • keep the extracted link out of normal logs.

Do not use a public URL scanner to inspect a live verification link. If you need diagnostics, log the hostname and a hash or redacted prefix, never the token.

Polling, backoff, and parallel workers

Fixed sleeps are easy to write and hard to operate. A five-second sleep may be wasteful when mail arrives immediately and too short when a provider is briefly delayed. Use a bounded loop with backoff, as above, and make the deadline a test setting that reflects your staging delivery path.

For parallel workers:

  1. generate a unique address with a worker or run identifier;
  2. never search by subject alone;
  3. filter by recipient and a recent timestamp;
  4. add small jitter so every worker does not poll at the same instant; and
  5. report sanitized state when the deadline expires.

If your API usage approaches the documented limit, reduce polling frequency, narrow the query, or consider a webhook design with authenticated callbacks and idempotency. Do not respond to a timeout by adding an unbounded retry loop.

What to assert beyond “email arrived”

A durable verification test checks the whole contract:

  • registration was accepted by the staging application;
  • exactly one expected message arrived for the test address;
  • the sender, recipient, subject, and timestamp matched;
  • the link used the expected host and preserved its token;
  • the link worked once in the intended browser session;
  • the same link was rejected or treated as already used afterward; and
  • the account's verified state is visible through an authoritative screen or API.

For a second test, reuse a persistent address only when you are intentionally testing re-verification or recovery. Otherwise, a fresh address per run keeps state isolation stronger. The persistence advantage is that a deliberately retained test identity can cover later lifecycle events without re-registering every suite; it is not a reason to share one mailbox across unrelated tests.

Scope and safe use

Use a temp mail API for applications and staging environments you are authorized to test. Do not use it to create fake accounts at scale, evade a third party's email policy, or process real customer verification messages. Inboxto is a receive-only email product: it does not send mail, provide SMS numbers, or guarantee that an external platform accepts every disposable domain.

If you are starting manually, read what temporary email is and how it works. When the staging flow is ready, review the live Inboxto API docs, store a scoped key in CI secrets, and begin with one verification path.

Sources checked

The Inboxto API endpoints, Bearer authentication, default rate-limit statement, and product capabilities were checked against the live public pages on September 21, 2026. Endpoint contracts and plan limits can change; use the live documentation as the final authority.

Ready to Protect Your Privacy?

Create your first anonymous email address in seconds. No registration required.

Get Started