# How to Stop Gmail Plus-Addressing (+1) & Dot-Trick Multi-Account Abuse in SaaS Signups

> Stop users from creating unlimited free trial accounts using Gmail plus-addressing (user+1@gmail.com), dot-trick aliases (u.s.e.r@gmail.com), and googlemail.com domains via canonical email normalization.

- **Author**: Alex, Co-Founder
- **Published**: 2026-04-09 (Updated: 2026-10-25)
- **Source**: https://mailveri.com/blog/how-to-prevent-gmail-plus-addressing-dot-trick-trial-abuse
- **AI Reference**: https://mailveri.com/llms-full.txt

---

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 `alex@gmail.com`, `a.l.e.x@gmail.com`, `alex+trial1@gmail.com`, and `alex@googlemail.com` 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](https://mailveri.com/industries/ai-startups) 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) | + (user+tag@gmail.com) | googlemail.com → gmail.com |
| ProtonMail (`proton.me` / `protonmail.com`) | Yes (Dots stripped) | + (user+tag@proton.me) | pm.me / protonmail.ch |
| Microsoft Outlook / Hotmail / Live | No! (john.doe != johndoe) | + (user+tag@outlook.com) | Distinct per TLD |
| Apple iCloud & Fastmail | No! (Dots are significant) | + (user+tag@icloud.com) | 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](https://mailveri.com/blog/free-email-providers-handling), stripping dots from `first.last@outlook.com` 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** as `DISPOSABLE`.
- `mail`: The provider-aware **canonical email address**—stripping dots and `+tags` for Gmail/ProtonMail while preserving significant dots on Outlook, iCloud, Fastmail, Yahoo, and custom domains.

```typescript
// lib/signup-canonical-guard.ts
// Stop Gmail +tag (user+1@gmail.com) and dot-trick (u.s.e.r@gmail.com) multi-account abuse
export interface CanonicalVerification {
  allowed: boolean;
  rawEmail: string;
  canonicalEmail: string;
  status: string;
  reason?: string;
}

export async function verifyAndNormalizeSignupEmail(
  rawEmail: string
): Promise<CanonicalVerification> {
  const res = await fetch('https://api.mailveri.com/v1/verify-mail/', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.MAILVERI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ email: rawEmail }),
    cache: 'no-store',
  });

  const data = await res.json();
  const status: string = data.status || 'UNKNOWN';
  // data.mail is provider-normalized (e.g., "a.l.e.x+trial9@googlemail.com" -> "alex@gmail.com")
  const canonicalEmail: string = (data.mail || rawEmail).toLowerCase().trim();

  if (status === 'DISPOSABLE') {
    return {
      allowed: false,
      rawEmail,
      canonicalEmail,
      status,
      reason: 'Disposable and temporary email domains are not eligible for free trial credits.',
    };
  }

  if (status.startsWith('INVALID') || status === 'FULL_INBOX') {
    return {
      allowed: false,
      rawEmail,
      canonicalEmail,
      status,
      reason: 'Please enter a valid, active email address.',
    };
  }

  return {
    allowed: true,
    rawEmail: rawEmail.trim(),
    canonicalEmail,
    status,
  };
}
```

## 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):

```sql
-- Enforce 1 free trial per real human inbox in PostgreSQL
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  raw_email TEXT NOT NULL,
  canonical_email TEXT NOT NULL,
  mailveri_status VARCHAR(32) NOT NULL,
  trial_credits_granted BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Blocks user+1@gmail.com, u.s.e.r@gmail.com & user@googlemail.com duplicates at the DB level
CREATE 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, `alex+saas@gmail.com`) so legitimate power users who use plus-addressing for inbox label filtering still have their rules respected, while checking `canonical_email` (`alex@gmail.com`) 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](https://mailveri.com/blog/how-to-block-disposable-emails-supabase-clerk-nextjs) for full framework hooks.
