If your SaaS or AI application grants free credits, API tokens, or a 14-day free trial upon registration, blocking disposable email domains is only half the battle. Savvy free-trial abusers bypass basic domain blacklists using a built-in feature of major mailbox providers: Gmail Plus-Addressing (+tag) and the Gmail Dot-Trick.
Because Gmail treats [email protected], [email protected], [email protected], and [email protected] as the exact same physical inbox—while naive SQL databases treat them as four distinct strings—a single user can script thousands of free-trial accounts that all deliver OTP verification codes to one real Gmail account.
In this engineering guide, we break down provider-specific alias rules across Gmail, ProtonMail, Outlook, iCloud, Fastmail, and Yahoo, and show how to enforce 1 Free Trial Per Canonical Inbox using MailVeri’s Real-Time Verification API and PostgreSQL unique indexes.
1. Why Naive Regex Normalization Breaks Legitimate User Signups
When developers first discover the Gmail dot-trick, a common mistake is writing a global regex that strips all dots (.) and plus tags (+...) from every email domain in their database. Doing this globally is a critical bug because email providers follow different RFC local-part rules:
| Mailbox Provider | Are Dots (.) Ignored? | Sub-Addressing Delimiter | Domain Alias Equivalent |
|---|---|---|---|
Gmail (gmail.com / googlemail.com) | Yes (Dots stripped) | + ([email protected]) | googlemail.com → gmail.com |
ProtonMail (proton.me / protonmail.com) | Yes (Dots stripped) | + ([email protected]) | pm.me / protonmail.ch |
| Microsoft Outlook / Hotmail / Live | No! (john.doe != johndoe) | + ([email protected]) | Distinct per TLD |
| Apple iCloud & Fastmail | No! (Dots are significant) | + ([email protected]) | me.com / mac.com |
| Yahoo Mail & Custom Corporate Domains | No! (Never strip dots) | Do not strip + on Yahoo/custom | Preserve exact local-part |
As detailed in our Free Email Providers Normalization Guide, stripping dots from [email protected] or a corporate Microsoft 365 domain corrupts the address into a completely different (or non-existent) mailbox!
2. Using MailVeri’s Canonical mail Field + Disposable Shield
Every response from MailVeri’s POST https://api.mailveri.com/v1/verify-mail/ endpoint returns both:
status: Verifies live mailbox deliverability and flags 100,000+ disposable email providers asDISPOSABLE.mail: The provider-aware canonical email address—stripping dots and+tagsfor Gmail/ProtonMail while preserving significant dots on Outlook, iCloud, Fastmail, Yahoo, and custom domains.
1// lib/signup-canonical-guard.ts2// Stop Gmail +tag ([email protected]) and dot-trick ([email protected]) multi-account abuse3export interface CanonicalVerification {4 allowed: boolean;5 rawEmail: string;6 canonicalEmail: string;7 status: string;8 reason?: string;9}10 11export async function verifyAndNormalizeSignupEmail(12 rawEmail: string13): Promise<CanonicalVerification> {14 const res = await fetch('https://api.mailveri.com/v1/verify-mail/', {15 method: 'POST',16 headers: {17 'Authorization': `Bearer ${process.env.MAILVERI_API_KEY}`,18 'Content-Type': 'application/json',19 },20 body: JSON.stringify({ email: rawEmail }),21 cache: 'no-store',22 });23 24 const data = await res.json();25 const status: string = data.status || 'UNKNOWN';26 // data.mail is provider-normalized (e.g., "[email protected]" -> "[email protected]")27 const canonicalEmail: string = (data.mail || rawEmail).toLowerCase().trim();28 29 if (status === 'DISPOSABLE') {30 return {31 allowed: false,32 rawEmail,33 canonicalEmail,34 status,35 reason: 'Disposable and temporary email domains are not eligible for free trial credits.',36 };37 }38 39 if (status.startsWith('INVALID') || status === 'FULL_INBOX') {40 return {41 allowed: false,42 rawEmail,43 canonicalEmail,44 status,45 reason: 'Please enter a valid, active email address.',46 };47 }48 49 return {50 allowed: true,51 rawEmail: rawEmail.trim(),52 canonicalEmail,53 status,54 };55}3. Enforcing Uniqueness at the PostgreSQL Database Layer
Always store both raw_email (where you send transactional emails so the user’s own inbox filter rules still work) and canonical_email (with a UNIQUE index so the same human cannot claim multiple free trials):
1-- Enforce 1 free trial per real human inbox in PostgreSQL2CREATE TABLE users (3 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),4 raw_email TEXT NOT NULL,5 canonical_email TEXT NOT NULL,6 mailveri_status VARCHAR(32) NOT NULL,7 trial_credits_granted BOOLEAN NOT NULL DEFAULT TRUE,8 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()9);10 11-- Blocks [email protected], [email protected] & [email protected] duplicates at the DB level12CREATE UNIQUE INDEX idx_users_canonical_email_unique ON users (LOWER(canonical_email));4. Frequently Asked Questions
Should I send welcome emails to raw_email or canonical_email?
Send transactional emails to raw_email (for example, [email protected]) so legitimate power users who use plus-addressing for inbox label filtering still have their rules respected, while checking canonical_email ([email protected]) to ensure they only receive one free-trial credit grant.
What about custom domains pointed at Google Workspace?
MailVeri inspects the domain’s live MX records during verification, detecting both consumer freemail aliases and custom domains routed through Google Workspace, Microsoft 365, or disposable MX clusters. See our Supabase, Clerk & Next.js Auth Guide for full framework hooks.
