Skip to main content

Block disposable emails in Next.js

Published Updated 27 minute readGuidesRichelo Killian

To block disposable emails in Next.js, run the check on the server, inside a Server Action or a Route Handler, before you create the account, and keep it fail-open so a slow or failed check never blocks a real signup. You can do this two ways: install the official isitdisposable JavaScript Software Development Kit (SDK), or call the detection Application Programming Interface (API) yourself with fetch. Either way, do the authoritative check server-side, never client-only, since anything in the browser can be bypassed, block on the recommended action, and let relay and alias addresses like Apple Hide My Email through, because they forward to real inboxes. The sections below give copy-pasteable TypeScript for both approaches, plus the App Router, the Pages Router, the fail-open wrapper, and client-side feedback.

I have spent more than fifteen years in email deliverability and shipped this exact check into production signup flows. The mechanics are simple and the mistakes are predictable. What follows is the version that works, with the reasoning behind each choice so you can adapt it to your own stack.

How do you block disposable emails in Next.js?

At a high level you add one server-side check to your signup path and wire the rest of your form around it. The check takes an email address, asks the detection service whether the domain is disposable, and returns a verdict your code acts on. Everything else is about putting that check in the right layer and making it safe to depend on.

Four pieces make up a complete implementation. A server-side entry point (a Server Action for App Router forms, or a Route Handler for client fetches and external callers) owns the decision. A small detection helper does the actual lookup, either through the official SDK or a raw call to the API, and returns a normalized result. A fail-open design guarantees that a timeout or outage results in "allow," not a broken form. And an optional client-side check gives the user instant feedback while they type, without ever being the thing that actually enforces the rule.

Layer Where it runs Its job Authoritative?
Server Action or Route Handler Server Call the detection check and enforce the block or allow decision Yes
Detection helper (checkEmail) Server Look up the address (SDK or raw fetch), normalize the result, fail open on error Yes, this is the check itself
Client component (on blur) Browser Give the user instant feedback before they submit No, feedback only
Middleware Edge, before the route Not the place for this check No, wrong layer

The single most important rule in that table is the "Authoritative?" column. The server is the only place a check can be trusted, because a determined user can skip your JavaScript entirely and post directly to your endpoint. A client-side check improves the experience, but it decides nothing. Get that division right and the rest is wiring.

Why block disposable email signups at all?

Blocking disposable signups is about protecting your account standing with your Email Service Provider (ESP) and keeping your data honest. It is not about protecting your reputation at Gmail, and that distinction changes how you should think about the whole feature.

Here is the mechanism. A disposable address works just long enough to confirm a signup, then its domain expires, so your next campaign to it hard bounces. A spike in hard bounces is one of the strongest list-quality signals your ESP watches. Mailchimp, SendGrid, Klaviyo, and the rest share sending infrastructure across many customers, so they police bounce and complaint rates aggressively, and a sudden spike can trigger a warning, a throttle, a forced list cleaning, or account suspension. That is the real cost of letting disposables pile up.

What it does not do is damage your sender reputation at Gmail, Outlook, or Yahoo. Those mailbox providers score you on how their own users treat your mail, and a dead throwaway domain is not one of those providers, so its bounce never reaches them. I cover the two-layer distinction in full in The Complete Guide to Disposable Email Detection. For this tutorial, hold on to the practical version: you are blocking disposables to stay in good standing with your ESP and to keep your metrics trustworthy, not to defend a Gmail score that was never at risk.

Where should the disposable email check live in a Next.js app?

The check belongs on the server, in whichever server-side primitive owns the request. In the App Router that means a Server Action for form submissions or a Route Handler for fetch-based and external callers. It does not belong in the browser as the sole gate, and it does not belong in middleware.

Placement Best for Bypassable? Use as the authority?
Server Action App Router forms submitting to your own app No, runs server-side Yes
Route Handler (app/api) Client fetch, mobile clients, external callers No, runs server-side Yes
Client component Instant inline feedback while typing Yes, runs in the browser No, pair it with a server check
Middleware Nothing here Not applicable No

