1. Home
  2. API docs
  3. Next.js

Verify email addresses in Next.js

In Next.js, call the SimpleVerifier API from server code only: a server action or a route handler. Read the key from process.env.SIMPLEVERIFIER_KEY without a NEXT_PUBLIC_ prefix so it is never sent to the browser, reject only status invalid, and treat timeouts as unknown so a slow mail server never blocks a signup.

By SimpleVerifier Team

Before you start

  • Create a key on the API keys page. Keys start with sv_live_ and are shown once.
  • Add SIMPLEVERIFIER_KEY to .env.local and to your host's environment settings. Never prefix it with NEXT_PUBLIC_; that would put it in the client bundle.
  • API access is included in the Pro plan ($29.99/month) and the Email + Phone plan ($49.99/month). See pricing.

1. Add a server-side helper

Typed, no dependencies, never throws. Import it only from server actions, route handlers and server components.

lib/simpleverifier.ts
// lib/simpleverifier.ts — server code only; never import this from a client component
export type EmailStatus = "valid" | "risky" | "invalid";

const API_URL = "https://simpleverifier.com/api/v1/verify";
const EMAIL_SHAPE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;

/** Resolves to the verdict, or null when the API could not answer. Never throws. */
export async function emailStatus(email: string): Promise<EmailStatus | null> {
  const address = email.trim();
  if (!EMAIL_SHAPE.test(address)) return "invalid";

  try {
    const res = await fetch(`${API_URL}?email=${encodeURIComponent(address)}`, {
      headers: { Authorization: `Bearer ${process.env.SIMPLEVERIFIER_KEY}` },
      signal: AbortSignal.timeout(30_000),
      cache: "no-store",
    });
    if (!res.ok) return null; // 401, 402, 429, 503: fail open
    const data = (await res.json()) as { status?: EmailStatus };
    return data.status ?? null;
  } catch {
    return null; // timeout or network error: fail open
  }
}

2. Check the address in a server action

The action runs on the server, so the key stays there.

app/signup/actions.ts
// app/signup/actions.ts
"use server";

import { emailStatus } from "@/lib/simpleverifier";

export interface SignupState {
  ok?: boolean;
  error?: string;
}

export async function signup(_prev: SignupState, formData: FormData): Promise<SignupState> {
  const email = String(formData.get("email") ?? "");
  const status = await emailStatus(email);

  if (status === "invalid") {
    return { error: "This email address cannot receive mail. Please check it." };
  }

  // Save the signup here. status is "valid", "risky" or null (API unavailable).
  return { ok: true };
}

3. Wire it to a form

useActionState (React 19) gives you the returned error and a pending flag for the button.

app/signup/SignupForm.tsx
// app/signup/SignupForm.tsx
"use client";

import { useActionState } from "react";
import { signup, type SignupState } from "./actions";

export default function SignupForm() {
  const [state, formAction, pending] = useActionState<SignupState, FormData>(signup, {});

  if (state.ok) return <p>Thanks, you're signed up.</p>;

  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required aria-describedby="email-error" />
      <button type="submit" disabled={pending}>
        {pending ? "Checking…" : "Sign up"}
      </button>
      <p id="email-error" role="alert">
        {state.error}
      </p>
    </form>
  );
}

4. Or expose a route handler

Useful when a client component wants to check the address as the visitor types (see the React form guide). Add rate limiting to it, since anyone can call a public route.

app/api/check-email/route.ts
// app/api/check-email/route.ts
import { NextResponse } from "next/server";
import { emailStatus } from "@/lib/simpleverifier";

export async function POST(request: Request) {
  const body = (await request.json().catch(() => ({}))) as { email?: unknown };
  const email = typeof body.email === "string" ? body.email : "";
  const status = await emailStatus(email);
  return NextResponse.json({ status });
}

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

Will Next.js cache the API response?
The helper passes cache: "no-store" so every call reaches the API. If you want to avoid re-checking the same address, cache the status in your own database keyed by the address.
Does this run on the Edge runtime?
Yes. The helper only uses fetch and AbortSignal.timeout, which both exist on the Edge runtime as well as Node.js.
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