Skip to content

Stopping free-trial abuse without punishing real users

· 7 min read · Disposable email

The way to stop free-trial abuse without hurting real users is to make a new identity expensive only at the moment your trial becomes expensive to you. Screen the email at signup (disposable domains and machine-generated addresses get rejected), key every trial to a normalized identity so the same person can't restart it, and release the costly parts of the trial in steps tied to stronger proof like a verified email or a card. Real users pass every step without noticing. Abusers run out of cheap identities.

Most advice on this topic jumps straight to "require a credit card". That works, and it also shrinks your top of funnel. You have more options than that.

Figure out what a trial costs you before you pick defenses

Trial abuse starts as a pricing question. Start by writing down what one trial actually costs:

  • Near zero. A project-management tool where a trial user creates a few boards. A serial re-trialer here is mostly an annoyance and a skewed conversion metric.
  • Real money per trial. Anything that hands out LLM tokens, GPU minutes, SMS, outbound email, or storage. Each fake trial is a line on your infrastructure bill, and abusers can script it.
  • Reputation cost. Products that send email or host content on behalf of users. Fake accounts get used to send spam from your domain or host phishing pages on your infrastructure, which can cost you deliverability for every customer.

If you're in the first bucket, light screening at signup is enough. If you're in the second or third, you need the step-by-step gating described below, because email screening alone only raises the cost of an identity, it doesn't cap what one identity can take.

The friction ladder

Each defense costs real users something. Order them by that cost and only climb as high as your per-trial cost justifies.

Step What it stops Cost to a real user
Reject disposable domains and randomized local parts Throwaway inboxes, scripted signups with gibberish addresses None for almost everyone
Dedupe on a normalized email Re-trials with jane+2@ or j.ane@gmail.com None
Email verification link Fake addresses, typos One click
Usage caps until verified Scripts that never open the inbox None if caps are generous
Card on file for expensive features Serial re-trialers, most farms High; some people leave
Manual or sales review Determined, high-value abuse High, but only for a few accounts

The first two rows are free for real users, so do them everywhere. The rest are a dial.

Screen the email at signup

Disposable inboxes are the cheapest identity on the internet: no account, no password, a new address in one click. They're also easy to recognize, since the domains are public. The community-maintained disposable-email-domains list covers several thousand of them, and our disposable domain category page shows the ones we track.

The second cheap identity is a machine-generated address at a real provider: xk7qmz2p@gmail.com, created in bulk by a script. A domain list can't catch these because the domain is legitimate. You need a check on the local part itself (we wrote up how that works in detecting machine-generated email addresses).

Here's a signup check using pingutil's fast mode, which skips company enrichment and returns the verdict in milliseconds:

type TrialDecision = 'full' | 'limited' | 'deny';

async function screenTrialEmail(email: string): Promise<{
  decision: TrialDecision;
  reason: string | null;
  category: string;
}> {
  const res = await fetch('https://pingutil.com/v1/lookup', {
    method: 'POST',
    headers: {
      authorization: `Bearer ${process.env.PINGUTIL_KEY}`,
      'content-type': 'application/json',
    },
    body: JSON.stringify({ input: email, skip_enrichment: true }),
  });

  // Fail open: a screening outage should not block every signup.
  if (!res.ok) return { decision: 'limited', reason: 'screening_unavailable', category: 'unknown' };

  const data = await res.json();

  if (data.verdict.is_spam) {
    return { decision: 'deny', reason: data.verdict.reason, category: data.category };
  }
  if (data.verdict.low_trust) {
    // role/system/sales addresses: allowed, but start them on the smaller trial
    return { decision: 'limited', reason: data.verdict.reason, category: data.category };
  }
  return { decision: 'full', reason: null, category: data.category };
}

Two choices in that code are deliberate. It fails open, because an outage in any third-party check should degrade your protection, not your signups. And low_trust addresses (things like info@ or sales@) get a smaller trial instead of a rejection. A shared inbox is a weak identity, but it's often a real small business.

Dedupe on a normalized identity

The classic re-trial is the same person with a slightly different address. Gmail ignores dots in the local part, and most providers deliver name+anything@ to name@. If your "has this person trialed before?" check compares raw strings, every variation is a fresh trial.

Store a normalized key alongside the raw email and check it before granting a trial:

function trialKey(email: string): string {
  const [rawLocal, rawDomain] = email.trim().toLowerCase().split('@');
  let local = rawLocal.split('+')[0];
  if (rawDomain === 'gmail.com') local = local.replace(/\./g, '');
  return `${local}@${rawDomain}`;
}

Only strip dots for Gmail. On most other providers j.smith and jsmith are different people. The rules per provider, and the edge cases, are in Gmail dots, plus addressing and duplicate accounts.

Use this key to detect, never to rewrite. Send mail to the address the user typed. And when a key matches a previous trial, don't throw an error page at them. Tell them they've already had a trial and offer to extend it by a few days if they talk to you. Some of those people are genuine returning evaluators, and that conversation is a sales lead.

Gate value, not entry

This is the part most teams skip, and it does the most work. Instead of deciding once at signup whether someone deserves a trial, give everyone a small trial immediately and release the expensive parts as they prove more:

  1. On signup, a screened email gets the product with tight caps: a handful of AI generations, a few hundred API calls, no outbound sending.
  2. After email verification, raise the caps to what an honest evaluator needs in a week.
  3. After a card is attached (not charged), open up the expensive resources: bulk sends, large exports, GPU jobs.

A real evaluator rarely hits step 1's caps before clicking the verification link. A script farming free credits hits them on every account and gets almost nothing per identity. You've moved the cost of abuse onto the abuser without asking anyone for a card up front.

If you bill with Stripe, you don't need the card at signup to run this model. Stripe supports trials without a payment method and lets you choose whether the subscription cancels, pauses or invoices when the trial ends with no card attached, via trial_settings.end_behavior.missing_payment_method. Their free trial documentation also warns that card-less trials can let spammers create fake customers, and suggests pairing them with account creation and a captcha. That's a fair warning, and it's exactly why caps belong in your product, not just in billing.

Don't punish the users you actually want

Abuse defenses fail quietly in the other direction too. A few patterns that turn away good users:

  • Blocking free webmail. Founders, freelancers and people evaluating a tool before pitching it internally use personal addresses all the time. Unless you're strictly B2B and sales-led, accept them. If you're weighing a work-email-only form, decide that as a sales question, not an abuse fix.
  • Blocking privacy relays. Apple Hide My Email, Firefox Relay and similar services produce random-looking addresses that forward to a real inbox. They're not disposable, and naive randomness checks flag them. How to handle them is covered in email relay services vs disposable email.
  • Generic rejection messages. "This email is not allowed" with no explanation reads as broken. Say what you need: "Please use a permanent email address so we can send your login link."
  • Rejecting on a single weak signal. A randomness heuristic will occasionally flag a real person, especially with non-English names. For borderline scores, prefer the limited trial over a hard block.

Measure each rule by what it costs you

Log the screening result with every trial: decision, reason, category. After a month you can answer the only question that matters for each rule, which is how many of the accounts it flagged would have paid. If accounts rejected as randomized_local_part never convert anywhere you can see, the rule is doing its job. If limited trials from role_based addresses convert as well as everyone else, loosen that rule.

You'll also spot the abuse you aren't catching. A spike of trials from one company domain might be a team evaluating together, which is good news, or one person cycling aliases on a domain they control. Spotting multiple signups from the same company shows how to tell those apart.

A short checklist

  • Write down your per-trial cost. Pick defenses that match it.
  • Reject disposable domains and randomized local parts at signup, failing open on errors.
  • Store a normalized trial key and check it before granting a trial.
  • Start everyone on capped usage, raise caps on verification, require a card for expensive features.
  • Allow free webmail and privacy relays unless you have a specific reason not to.
  • Log every decision and review conversion by reason monthly.

If you want to see what the signup check returns for a given address before you wire it in, paste one into the live demo.

Questions people ask

Does requiring a credit card stop free trial abuse?
It stops most casual abuse because a card is harder to replace than an email address, but it also cuts the number of people who start a trial at all. Many teams get a better trade by asking for a card only when the trial reaches something expensive.
How do I know if someone is creating multiple free trials?
Store a normalized form of every trial email (lowercased, plus tags removed, Gmail dots removed) and check new signups against it. Add the payment fingerprint, device or IP as secondary keys, and treat a match as a reason to shorten the trial rather than reject outright.
Should I block Gmail and other free email addresses from my trial?
Usually not. Plenty of real buyers evaluate tools from a personal address. Block disposable domains and machine-generated addresses, and save free-webmail restrictions for products that are strictly B2B.