Middleware deserves a direct explanation, because it looks tempting and is the wrong tool. Middleware runs before the route on every request that matches its config, so putting an email check there adds latency to requests that have nothing to do with signup. It also runs before your route handler parses the request body, so getting at the submitted email field is awkward, and it runs in a constrained runtime that is not built for this. The right home for the check is inside the Server Action or Route Handler that already owns account creation, where you have the parsed input and full context to make the decision.

The rest of this guide builds the App Router path first, since it is the modern default, then covers the Pages Router in its own section so the piece serves both.

How do you block disposable emails with a Next.js Server Action?

The App Router path has four files: a detection helper, a Server Action that calls it, a client form component, and your environment configuration. Start with the configuration and the helper, because everything else depends on them.

Store your API key as an environment variable, never in source. The isitdisposable API uses two kinds of key: sk_ secret keys for server-side use, and pk_ publishable keys that are safe in the browser and restricted to origins you approve. This check runs on the server, so it uses a secret key. Create a .env.local file, which Next.js keeps out of your bundle and Git ignores by default:

## .env.local
ISITDISPOSABLE_API_KEY=sk_live_your_secret_key

Now the detection helper. You have two ways to write it, and they are interchangeable: both return the same small EmailCheck shape, so every other file in this guide works without change regardless of which you pick. The recommended path is the official SDK, which handles timeouts, retries, and fail-open behavior for you.

// lib/check-email.ts  (using the official SDK)
import "server-only";
import { IsItDisposable } from "isitdisposable";

export type EmailCheck = {
  checked: boolean; // false means the lookup did not run, so treat as allow
  disposable: boolean; // the core verdict
  relay: boolean; // forwarding or alias provider, allow these
  action: "allow" | "warn" | "block"; // recommended action from your policy
};

// One client for the whole app. With no argument it reads ISITDISPOSABLE_API_KEY.
const client = new IsItDisposable();

export async function checkEmail(email: string): Promise<EmailCheck> {
  // failOpen is true by default: on a timeout, dropped connection, rate limit,
  // or server error, the client resolves to { checked: false, action: "allow" }
  // and logs a warning instead of throwing. A missing or revoked key still
  // throws, because that is a setup bug you want to see in development.
  const r = await client.check({ email });
  return {
    checked: r.checked,
    disposable: r.disposable === true, // null (not checked) becomes false
    relay: r.relay === true,
    action: r.action,
  };
}

Install it with npm install isitdisposable. It has zero runtime dependencies and runs on Node.js 18 and later plus edge runtimes.

If you would rather not add a dependency, here is the exact same helper written against the raw API with fetch. It calls POST https://api.isitdisposable.com/v1/check with a bearer token and normalizes the response into the identical shape.

// lib/check-email.ts  (rolling it yourself, no SDK)
import "server-only";

export type EmailCheck = {
  checked: boolean;
  disposable: boolean;
  relay: boolean;
  action: "allow" | "warn" | "block";
};

// Fail open: on any failure we allow the signup and move on.
const ALLOW_ON_FAILURE: EmailCheck = {
  checked: false,
  disposable: false,
  relay: false,
  action: "allow",
};

export async function checkEmail(email: string): Promise<EmailCheck> {
  try {
    const res = await fetch("https://api.isitdisposable.com/v1/check", {
      method: "POST",
      headers: {
        "content-type": "application/json",
        Authorization: `Bearer ${process.env.ISITDISPOSABLE_API_KEY}`,
      },
      body: JSON.stringify({ email }),
      signal: AbortSignal.timeout(2000), // fail open: cap the wait at 2 seconds
      cache: "no-store",
    });

    if (!res.ok) {
      // A 401 (bad key) or 400 (bad input) lands here. Log it, never block.
      console.warn("checkEmail non-OK response, allowing:", res.status);
      return ALLOW_ON_FAILURE;
    }

    const data = await res.json();
    // The API itself fails open: an over-quota or inactive account returns
    // HTTP 200 with checked:false and action:"allow", which maps straight through.
    return {
      checked: data.checked === true,
      disposable: data.disposable === true, // null when not checked, so false
      relay: data.relay === true,
      action: data.action ?? "allow",
    };
  } catch (err) {
    // Timeout, network error, or bad JSON: allow and move on.
    console.warn("checkEmail failed, allowing:", err);
    return ALLOW_ON_FAILURE;
  }
}

