TL;DR: A simple email input field is an attack surface. Users exploit aliases, relay services, and disposable domains to abuse free tiers, evade bans, and game referral systems. Here's how to defend against each vector.
Your registration form has one text field: email. It looks innocent. It's not.
That field is an attack surface for multi-accounting, ban evasion, referral fraud, and trial abuse. A determined user can create hundreds of accounts using a single Gmail address. Privacy-focused services give users unlimited anonymous aliases. Disposable email providers exist specifically for throwaway accounts.
This article covers five attack vectors and how to defend against each. See open-source libraries at the end for ready-to-use implementations.
1. Alias Abuse: One Inbox, Infinite Accounts
Most email providers support aliases that all deliver to the same inbox. Users exploit this to create multiple accounts that appear unique but aren't.
Gmail: Dots and Plus-Addressing
Gmail ignores dots in the local part and supports plus-addressing:
john.doe@gmail.com
johndoe@gmail.com
j.o.h.n.d.o.e@gmail.com
johndoe+signup1@gmail.com
johndoe+signup2@gmail.com
johndoe+freetrialforever@gmail.com
All six addresses deliver to the same inbox. A naive uniqueness check treats them as different users.
ProtonMail: Dots, Hyphens, and Underscores
ProtonMail goes further. As a security measure against impersonation, they ignore dots, hyphens, and underscores:
journalist.name@protonmail.com
journalistname@protonmail.com
journalist-name@protonmail.com
journalist_name@protonmail.com
All equivalent. ProtonMail implemented this to prevent attackers from registering lookalike addresses.
Yahoo: Hyphen-Based Aliases
Yahoo uses hyphens instead of plus signs:
john@yahoo.com
john-shopping@yahoo.com
john-newsletters@yahoo.com
Fastmail: Subdomain Addressing
Fastmail supports both plus-addressing and subdomain aliases:
user+tag@fastmail.com
anything@user.fastmail.com
randomalias@user.fastmail.com
The subdomain format is particularly dangerous because the local part can be anything.
Defense: Provider-Specific Normalization
Normalize emails before storing or checking uniqueness:
public static class EmailNormalizer
{
private static readonly HashSet<string> GmailDomains =
new(StringComparer.OrdinalIgnoreCase) { "gmail.com", "googlemail.com" };
private static readonly HashSet<string> ProtonMailDomains =
new(StringComparer.OrdinalIgnoreCase) { "protonmail.com", "proton.me", "pm.me" };
public static string Normalize(string email)
{
var atIndex = email.LastIndexOf('@');
var localPart = email[..atIndex];
var domain = email[(atIndex + 1)..].ToLowerInvariant();
if (GmailDomains.Contains(domain))
{
localPart = StripPlusSuffix(localPart);
localPart = localPart.Replace(".", "");
}
else if (ProtonMailDomains.Contains(domain))
{
localPart = StripPlusSuffix(localPart);
localPart = localPart.Replace(".", "")
.Replace("-", "")
.Replace("_", "");
}
// Yahoo: strip hyphen suffix
// Fastmail: handle subdomain addressing
// Outlook/iCloud: strip plus suffix only
return $"{localPart.ToLowerInvariant()}@{domain}";
}
private static string StripPlusSuffix(string localPart)
{
var plusIndex = localPart.IndexOf('+');
return plusIndex > 0 ? localPart[..plusIndex] : localPart;
}
}
Store both the original email (for sending) and the normalized form (for uniqueness checks).
2. Relay Services: Unlimited Anonymous Aliases
Email relay services let users generate unlimited unique addresses that forward to their real inbox. Unlike provider aliases, each relay address is genuinely unique at the DNS level.
Major Relay Services
| Service | Domain(s) | Notes |
|---|---|---|
| Apple Hide My Email | privaterelay.appleid.com |
Built into iCloud+ |
| Firefox Relay | mozmail.com |
Free tier available |
| DuckDuckGo | duck.com |
Email Protection feature |
| SimpleLogin |
simplelogin.com, slmails.com, etc. |
Proton-owned |
| Proton Pass | passmail.net |
Password manager integration |
| addy.io (AnonAddy) |
addy.io, anonaddy.com
|
Subdomain aliases |
| Fastmail Masked Email | fastmail.com |
Premium feature |
| GitHub | users.noreply.github.com |
Commit email privacy |
Subdomain-Based Services
Some services use username-based subdomains:
randomalias@johndoe.anonaddy.com
anotheralias@johndoe.anonaddy.com
This requires wildcard matching, not just exact domain checks.
Defense: Relay Service Blocklist
public static class RelayServiceBlocklist
{
private static readonly HashSet<string> BlockedDomains =
new(StringComparer.OrdinalIgnoreCase)
{
"privaterelay.appleid.com",
"mozmail.com",
"duck.com",
"simplelogin.com", "slmails.com",
"passmail.net",
"addy.io", "anonaddy.com",
"users.noreply.github.com"
};
private static readonly string[] WildcardParents =
{
"anonaddy.com", // *.anonaddy.com
"aleeas.com" // *.aleeas.com (SimpleLogin)
};
public static bool IsRelayService(string domain)
{
if (BlockedDomains.Contains(domain))
return true;
foreach (var parent in WildcardParents)
{
if (domain.EndsWith($".{parent}", StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}
Tradeoff: Blocking relay services frustrates privacy-conscious users. Consider your product's threat model. A free tier with expensive compute? Block them. A paid product with identity verification? Maybe allow them.
3. Disposable Emails: Throwaway Accounts
Disposable email providers create temporary inboxes that expire after minutes or hours. Users create an account, verify the email, and abandon the address.
Scale of the Problem
The disposable-email-domains community maintains a blocklist of 38,000+ domains. New services appear constantly.
Common Disposable Providers
- Mailinator, Guerrilla Mail, 10 Minute Mail
- Temp Mail, ThrowAwayMail, Fake Inbox
- Thousands of lesser-known variants
4. Fake Domains: Non-Existent Email Addresses
Users provide email addresses with domains that can't receive mail:
- Typos:
user@gmial.com,user@hotnail.com - Invented domains:
user@notreal.fake - Expired domains: Previously valid, now unregistered
Defense: MX Record Validation
Verify the domain has mail exchanger (MX) records in DNS:
public class MxRecordValidator
{
private readonly ILookupClient _dnsClient;
public async Task<bool> HasValidMxRecordsAsync(string domain, CancellationToken ct)
{
try
{
var result = await _dnsClient.QueryAsync(domain, QueryType.MX, ct);
// Check for MX records
if (result.Answers.MxRecords().Any())
return true;
// Fallback: Check for A/AAAA records (some domains accept mail without MX)
var aResult = await _dnsClient.QueryAsync(domain, QueryType.A, ct);
return aResult.Answers.ARecords().Any();
}
catch (DnsResponseException)
{
return false;
}
}
}
Performance note: DNS lookups add latency. Cache results and consider making MX validation async (verify after initial signup, not blocking).
5. Format Bypass: Invalid Email Strings
Weak regex patterns allow malformed addresses or miss edge cases.
Common Validation Mistakes
// Too permissive - allows invalid characters
var weak = @".+@.+";
// Too strict - rejects valid addresses
var strict = @"^[a-z]+@[a-z]+\.[a-z]{2,3}$";
// Rejects: user+tag@domain.com, UPPERCASE@domain.com, user@domain.co.uk
Defense: HTML5 Living Standard Pattern
The HTML5 specification defines a practical email validation pattern that handles real-world addresses:
public partial class EmailValidator
{
// HTML5 Living Standard email pattern
// Source: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
private const string Html5Pattern =
@"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
[GeneratedRegex(Html5Pattern, RegexOptions.Compiled)]
private static partial Regex EmailRegex();
public bool IsValidFormat(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;
return EmailRegex().IsMatch(email);
}
}
This pattern is a "willful violation" of RFC 5322. It rejects technically valid but practically useless addresses (like "quoted string"@domain.com) while accepting everything real users actually use.
Validation Pipeline
Combine all defenses in order of computational cost:
public async Task<ValidationResult> ValidateAsync(string email)
{
// 1. Format (fast, no I/O)
if (!IsValidFormat(email))
return Failure(Error.InvalidFormat);
var domain = ExtractDomain(email);
// 2. Relay service (fast, in-memory lookup)
if (IsRelayService(domain))
return Failure(Error.RelayService);
// 3. Disposable domain (fast, in-memory lookup)
if (IsDisposable(domain))
return Failure(Error.Disposable);
// 4. MX records (slow, DNS lookup)
if (!await HasValidMxRecordsAsync(domain))
return Failure(Error.InvalidDomain);
return Success();
}
Order matters. Check cheap operations first, expensive operations last.
Open Source Libraries
Don't build this from scratch. These libraries handle normalization, relay detection, disposable blocklists, and MX validation:
| Language | Library | Features |
|---|---|---|
| .NET | Alos.Email.Validation | Provider normalization, relay blocklist, 38K+ disposable domains, MX validation, auto-update |
| Node.js | mailchecker | 55K+ disposable domains, multi-language (JS, PHP, Python, Ruby, Go, Rust) |
| Node.js | email-verifier | MX validation, disposable detection, caching |
| Python | email-validator | RFC compliance, MX validation, internationalization |
| Python | disposable-mail-checker | Disposable domain blocklist based on community lists |
| Ruby | valid_email2 | MX validation, disposable blocklist, Rails integration |
| Go | email-verifier | Disposable detection, free provider detection, auto-update, SMTP check |
| PHP | email-checker | 1K+ disposable domains, Symfony/Laravel integration |
| PHP | disposable-email-filter-php | Auto-updated weekly, framework-agnostic, Laravel support |
None of these defenses are perfect. Determined attackers can register custom domains, use corporate email, or buy access to real accounts. The goal is to raise the cost of abuse high enough that it's not worth the effort for most attackers.
Top comments (0)