Free trial abuse: the email signals that catch it, and where they stop
Stop repeat free-trial sign-ups with email signals: disposable and relay inboxes, +tag and Gmail-dot variants, typo domains, new domains and a blocklist.
Free trial abuse is one person starting your trial again and again under a new email address each time. The address is where most of it shows first: throwaway inboxes, privacy relays, +tag and Gmail-dot variants of the same inbox, typo and lookalike domains, and company-looking domains registered last week. Block only what you’re sure of, de-duplicate the rest, and send uncertain sign-ups to a review lane, because every false positive is a real customer you turned away.
Why abuse starts with the email address
If your trial includes something that costs you money, such as AI credits, compute, SMS, seats or data exports, a new trial is worth a new identity. The cheapest new identity is a new email address, and there are several ways to get one, each a little more expensive than the last:
| Trick | Effort for the abuser | Example |
|---|---|---|
+tag on the same inbox |
None | jane+2@gmail.com |
| Dots in a Gmail address | None | j.ane@gmail.com |
| Disposable inbox | Seconds | x7@temp-inbox.example |
| Relay alias | Seconds, with an account | k2@privaterelay.appleid.com |
| New webmail account | Minutes, sometimes a phone number | jane.trial7@outlook.com |
| New domain with a catch-all | A few dollars | jane@acme-trial.io |
The goal isn’t to make abuse impossible. It’s to make each extra trial cost more than it’s worth, while a genuine sign-up costs nothing extra. The free trial abuse playbook has the short version; this post goes through each signal, what it catches and where it stops.
The signals, one by one
Disposable inboxes: always block
Throwaway inbox services hand out working addresses in seconds and forget them within hours. isBusinessEmail returns category: "disposable" and is_disposable: true, with a reason that says how it knew: disposable_provider for a list match, mx_shared_disposable when the domain’s mail servers also serve known disposable domains, or disposable_name when the name advertises it and nothing suggests a real organization. Every policy blocks them.
This is the one signal to block without a second thought. A disposable inbox won’t receive your onboarding email next week, so even a genuine person behind it isn’t someone you can reach. Why lists alone miss some of them is in the disposable email problem, and the disposable email checker checks a single domain.
Relays: block for B2B, decide for consumer products
Apple Hide My Email, Firefox Relay, DuckDuckGo, SimpleLogin and addy.io forward to a real, persistent inbox. The person is reachable, so a relay isn’t disposable. But one account can create many aliases, so you can’t de-duplicate by address, and the alias hides which company the person works for. is_relay is true; the b2b and strict policies block, lenient allows.
For a B2B trial, blocking relays is reasonable. On a consumer product, privacy-minded people use them on purpose, and Sign in with Apple can hand them out by design. Allow them there, with limits, and lean on the controls further down. Email relay addresses covers the trade-off.
Personal providers: a policy choice, not an abuse signal
A gmail.com address isn’t abuse. Most consumers and many buyers sign up that way. is_free_provider tells you the domain is shared; what you do about it is a product decision. If your product needs a company account, require a work email, as should B2B SaaS block free email signups? discusses. If it doesn’t, allow personal addresses and de-duplicate them properly.
Requiring a work email has a side effect worth planning for: it pushes abusers toward cheap new domains, which is why the domain-age signal below matters more once you do.
+tags and Gmail dots: one inbox, many addresses
jane+1@gmail.com, jane+trial@gmail.com and jane@gmail.com all land in the same inbox. isBusinessEmail flags the tag with has_subaddress and the reason subaddress, and returns normalized_email without it: jane@gmail.com.
Gmail also ignores dots in the name, so j.ane@gmail.com and ja.ne@googlemail.com reach that inbox too. normalized_email removes +tags but doesn’t fold dots, because that rule belongs to one provider. Fold them yourself, and only for gmail.com and googlemail.com: Google Workspace on a company domain and most other providers treat j.ane and jane as different people.
const GMAIL = new Set(['gmail.com', 'googlemail.com']);
// One key per inbox: the API already dropped the +tag; fold Gmail dots here.
function trialKey(v) {
if (!v.normalized_email) return null;
const at = v.normalized_email.lastIndexOf('@');
const local = v.normalized_email.slice(0, at);
const domain = v.normalized_email.slice(at + 1);
return GMAIL.has(domain) ? `${local.replaceAll('.', '')}@gmail.com` : v.normalized_email;
}
Store the key in its own column with a unique index, and allow one trial per key. Don’t reject +tags themselves: many careful people use them to filter mail, and a tag only matters when the address without it already has a trial. Plus addressing and sign-up dedupe goes deeper.
Typo and lookalike domains
jane@gmial.com is usually an honest mistake. did_you_mean returns gmail.com with the reason typo_suspected; show it as a one-click fix and check again, as email typo domains describes. Some typo domains are themselves run as throwaway inbox services; those come back disposable with did_you_mean still filled.
Lookalikes are more often deliberate: a Cyrillic о in place of a Latin o, or a 0 for an o. is_lookalike is true with the reason lookalike_domain, and under b2b such an address never gets a plain allow. Detection compares the domain with popular email providers, not with your brand, so check imitations of your own domain yourself. See lookalike and punycode domains.
Brand-new domains
Once personal and disposable addresses are out, the cheapest workaround is a domain: acme-trial.io for a few dollars, plus a catch-all so every address on it works. With deep=true, isBusinessEmail looks up the registration date over RDAP and checks the homepage, returning domain_age_days, is_new_domain (under 30 days) and reasons such as new_domain, young_domain and parked_domain. A deep check counts as 5 checks, with at most 1,000 a day, so run it at trial start and only for custom domains.
New isn’t proof: real startups sign up the week they register. Review new domains rather than blocking them, and watch for several trials from the same new domain, which is one organization whatever comes before the @. Domain age as a fraud signal covers this in detail.
The blocklist
is_blocked is true when the domain, or for secret keys the exact address, is on the isBusinessEmail blocklist, which our admins maintain after reviewing spam and abuse reports. The reasons are blocked_domain and blocked_email, and every policy blocks. Exact addresses are stored only as hashes. If abuse keeps coming from a domain that should be listed, report it.
Unknown
category: "unknown" means a custom domain with too little evidence to call: very new, parked, no SPF or DMARC, forwarding-only mail, or a name that suggests a mailbox service. b2b returns review, strict blocks and lenient allows (categories and policies). For trials, treat it as review.
What email signals can’t catch
Email checks stop the cheap, repeatable abuse. They don’t stop someone with an established company domain or a stack of genuine webmail accounts, because every one of those addresses is real. isBusinessEmail also never checks whether a mailbox exists, so send a confirmation email before the trial starts; it proves the inbox works, though a disposable inbox passes it too.
Pair the email check with controls that don’t depend on the address:
- Rate limits on sign-ups per IP address, device and network, and on the expensive actions themselves.
- Card verification before the costly part of the trial, with a check that the same card hasn’t already started one.
- Usage caps per trial on credits, exports, messages or seats, so the worst case of one abusive trial stays small.
- Behavior after sign-up, such as several new accounts with the same onboarding answers or the same usage pattern.
A policy: signal to action
| Signal | Field | Action |
|---|---|---|
| On the blocklist | is_blocked |
block |
| Typo of a provider | did_you_mean |
ask to confirm the fix, then check again |
| Disposable inbox | is_disposable |
block |
| Can’t receive mail | category: "invalid" |
block, with a “check for typos” message |
| Same inbox as an existing trial | your trial key | no second trial; offer to sign in |
| Relay | is_relay |
block for B2B; allow with limits for consumer products |
| Lookalike of a provider | is_lookalike |
review |
| New domain (under 30 days) | is_new_domain |
review: card or manual approval |
| Parked homepage | reason parked_domain |
review |
| Unknown | category: "unknown" |
review |
| Personal provider | is_free_provider |
your policy: allow with limits, or block on work-email products |
+tag on a first trial |
has_subaddress |
allow |
| Established business domain | category: "business" |
allow |
Review should mean “let them in, hold back the expensive part”: a confirmed email, a card on file, a quick manual look, or a few days of normal usage. Most reviewed sign-ups are genuine, so keep the step light.
Putting it together in code
check() below is a thin wrapper around POST /v1/check with a timeout that returns null on any failure; the code examples have one in several languages. The typo check comes before the disposable and invalid blocks on purpose: someone who typed gmial.com should see the fix, not a rejection.
async function screenTrial(email, { b2b }) {
let v = await check(email);
if (!v) return { lane: 'allow', recheck: true }; // fail open, re-check later
if (['business', 'unknown'].includes(v.category)) v = (await check(email, { deep: true })) ?? v;
if (v.is_blocked) return { lane: 'block' };
if (v.did_you_mean) return { lane: 'confirm', suggestion: v.did_you_mean };
if (v.is_disposable || v.category === 'invalid') return { lane: 'block' };
const key = trialKey(v);
if (key && (await trials.exists(key))) return { lane: 'existing_account' };
const codes = v.reasons.map((r) => r.split(':')[0]);
if (v.is_lookalike || v.is_new_domain || v.category === 'unknown' || codes.includes('parked_domain')) {
return { lane: 'review', key };
}
if (v.is_relay || v.is_free_provider) return { lane: b2b ? 'block' : 'limited', key };
return { lane: 'allow', key };
}
Store the lane and reasons on every trial. After a few weeks, count how many reviewed sign-ups you approved and how each lane converted to paid. If nearly every reviewed account turns out fine, the lane is too wide; narrow one rule at a time.
Next steps
- Block disposable and blocklisted addresses first; each is a single field.
- Add the trial key with Gmail dot folding and a unique index.
- Route relays, lookalikes, unknown and new domains to review, and cap what a trial can use.
- Test every lane with the test addresses, such as
disposable@test.isbusinessemail.comandunknown@test.isbusinessemail.com, then get a free API key.
Frequently asked questions
How do I stop people from signing up for multiple free trials?
Build a key for every trial from the email address with +tags removed and, for gmail.com and googlemail.com only, dots removed, and allow one trial per key. Block disposable inboxes, review relays and brand-new domains, and add rate limits, card checks and usage caps for the expensive features.
Should I block Gmail addresses to stop trial abuse?
Usually not. Most people who sign up with Gmail are genuine, and blocking them costs you real customers. De-duplicate Gmail addresses properly and cap what a trial can use instead. Require a work email only if your product needs a company account anyway.
Does Gmail really ignore dots in addresses?
Yes. For gmail.com and googlemail.com, j.ane@gmail.com and jane@gmail.com reach the same inbox. Google Workspace accounts on a company domain and most other providers treat dots as part of the name, so fold dots only for those two domains.
Are plus addresses a sign of fraud?
No. Many careful people use a +tag to filter mail by sender. A tag only matters when the same address without it already has a trial, so de-duplicate on the normalized address instead of rejecting tags.
Can email checks stop all free trial abuse?
No. Someone with an established company domain or a stack of genuine webmail accounts passes any email check, because every one of those addresses is real. Email signals stop the cheap, repeatable abuse; rate limits, card verification and usage caps handle the rest.
What should happen to sign-ups that land in review?
Let them start, but hold back what is expensive until something confirms they are real: a confirmed email, a card on file, a quick manual approval or a few days of normal usage. Most reviewed sign-ups are genuine, so keep the step light.