A few details in the raw version are worth calling out. The import "server-only" line (from the server-only package, installed with npm install server-only) makes the build fail if this file is ever imported into client code, which guarantees your secret key cannot leak into the browser bundle. The AbortSignal.timeout(2000) caps how long you will wait, so a slow response cannot stall your form; on older runtimes without it, use an AbortController with a setTimeout. And cache: "no-store" stops Next.js from caching the result across requests.

Next, the Server Action. It runs on the server, validates syntax cheaply first, calls the helper, and blocks on the recommended action.

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

import { checkEmail } from "@/lib/check-email";

export type SignupState = {
  ok: boolean;
  error?: string;
};

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

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

  // 1. Cheap syntax check first. No need to call the API on obvious junk.
  if (!EMAIL_RE.test(email)) {
    return { ok: false, error: "Enter a valid email address." };
  }

  // 2. Server-side authoritative check.
  const check = await checkEmail(email);

  // 3. Honor the recommended action from your dashboard policy. By default,
  //    disposable maps to block and relay or alias addresses map to allow.
  //    On a fail-open result the action is "allow", so nothing is blocked.
  if (check.action === "block") {
    return {
      ok: false,
      error: "Please sign up with a non-disposable email address.",
    };
  }

  // 4. "warn" means allow but you may want to flag it for review.
  // if (check.action === "warn") { /* tagSignup(email) */ }

  // 5. Create the account.
  // await createUser(email);

  return { ok: true };
}

If you would rather decide policy in your own code instead of the dashboard, block on check.disposable directly. Just remember to let relays through explicitly in that case, since check.relay is a separate signal. Honoring action gets that right for you, which is the next section's point.

Finally, the form. It is a Client Component that uses useActionState to call the action and render its result, with a pending state for the button.

// app/signup-form.tsx
"use client";

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

const initialState: SignupState = { ok: false };

export function SignupForm() {
  const [state, formAction, isPending] = useActionState(signup, initialState);

  return (
    <form action={formAction}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />

      <button type="submit" disabled={isPending}>
        {isPending ? "Checking..." : "Sign up"}
      </button>

      {state.error ? <p role="alert">{state.error}</p> : null}
      {state.ok ? <p>You are in. Check your inbox.</p> : null}
    </form>
  );
}

That is a complete, working block on the App Router. One version note: useActionState is the React 19 and Next.js 15 name. On React 18 and Next.js 14 the hook was called useFormState and imported from react-dom, and it returned [state, formAction] without the pending flag, so you used useFormStatus for the button state. The action signature and the rest of the flow are identical.

Should you use the isitdisposable SDK or call the API yourself?

Use the SDK unless you have a specific reason not to. It is the fastest correct path: it reads your key from the environment, applies sensible timeouts, retries once, and fails open by default, so the common mistakes are handled before you write a line. Roll your own only when you are avoiding dependencies or need full control over the wire details.

Consideration Official SDK (isitdisposable) Roll it yourself (fetch)
Setup npm install isitdisposable, construct a client No dependency, write the fetch and the parsing
Fail-open Built in (failOpen: true): resolves to allow on network trouble, throws on misconfiguration You implement the timeout, the allow-on-failure, and the logging
Types Fully typed results You type the response yourself
Batch checkBatch([...]) helper for up to 100 You call /v1/check/batch and parse it
Runtime Node.js 18 and later plus edge, zero runtime dependencies Anywhere fetch runs
Control Sensible defaults, less code Full control over every request detail

Both are shown in full above, and both return the same EmailCheck shape, so this is a local decision inside one file. It does not affect the Server Action, the Route Handler, or anything else in this guide. The isitdisposable SDK is one of the official clients released alongside a Python SDK and a Model Context Protocol (MCP) server, covered in the announcement of the official SDKs and MCP server. The full request and response reference lives at the isitdisposable docs.

How do you validate emails in a Next.js Route Handler?

