Email Plus Addressing in SaaS: How to Handle + Aliases Safely
Email validation without restrictive regex: valid special characters, safe email normalization, canonical addresses, and SaaS trial abuse prevention.
An address such as alex+news@example.com is a normal email address. The +news part is called a tag. Many people use tags to sort email, see where an address was shared, or keep test accounts separate.
This can create a problem for SaaS products. A person may sign up for a free trial with alex@example.com, then try again with alex+trial2@example.com.
It is tempting to block the + character during signup. Do not do that. It blocks real users, breaks a useful email feature, and does not stop serious trial abuse.
The better approach is simple: accept valid email addresses, verify who controls the mailbox, and protect the actual benefit with real anti-abuse checks.
What plus addressing means
Every email address has two parts:
local-part@domain
For this address:
alex+invoices@example.com
\___/ \______/ \_________/
user tag domain
alex is the user part. invoices is the tag. The whole left side, alex+invoices, is still the local part of the email address.
RFC 5322, the main Internet email format standard, allows + in an ordinary unquoted local part. So + is not a bad character or a broken email address.
RFC 5233 describes sub-addressing for email filtering. It uses the words user and detail for the two pieces around a separator such as +.
There is one important detail: the RFC allows the syntax, but it does not force every email provider to treat alex+tag@example.com as alex@example.com. The receiving mail provider decides how delivery works.
For example:
- Google says email sent to Gmail plus variations arrives in the same inbox.
- Exchange Online has plus addressing enabled by default, but an administrator can turn it off.
- Apple provides normal aliases and Hide My Email addresses. These are separate addresses that forward to the user.
This is why a SaaS product must not assume that every domain uses the same rules.
Why real users use aliases
Plus aliases are useful, not suspicious by default.
sam+receipts@gmail.com
sam+product-updates@gmail.com
qa+password-reset@company.test
People use them to:
- put receipts and newsletters into different folders;
- find out which service leaked or shared an address;
- test signup, invites, and password resets;
- keep a separate address for each service;
- avoid sharing their main address through an alias or relay.
Developers and technical buyers use aliases often. If a developer tool rejects +, it can make a product feel less reliable before the user even sees the product.
Why blocking + does not stop abuse
Suppose a product gives one free trial per email address. Its database checks only the exact email string.
alex@example.com
alex+trial1@example.com
alex+trial2@example.com
Those are three different strings. A basic unique index accepts all three.
Blocking + seems like a fast fix. But the same person can still use many other methods:
- Gmail dot variations such as
a.lex@gmail.com; - a catch-all domain that accepts any local part;
- temporary email services;
- Apple Hide My Email or another relay service;
- another mailbox;
- an organization domain with real, valid aliases.
Google documents that dots do not change a consumer gmail.com address. It also documents that dots can matter for Google Workspace usernames. This means a rule such as “remove every dot for every Google domain” can join two different people by mistake.
The main lesson is that an email string is not proof of a person. Blocking one character only changes the spelling an abuser uses.
Keep development and production rules close
Plus aliases are very useful in development and staging. A QA engineer can test many cases with one inbox:
qa+new-user@example.test
qa+expired-invite@example.test
qa+acme-workspace@example.test
Do not make production email validation stricter than staging validation. That creates two different systems and lets bugs reach production.
Use the same email rules in every environment. Then make test accounts safe in other ways:
- allow known test domains only in non-production;
- mark test accounts with a field such as
account_type = 'test'; - do not send test users into production analytics or marketing tools;
- skip billing only for approved test accounts;
- use controlled test fixtures when you test fraud rules.
Email validation with regex: why blocking + is an anti-pattern
The phrase “stop validating email addresses with regex” points to a real limitation: matching a pattern cannot tell you whether someone controls a mailbox. A reasonable format check is still useful for catching mistakes before sending mail. The problem is treating a restrictive regex as either a full email standard or a fraud control.
Many signup forms use a regex like this:
^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+$
It rejects +, even though + is allowed by RFC 5322. It also rejects other valid email characters.
Which special characters are valid in email addresses?
Alongside letters and digits, RFC 5322 section 3.2.3 allows these characters in an unquoted local-part atom:
! # $ % & ' * + - / = ? ^ _ ` { | } ~
Dots separate atoms. For this unquoted form, a dot cannot start or end the local part, and two dots cannot appear together. Quoted local parts follow different rules. Allowed syntax does not mean every provider offers every possible mailbox name. RFC 5322
Email validation vs verification
Validation checks whether an address fits the formats your application supports. Verification sends a confirmation link or email OTP to check that the user can receive mail there. Use both: a maintained validator for your supported formats, then a verification flow. Neither step proves that the user has never claimed a trial before.
Do not try to fully implement every RFC email rule in one browser regex. Full email parsing is more complicated than it looks. A practical signup flow should:
- do a basic format check for a usable address;
- accept reasonable local-part characters, including
+; - send a short-lived verification link or code;
- make the backend enforce all important business rules.
Client-side validation is only a user experience feature. Anyone can call the signup API without using your form. Trial limits, referral limits, and fraud checks must run on the server.
Store more than one email value
Do not use one email field for everything. Delivery, login, duplicate detection, and fraud checks are different jobs.
This is a useful model:
CREATE TABLE user_emails (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id),
delivery_email text NOT NULL,
comparison_email text NOT NULL,
abuse_identity_key text,
verified_at timestamptz,
canonicalizer_ver smallint NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX user_emails_comparison_uq
ON user_emails (comparison_email);
CREATE INDEX user_emails_abuse_identity_idx
ON user_emails (abuse_identity_key)
WHERE abuse_identity_key IS NOT NULL;
Here is what each value means:
| Field | Use it for |
|---|---|
delivery_email | The verified address where you send email. Keep what the user entered. |
comparison_email | A careful value used for account uniqueness. |
abuse_identity_key | A provider-aware signal for trial or referral abuse checks. Do not treat it as final proof of identity. |
verified_at | The time the user proved they could receive email at this address. |
canonicalizer_ver | The version of the normalization rules used to create the keys. |
In a multi-tenant product, email uniqueness may belong inside a tenant instead of across the full product. One consultant may need to use the same verified email in several customer workspaces.
Email address normalization: what is the best practice?
Normalization means creating a comparison value from an email address. It can help spot known aliases, but it can also cause mistakes.
What is a canonical email address?
A canonical email address is a comparison form chosen by an application under a defined set of rules. There is no universal rule that reveals the original mailbox behind every alias. For example, ignoring Gmail dots can help group consumer Gmail addresses, but doing the same for a business domain can join separate mailboxes.
You may call the comparison field normalized_email or comparison_email. Keep it separate from the delivery address and document what its rules mean. In this article, the broader alias grouping goes into abuse_identity_key, which is a risk signal rather than a unique login key.
Why treat email local parts as opaque strings?
Here, “opaque” means your application should preserve the local part without guessing how another provider routes it. RFC 5321 section 2.3.11 assigns interpretation of the local part to the receiving host. It does not prohibit a SaaS product from validating syntax or setting a documented signup policy. It does explain why a sender cannot safely rewrite an unknown provider’s mailbox name. RFC 5321
Exchange Online makes the risk concrete: it first checks the complete address, including the plus tag, before trying the address without the tag. A custom routing setup can therefore behave differently from a simple “remove the tag” assumption. Provider rules can be useful when scoped, tested, and reviewed; detecting a provider from MX records alone is not enough. Microsoft documentation
The safe rule is: only change a local part when you know the provider’s documented behavior. Always keep the original verified delivery address.
This TypeScript example uses conservative rules. It only creates an alias-based abuse signal for consumer Gmail domains. It does not rewrite every domain with a + sign.
import { domainToASCII } from "node:url";
type EmailKeys = {
deliveryEmail: string;
comparisonEmail: string;
abuseIdentityKey?: string;
};
const GMAIL_DOMAINS = new Set(["gmail.com", "googlemail.com"]);
function splitEmail(input: string): [local: string, domain: string] {
const value = input.trim();
const at = value.lastIndexOf("@");
if (at <= 0 || at === value.length - 1) {
throw new Error("Invalid email address");
}
return [value.slice(0, at), value.slice(at + 1)];
}
export function deriveEmailKeys(input: string): EmailKeys {
const [local, rawDomain] = splitEmail(input);
const domain = domainToASCII(rawDomain).toLowerCase();
if (!domain) {
throw new Error("Invalid email domain");
}
// Keep the local part unchanged for normal account comparison.
const deliveryEmail = `${local}@${domain}`;
const comparisonEmail = deliveryEmail;
if (!GMAIL_DOMAINS.has(domain)) {
return { deliveryEmail, comparisonEmail };
}
let gmailLocal = local.toLowerCase();
const plus = gmailLocal.indexOf("+");
if (plus !== -1) {
gmailLocal = gmailLocal.slice(0, plus);
}
// Dot folding is for consumer Gmail. Do not use it for every domain.
gmailLocal = gmailLocal.replaceAll(".", "");
return {
deliveryEmail,
comparisonEmail,
abuseIdentityKey: `${gmailLocal}@gmail.com`,
};
}
Why keep comparisonEmail conservative? SMTP local parts can be case-sensitive, and plus delivery rules are controlled by the receiving domain. A global “remove everything after +” function can merge addresses that belong to different people.
For a domain your company owns, you can use stronger rules because you control its mail server. For a third-party domain, add a rule only after checking provider documentation and adding tests.
The example handles common signup addresses. It is not a complete parser for every allowed RFC 5322 address. If your product supports internationalized addresses, use a maintained parser and test SMTPUTF8 support described in RFC 6531.
Can aggressive email normalization cause account takeover?
It can contribute to an account takeover if a login or recovery flow trusts a false match. Imagine a domain that routes lee@example.com and lee+finance@example.com to different people. If your application strips the tag, both inputs now point to the same account key. A recovery flow that sends a reset link to the newly supplied address could then send access to the wrong person.
That is an example of a vulnerable design, not a claim that all normalization is unsafe. Bind reset tokens to the existing account, send recovery mail to its stored verified address, and require proof before linking accounts. Never use a match on an abuse signal alone to grant access.
Decide what “unique” means first
Before adding a database constraint, ask what you want to protect.
| Question | Possible answer | Design result |
|---|---|---|
| Is email used for login? | Yes | One verified login email may map to one account. |
| Can users join several workspaces? | Yes | Store membership separately from the user identity. |
| Can a user add more than one address? | Yes | Store many verified email rows per user. |
| Are you protecting a free trial? | Yes | Limit the trial benefit, not just account creation. |
If a normalized value matches an existing account, do not tell an anonymous visitor that the account exists. Give a neutral message and offer sign-in or password recovery through a rate-limited flow. This helps prevent account enumeration.
Use real anti-abuse controls for real abuse
Email verification proves that someone controls a mailbox. It does not prove they are a new customer or that they should receive another free benefit.
Protect high-value trials, credits, and referrals at the benefit layer. Combine several signals instead of trusting one email rule.
Verify the mailbox
Send a one-time token with a short expiry. Store only a hash of the token, expire it quickly, invalidate it after use, and limit how often a user can request or guess a token.
Use similar messages for existing and new email addresses. Do not help an attacker learn whether an account exists.
Use OpenID Connect for social login
If users sign in with Google, Microsoft, or another provider, use OpenID Connect. It gives your application a provider-specific user identifier.
Store the pair below as the external identity key:
(issuer, subject)
The email claim is useful profile data, but it is not the best permanent identity key. Do not merge accounts only because two providers return the same email string.
Add stronger checks when the benefit is expensive
For an expensive AI trial, cloud credit, or paid promotion, you may need more checks:
- email and alias signals;
- workspace invite or company domain information;
- payment method setup;
- signup rate by account, IP, network, and device;
- past trial or coupon use;
- a risk score and manual review for unusual cases.
Stripe SetupIntents can save and authenticate a payment method without taking a payment immediately. That can raise the cost of repeated abuse, but it also adds signup friction. A card is not proof that there is one person behind an account.
Use phone OTP with care
Phone verification can slow some automated signups. It is not a perfect identity check. Phone numbers can be recycled, shared, virtual, or taken over through SIM swaps.
NIST treats PSTN-based authentication, including SMS, as restricted. Use it when the risk supports the extra friction. Add send and attempt limits, and give legitimate users a recovery option.
Treat device fingerprints as risk signals
Device and browser fingerprints can help find repeated automation. They can also change, be spoofed, and create privacy duties.
OWASP recommends using device data, IP addresses, and remembered-device cookies as risk signals, not as a replacement for authentication. Do not block a user only because one signal looks unusual.
Low risk -> allow signup
Medium risk -> require email verification or another check
High risk -> delay the benefit, ask for stronger proof, or review
Confirmed abuse -> deny and save the reason for the decision
Collect only the data you need. Explain it in your privacy notice, keep it for a limited time, and give users a way to recover from a false positive.
Compare the options
| Method | Good for | Main problem |
|---|---|---|
Block + | Nothing as a general fraud rule | Blocks real users and is easy to bypass. |
| Exact email uniqueness | Stopping the same string from registering twice | Misses valid aliases. |
| Provider-aware normalization | Finding known aliases for selected providers | Wrong rules can merge separate mailboxes. |
| Email verification | Confirming mailbox control | Does not prove one human. |
| OpenID Connect | Login and account linking | One person can still use more than one provider. |
| Phone OTP | A risk-based extra check | Numbers can be shared or attacked. |
| Payment method check | Expensive trials or credits | Adds cost and signup friction. |
| Device and rate signals | Finding automation patterns | Can produce false positives and privacy concerns. |
The recommended approach
For most SaaS products, follow these steps:
- Accept
+and other reasonable email characters in the signup form. - Keep the verified delivery address exactly for sending email.
- Lowercase and normalize the domain safely, but do not apply global local-part rules.
- Keep provider-specific alias rules separate and versioned.
- Define uniqueness for the right thing: an account, a tenant membership, or a trial benefit.
- Verify mailbox control before activating the account.
- Use OpenID Connect subject IDs for social login.
- Add risk checks only when the value at risk needs them.
- Log why a signup was challenged or denied, so you can fix false positives.
- Give genuine users a recovery path.
The rule to remember is simple: do not judge a person from one character in an email address. Accept valid addresses, keep delivery data intact, and use layered controls to protect valuable benefits.
Common questions about email aliases
Should I strip plus tags before saving an email address?
Keep the delivery address, including its tag. If you need alias detection, calculate a separate value using documented provider rules. Verify and send mail to the submitted address, not a guessed base address.
Does Google Workspace ignore dots like Gmail?
No. Google’s consumer Gmail dot rule does not apply to Google Workspace usernames on custom domains. Keep those dots when comparing addresses. Google’s explanation
Does blocking email aliases prevent free-trial abuse?
No. It removes one route while leaving other mailboxes, domains, and relay services available. Protect trial claims with server-side limits and several risk signals. Phone OTP, OpenID Connect, and payment checks each add evidence, but none guarantees one account per human.
Further reading
David Celis discusses the limits of complex patterns in Stop Validating Email Addresses with Regex. For a production implementation, pair reasonable syntax checks with mailbox verification and server-side abuse controls, using the standards and provider documents below.
References
- RFC 5321: Simple Mail Transfer Protocol
- RFC 5322: Internet Message Format
- RFC 5233: Sieve Email Filtering, Subaddress Extension
- RFC 6531: SMTP Extension for Internationalized Email
- Google Workspace: Create variations of your email address
- Google: Dots do not matter in Gmail addresses
- Microsoft: Plus Addressing in Exchange Online
- Apple: Hide My Email with Sign in with Apple
- OpenID Connect Core 1.0
- OWASP: Email Validation and Verification
- OWASP: Authentication Cheat Sheet
- OWASP: Bot Management and Anti-Automation
- NIST SP 800-63B: Authentication and Authenticator Management
- Stripe: SetupIntents