# Plus-addressing normalization

- **Pattern:** `ab-000860` (`data-quality-list-hygiene.dedup-normalization.plus-address-normalization`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000860
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000860)

## Why it matters

Gmail and many providers route `user+tag@gmail.com` to the same mailbox as `user@gmail.com`. Without stripping plus-tags before deduplication, a single subscriber can appear multiple times with different tags (`user+promo@`, `user+news@`, `user+test@`) — each receiving its own copy of every send. The duplication inflates list counts, skews per-contact engagement metrics, and in markets with per-contact billing (most ESPs), directly increases cost. More critically, a suppression or unsubscribe recorded against the base address won't suppress the tagged variants, creating compliance exposure under CAN-SPAM § 5's unsubscribe-honoring requirement.

## Severity rationale

Low because plus-address collisions affect a minority of subscribers, but each duplicated contact creates a gap in unsubscribe coverage that compounds across campaigns.

## Remediation

Strip the plus-tag from the local part during canonicalization before the deduplication lookup:

```ts
function canonicalizeEmail(email: string): string {
  const [local, domain] = email.toLowerCase().trim().split('@')
  if (!local || !domain) return email.toLowerCase().trim()
  return `${local.split('+')[0]}@${domain}`
}

const canonical = canonicalizeEmail(rawEmail)
await db.contact.upsert({
  where: { email: canonical },
  create: { email: canonical, email_original: rawEmail },
  update: { updated_at: new Date() }
})
```

Store both `email` (canonical) and `email_original` (as submitted). Suppressions and unsubscribes must be keyed on the canonical form so all variants are covered. Note: Outlook's plus-addressing behavior differs from Gmail — plus-stripping is safe for `@gmail.com` and `@yahoo.com` domains; apply universally with awareness that it is a dedup heuristic, not a routing guarantee.

## Detection

- **ID:** `plus-address-normalization`
- **Severity:** `low`
- **What to look for:** Enumerate all email normalization steps applied before deduplication. Check whether the system strips the plus-tag from email addresses before deduplication checks. `alice+promo@gmail.com` and `alice@gmail.com` refer to the same mailbox. Without stripping the tag, a user can subscribe with multiple plus-tagged variants and receive duplicate sends. Look for regex or string manipulation that removes `+...` from the local-part before lookup.
- **Pass criteria:** Plus-tags are stripped from the local-part for deduplication purposes. Optionally: the canonical (stripped) form is stored while the original is kept in a separate column for reference. At least 1 normalization function handles plus-tag stripping.
- **Fail criteria:** Plus-tagged addresses are treated as unique addresses. Multiple `+tag` variants for the same base address can co-exist in the list.
- **Skip (N/A) when:** The system explicitly uses plus-tagging for contact attribution and intentionally tracks per-tag variants.
- **Detail on fail:** Example: `"user+campaign1@gmail.com and user+campaign2@gmail.com treated as separate contacts — same person receives duplicate sends"`
- **Remediation:** Strip plus-tags before deduplication lookup:

  ```ts
  function canonicalizeEmail(email: string): string {
    const [local, domain] = email.toLowerCase().trim().split('@')
    if (!local || !domain) return email.toLowerCase().trim()
    // Strip plus-tag from local part
    const canonicalLocal = local.split('+')[0]
    return `${canonicalLocal}@${domain}`
  }

  // Store canonical form; optionally keep original:
  const canonical = canonicalizeEmail(rawEmail)
  await db.contact.upsert({
    where: { email: canonical },
    create: { email: canonical, email_original: rawEmail, ... },
    update: { updated_at: new Date() }
  })
  ```

  Note: Gmail and many providers route `user+tag@gmail.com` to `user@gmail.com`. However, Outlook's plus-addressing behavior differs — apply this normalization selectively or universally with awareness of false deduplication risk on providers that treat `+` as a literal character (rare in practice).

## External references

- iso-25010 functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000860