A Route Handler is the right entry point when the caller is not one of your own App Router forms: a client-side fetch, a mobile app, a separate frontend, or a third-party integration. It reuses the same checkEmail helper and returns the classification as JavaScript Object Notation (JSON), letting the caller decide what to do.

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

export async function POST(request: Request) {
  const body = await request.json().catch(() => ({}));
  const email = String(body.email ?? "").trim().toLowerCase();

  if (!email.includes("@")) {
    return NextResponse.json({ error: "Invalid email" }, { status: 400 });
  }

  const check = await checkEmail(email);

  return NextResponse.json({
    disposable: check.disposable,
    relay: check.relay,
    action: check.action,
    allow: check.action !== "block", // relays and warns are allowed
  });
}

So which do you use? If a user submits your own signup form in the App Router, prefer the Server Action: it works without client-side JavaScript through progressive enhancement, and it keeps the decision next to account creation. Reach for the Route Handler when something other than that form needs a verdict, or when you specifically want a fetch-based endpoint. Notice the allow field folds the policy in: it is true unless the recommended action is "block," so relay and alias addresses come back allowed by default.

How do you add client-side disposable email feedback in Next.js?

Client-side feedback is a nicety layered on top of the server check, never a replacement for it. The pattern is to check the address on blur, when the user leaves the email field, and show a gentle hint if it looks disposable. Because the detection service works at the Domain Name System (DNS) level with no mailbox probing, it is fast and safe to call on interaction like this.

The important detail is that the browser calls your own Route Handler, not the API with your secret key. That keeps your sk_ key on the server, where it belongs, and out of every visitor's browser.

// app/email-field.tsx
"use client";

import { useState } from "react";

export function EmailField() {
  const [hint, setHint] = useState<string | null>(null);

  async function onBlur(e: React.FocusEvent<HTMLInputElement>) {
    const email = e.target.value.trim().toLowerCase();
    if (!email.includes("@")) return;

    try {
      const res = await fetch("/api/validate-email", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email }),
      });
      const data = await res.json();
      // UX hint only. The Server Action on submit is still the real gate.
      setHint(data.disposable ? "That looks like a disposable address." : null);
    } catch {
      setHint(null); // never block typing because a hint request failed
    }
  }

  return (
    <div>
      <input name="email" type="email" onBlur={onBlur} required />
      {hint ? <p role="status">{hint}</p> : null}
    </div>
  );
}

There is a lower-effort option for inline enforcement that does not touch your React at all: the official snippet. You drop one script tag with a pk_ publishable key onto the page and it watches your email fields and enforces a mode you choose, with no build step. Publishable keys are safe in the browser because they are restricted to origins you approve, so a lifted key only works from your own sites. See the snippet guide in the docs. Whichever you use for feedback, remember the rule: the client-side piece exists to save honest users a round trip of frustration, and your Server Action still runs the authoritative check on submit.

How do you make the check fail-open in Next.js?

Fail-open means that if the detection check is slow, unreachable, or errors, the signup still succeeds. This is the rule that keeps a third-party dependency from ever costing you a real customer, and it is worth treating as non-negotiable. You never want a detection outage to become a signup outage. In this stack it holds at three layers.

  • Your wrapper. In the raw helper, every failure path (a non-OK response, a network error, a timeout, or malformed JSON) returns the permissive ALLOW_ON_FAILURE result, and each is logged. Because the recommended action in that result is "allow," your Server Action's block condition is simply skipped and the user proceeds.
  • The SDK. If you use the official client, this is already done for you. With failOpen true (the default) it resolves to a safe checked: false, action: "allow" result on a timeout, dropped connection, rate limit, or server error, and calls its onWarning handler, which logs to the console by default. Genuine setup mistakes, like a missing or revoked key, still throw, so you catch them in development rather than shipping them.
  • The platform. The API fails open on its own side too. If your account is over quota or the subscription is inactive, it still returns HTTP 200 with checked: false and action: "allow" rather than an error, and it sheds load the same safe way when a region is saturated. So even a billing lapse degrades into "allow," not a broken form.
// The failure contract, restated. Any trouble becomes an allow.
const ALLOW_ON_FAILURE: EmailCheck = {
  checked: false,
  disposable: false,
  relay: false,
  action: "allow",
};

