1. Home
  2. API docs
  3. React form

Validate email addresses in a React form

Never call the SimpleVerifier API from React code: anyone can read a key from the browser. Instead, have the form post the address to your own backend endpoint, which holds the key and calls the API, then show a hint when the status is invalid. Check again on submit, because browser checks can be skipped.

By SimpleVerifier Team

Before you start

  • Create a key on the API keys page. Keys start with sv_live_ and are shown once.
  • The key lives only on your server as SIMPLEVERIFIER_KEY. Nothing in the React code, its environment variables or its build output should contain it.
  • API access is included in the Pro plan ($29.99/month) and the Email + Phone plan ($49.99/month). See pricing.

1. Add a field that checks on blur

It posts to /api/check-email on your own domain when the visitor leaves the field, shows a message only for invalid, and ignores any answer that arrives after the visitor has typed something else.

EmailField.tsx
// EmailField.tsx — runs in the browser; it calls YOUR backend, never SimpleVerifier
import { useRef, useState } from "react";

type EmailStatus = "valid" | "risky" | "invalid" | null;

export function EmailField() {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<EmailStatus>(null);
  const [checking, setChecking] = useState(false);
  const latest = useRef("");

  async function check() {
    const address = email.trim();
    if (!address.includes("@")) return;
    setChecking(true);
    try {
      const res = await fetch("/api/check-email", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: address }),
      });
      const data = (await res.json()) as { status: EmailStatus };
      // Ignore the answer if the visitor has typed something else since.
      if (latest.current.trim() === address) setStatus(res.ok ? data.status : null);
    } catch {
      setStatus(null); // network error: don't block the visitor
    } finally {
      setChecking(false);
    }
  }

  return (
    <div>
      <label htmlFor="email">Email</label>
      <input
        id="email"
        name="email"
        type="email"
        required
        value={email}
        onChange={(e) => {
          latest.current = e.target.value;
          setEmail(e.target.value);
          setStatus(null);
        }}
        onBlur={check}
        aria-invalid={status === "invalid"}
        aria-describedby="email-hint"
      />
      <p id="email-hint" role="status">
        {checking && "Checking…"}
        {status === "invalid" && "This address can't receive mail. Please check it for typos."}
      </p>
    </div>
  );
}

2. Add the backend endpoint

This Express server uses the helper from the Node.js guide. On Next.js, use the route handler from the Next.js guide instead. Add rate limiting to the check endpoint, since anyone can call it.

server.mjs
// server.mjs — Express backend the form posts to; emailStatus() is the
// server-side helper from the Node.js guide and reads SIMPLEVERIFIER_KEY
import express from "express";
import { emailStatus } from "./simpleverifier.mjs";

const app = express();
app.use(express.json());

app.post("/api/check-email", async (req, res) => {
  const status = await emailStatus(req.body?.email);
  res.json({ status });
});

app.post("/signup", async (req, res) => {
  // Check again on submit: the browser check is a hint, not a guard.
  if ((await emailStatus(req.body?.email)) === "invalid") {
    return res.status(422).json({ error: "This email address cannot receive mail. Please check it." });
  }
  // Save the signup here.
  res.status(201).json({ ok: true });
});

app.listen(3000);

What the API returns

GET or POST https://simpleverifier.com/api/v1/verify with email runs the syntax, domain, disposable-domain and live mailbox checks in one call and returns:

{
  "email": "jane@company.com",
  "status": "valid",
  "reason": "Mailbox exists",
  "mailbox_checked": true
}

Every snippet above follows the same rule: only a successful response with status set to invalid blocks a person.

ResultWhat it meansWhat to do
validThe mail server confirmed the mailbox exists.Accept.
riskyThe domain is real but the mailbox couldn't be confirmed: catch-all domains, role addresses like info@, providers that hide mailbox status.Accept on signup forms. Store the status and keep these out of cold campaigns.
invalidBad format, no mail server, a disposable domain, or the mailbox doesn't exist.Reject and ask the person to check the address.
timeout, 429 or 503The check didn't finish: a slow mail server, too many calls this minute, or a brief outage.Accept (fail open) and log it. Re-check later if it matters.
401 or 402The key is wrong or revoked, or the subscription isn't active.Accept, and alert yourself: every check is being skipped until it's fixed.

Errors return JSON with an error message. Each key can make 120 calls a minute; for thousands of addresses, upload a CSV in the dashboard instead.

Phone numbers and spreadsheets

The same key works with /api/v1/phone?phone=…&country=… to validate a phone number and normalise it to E.164, with live carrier lookups on the Email + Phone plan. The request and response are in the API reference. To check a column of addresses without code, use the Google Sheets add-on with the same key.

Common questions

Why can't I call the API directly from the browser?
Anything the browser runs, visitors can read, including keys in environment variables that get bundled (REACT_APP_, VITE_ or NEXT_PUBLIC_ prefixes). Someone could copy the key and use your plan. Keep it on the server.
Should the form block submission while the check runs?
No. Let people submit, and run the same check on the server when the form arrives. The on-blur check is there so people can fix a typo early; the server check is the one that counts.
How much does the SimpleVerifier API cost?
API access is included in the paid plans: Pro at $29.99/month and Email + Phone at $49.99/month. There are no per-call credits. Each key can make 120 calls a minute.
Should I reject risky addresses?
Not on a signup or contact form. Risky means the mailbox couldn't be confirmed, which includes every address at a catch-all company domain. Accept it, store the status, and leave risky addresses out of cold outreach.
What if the API is slow or down?
Let the address through. A mailbox check can take several seconds when a mail server is slow, so cap the wait at about 30 seconds (less on a busy form) and treat a timeout, a 429 or a 503 as unknown rather than invalid.

Ready to clean your email and phone lists?

Start verifying emails and phone numbers today and boost your deliverability.

Get Started

Cancel anytime • No setup required