Files
skills/docs/communication/emails/suppression/src/utils/email-utils.md
2026-05-29 08:20:56 +01:00

4.0 KiB
Raw Blame History

email-utils

(no submodules)

Purpose

email-utils provides three pure, side-effect-free helper functions used throughout @suaveplan/suppression to normalise, decompose, and validate email addresses. They exist as shared utilities so that the store, importer, and exporter all apply identical normalisation semantics — preventing duplicates caused by case differences or leading/trailing spaces.

All functions operate on string values only; no external libraries or Node.js builtins are required, keeping the module universally usable in both server and edge environments.

Features

  • normalizeEmail — trim + lowercase canonical form used for all storage and equality checks
  • extractDomain — isolate the domain segment after the last @
  • isValidEmail — lightweight RFC 5321 structure check (length limits, unique @, non-empty parts, dot-containing domain)
  • Throws SuppressionError with code SUPPRESSION.INVALID_EMAIL on malformed input (where applicable)
  • Zero external dependencies; safe in edge/browser runtimes

Usage: Basic Example

import {
  normalizeEmail,
  extractDomain,
  isValidEmail,
} from "@suaveplan/suppression";

console.log(normalizeEmail("  USER@Example.COM  ")); // "user@example.com"
console.log(extractDomain("user@example.com"));       // "example.com"
console.log(isValidEmail("user@example.com"));        // true
console.log(isValidEmail("not-an-email"));            // false

Usage: Advanced Example

import { normalizeEmail, isValidEmail, extractDomain } from "@suaveplan/suppression";
import { SuppressionError, SUPPRESSION_CODES } from "@suaveplan/suppression/errors";

function safeNormalise(raw: string): string {
  if (!isValidEmail(raw.trim())) {
    throw new SuppressionError(
      SUPPRESSION_CODES.INVALID_EMAIL,
      "suppression.invalid_email",
      { context: { raw } }
    );
  }
  return normalizeEmail(raw);
}

// Domain grouping
const emails = ["Alice@Example.com", "bob@example.com", "carol@other.io"];
const byDomain = Map.groupBy(
  emails.map(normalizeEmail),
  (e) => extractDomain(e)
);
console.log([...byDomain.keys()]); // ["example.com", "other.io"]

API Reference

normalizeEmail(email: string): string

Trims leading/trailing whitespace and converts the entire string to lower-case. Does not validate the result — use isValidEmail if validation is needed.

Parameters:

  • email — raw email string

Returns: normalised email string

extractDomain(email: string): string

Returns the substring after the last @ character, converted to lower-case.

Parameters:

  • email — email address (need not be normalised)

Returns: domain string (e.g. "example.com")

Throws: SuppressionError with code SUPPRESSION.INVALID_EMAIL when no @ is found.

isValidEmail(email: string): boolean

Performs a lightweight structural validity check:

  1. Must be a string of at most 254 characters
  2. Exactly one @ character, not at position 0
  3. Local part: 1–64 characters, no leading/trailing whitespace
  4. Domain: non-empty, contains at least one ., no leading/trailing dot or hyphen

Parameters:

  • email — string to test

Returns: true if all checks pass, false otherwise. Never throws.

Implementation Notes

normalizeEmail is intentionally minimal — it does not attempt Unicode NFKC normalisation or address parsing (angle-bracket stripping, comment removal). Production email pipelines should apply RFC 5321 full parsing upstream before calling this function.

extractDomain uses lastIndexOf("@") rather than indexOf to correctly handle the rare-but-valid case of multiple @ characters in quoted local parts, then lowercases the result for consistent domain matching.

isValidEmail deliberately avoids regular expressions to keep the logic readable and auditable. The length limits (254 total, 64 local) derive from RFC 5321 §4.5.3. IP-literal domains ([192.168.1.1]) and quoted local parts are not supported, matching the practical subset of addresses used in transactional email.