When every layer fails open, a problem anywhere in the chain degrades quietly into "allow" instead of a broken form. That is the difference between a check you can safely put in front of every signup and one you cannot. Set a tight timeout, treat any failure as allow, and log it so you can see when the check is degrading without the user ever feeling it.

How should you handle relay and alias addresses in Next.js?

You should allow them. Relay and alias addresses are not disposable, and blocking them means turning away real, reachable, often paying customers. This is the most common way a well-intentioned disposable filter backfires.

A relay address forwards to a real, permanent inbox the person controls. Apple Hide My Email, Firefox Relay, SimpleLogin, addy.io, DuckDuckGo Email Protection, and Proton aliases all put a privacy layer in front of a mailbox someone actually reads. These users tend to be exactly the kind you want: Apple Hide My Email users are iCloud Plus subscribers, and Proton and SimpleLogin users are privacy-conscious people who frequently pay for software. Their addresses are deliverable and durable, so they do not expire and spike your bounces the way disposables do.

The code above already handles this correctly, and this is the payoff for blocking on action rather than on a raw signal. The detection service returns relay as its own field, separate from disposable, and the default enforcement policy maps relay to "allow." So when your Server Action blocks only on action === "block", relay addresses pass through untouched with no extra work. If you instead decide policy in code by blocking on check.disposable, you take on the job of remembering to let relays through, because they are a distinct signal. Either approach works, but honoring action is the one that is correct by default. A detection source that collapses relays into a single "bad address" flag would force you to choose between blocking real customers and letting throwaways through; keeping them separate avoids the tradeoff entirely.

Static list or detection API in Next.js?

Your checkEmail helper can be backed two ways at the data level: a static list of disposable domains bundled into your repository, or a live detection API (whether you reach it through the SDK or a raw call). The code around it, the Server Action, the Route Handler, the fail-open contract, does not change. Only where the verdict comes from does.

Consideration Static list in your repo Detection API
Freshness Frozen at your last update Refreshed daily on the provider side
MX validation None, it is a text match on the domain Included, catches new domains on known infrastructure
Relay classification You build and maintain it yourself Returned as a separate relay signal
Maintenance Yours, indefinitely The provider's
Failure mode Local, cannot go down, but never gets fresher Fails open (HTTP 200 with checked: false), so signups keep working

A static list is a reasonable choice for a hobby project, an offline cleanup, or an environment that cannot call an external service. Its weakness is that it is frozen the moment you commit it, and it gives you neither Mail Exchanger (MX) validation nor relay classification without building both yourself. A live API trades that maintenance for a network call, and returns fresher coverage plus the relay signal your relay handling depends on. I compare the two approaches in depth, including when the free list is genuinely the right call, in Free disposable email lists vs a live detection API. For a production signup form, the API is usually the better fit, which is why the helper in this guide is written against one.

How do you block disposable emails in the Pages Router?

The Pages Router has no Server Actions, so the server-side authority lives in an API route that your form calls on submit. The same checkEmail helper is reused unchanged, since it is just a server-side function, and it does not care whether you backed it with the SDK or a raw fetch.

A note on placement, because it trips people up: getServerSideProps is not the tool for this. It runs when the page is rendered, before the user has typed anything, so it cannot validate a submitted address. The submit-time authority in the Pages Router is an API route.

// pages/api/validate-email.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { checkEmail } from "@/lib/check-email";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== "POST") {
    return res.status(405).json({ error: "Method not allowed" });
  }

  const email = String(req.body?.email ?? "").trim().toLowerCase();
  if (!email.includes("@")) {
    return res.status(400).json({ error: "Invalid email" });
  }

  const check = await checkEmail(email);
  return res.status(200).json({
    disposable: check.disposable,
    relay: check.relay,
    action: check.action,
    allow: check.action !== "block",
  });
}

The form is a client component that posts to that route on submit and blocks when the recommended action is "block":

// pages/signup.tsx
import { useState } from "react";

export default function Signup() {
  const [error, setError] = useState<string | null>(null);

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setError(null);

    const form = new FormData(e.currentTarget);
    const email = String(form.get("email") ?? "").trim().toLowerCase();

    const res = await fetch("/api/validate-email", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email }),
    });
    const data = await res.json();

    if (!data.allow) {
      setError("Please sign up with a non-disposable email address.");
      return;
    }

    // Allowed, including relays. Proceed to create the account.
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <button type="submit">Sign up</button>
      {error ? <p role="alert">{error}</p> : null}
    </form>
  );
}

The shape is the same as the App Router version: a cheap syntax check, a server-side call to the shared helper, block on the recommended action, allow relays. The only real difference is that account creation happens after the fetch resolves in your submit handler rather than inside a Server Action.

What are common mistakes when blocking disposable emails in Next.js?

Most broken implementations fail in one of a handful of predictable ways. Each has a one-line fix, and every fix appears in the code above.

Mistake Why it bites Fix
Client-only validation Anyone can skip the browser and post directly to your endpoint Make the server the authority, treat client checks as UX only
Blocking relay addresses You turn away real, often paying customers Block on action, which allows relays by default, or allow relay explicitly
No fail-open A slow or down check blocks real signups Use the SDK's default, or wrap your fetch in a timeout and allow on failure
Hardcoded API key It leaks through source control and build artifacts Load it from an environment variable, server-side only
A secret key in the browser An sk_ key shipped to the client can be lifted and abused Keep sk_ server-side; for the browser use a pk_ publishable key or the snippet
NEXT_PUBLIC_ on the secret key The NEXT_PUBLIC_ prefix inlines the value into every browser bundle Name it ISITDISPOSABLE_API_KEY with no public prefix, read it only in server code
Putting the check in middleware Wrong layer, adds latency, lacks the parsed body Check inside the Server Action or Route Handler

The key-handling mistakes are worth dwelling on, because they are specific to how Next.js and this API work together. The API gives you two key types on purpose: sk_ secret keys for the server and pk_ publishable keys, origin-restricted, for the browser. Any environment variable prefixed with NEXT_PUBLIC_ is inlined into the client bundle and shipped to every visitor, so a secret key with that prefix is published to the world. Keep your sk_ key as ISITDISPOSABLE_API_KEY with no public prefix, read it only in server code (the helper, the Route Handler, the Pages API route), and when you need something in the browser, reach for a publishable key or the drop-in snippet instead.

The rest of the mistakes reduce to a single principle: the server decides, the client informs, and the check must never be able to break the form. Hold to that and a disposable email block in Next.js is a small, reliable piece of your signup flow.

What should you use to block disposable emails in Next.js?

Use a server-side check backed by a detection source that is fresh, validates Mail Exchanger records rather than matching a static file, and classifies relay and alias services separately so you can allow them. Wire it into a Server Action or Route Handler, keep it fail-open, and add optional client-side feedback for polish. Those are the requirements the code in this guide is built to satisfy, whether you use the SDK or call the API yourself.

If you want a source that meets them out of the box: isitdisposable.com maintains a live dataset of more than 205,000 domains drawn from six upstream sources, validated against live MX records and refreshed daily, with DNS-level detection and no mailbox probing. You can integrate it three ways: the official JavaScript SDK shown above (npm install isitdisposable), a single API call with fetch, or a drop-in snippet for your forms. It returns relay as a signal separate from disposable and advises allowing relays, and it fails open by design, returning a safe "allow" rather than an error when it cannot respond, so your signups keep working. The full field reference is at the docs, and there is a free plan with 250 lookups per month plus a 14-day full-access trial with no credit card, which is enough to wire it into a Next.js signup flow and test it against your own traffic.

To recap the whole approach: put the authoritative check on the server, before account creation, and make it fail open so it can never break a signup. Block on the recommended action, and let relay and alias addresses through, since they forward to real inboxes. Add a client-side hint on blur if you want the polish, but remember it enforces nothing. Do that, with the SDK or a few lines of fetch, and you have a disposable email block in Next.js that protects your ESP standing and your data without ever getting in a real customer's way.

About the author

Richelo Killian

Founder

Founder of isitdisposable.com and the SenderWorx email tool suite. Builds email infrastructure and anti-abuse tooling.