/** * Per-signup email aliasing. * * Every account the agent creates gets its own delivery address. That address is the * correlation key used by `verification-expectations.ts`: a verification mail is only * considered if it arrived at the exact address the agent minted for that signup. * Without per-signup aliasing, correlation degrades to "some mail about GitHub arrived", * which anyone on the internet can manufacture. * * ── Mechanism verification (checked live 2026-07-26, not from memory) ────────────── * * Plus-addressing (subaddressing, RFC 5233) is the primary mechanism. * * Google Workspace Learning Center, "Tips to optimize your Gmail inbox" * (https://support.google.com/a/users/answer/9308648): * "You can create variations of your email address where all messages arrive in your * current inbox. Just add a plus sign (+) and any word before the @ sign in your * current address." * So `user+gv-github-com-k3n9x2p4@gmail.com` lands in `user@gmail.com`. Confirmed for * both gmail.com and Workspace-hosted domains. * * Cloudflare Email Routing docs, "Email Routing addresses" * (https://developers.cloudflare.com/email-routing/setup/email-routing-addresses/): * "supports subaddressing, also known as plus addressing, as defined in RFC 5233" and * "if you send an email to `user+detail@example.com` it will be matched by the * `user@example.com` routing rule." * Cloudflare also documents catch-all: "Email Routing forwards every email sent to your * domain, including misspelled local parts, to a single destination." * * Caveat that is NOT hypothetical: a meaningful share of signup forms reject `+` in the * email field, either by regex or by silently stripping it. When that happens the caller * must fall back to a catch-all local part on a domain the owner controls, see * `mintCatchAllAddressFor`, which produces the same encoded tag as a bare local part * (`gv-github-com-k3n9x2p4@example.com`) and parses back identically. * * ── Encoding ────────────────────────────────────────────────────────────────────── * * Tag layout: `--` * prefix `gv` for a full domain encoding, `gvt` when the domain had to be * truncated to fit the 64-octet local-part limit (RFC 5321 §4.5.3.1). * encodedDomain `.` -> `-`, literal `-` -> `--`. Reversible, and it keeps punycode * labels (`xn--...`) intact. Dots are deliberately avoided in the tag * because Gmail ignores dots in the local part. * nonce 8 lowercase alphanumerics, no hyphen, so the tag splits unambiguously * at its last hyphen. * * Deterministic enough to parse back to a service; the nonce makes two signups at the * same service distinct. */ export interface SignupAlias { /** The full address to hand to the signup form. */ readonly address: string; /** The owner's real delivery address this alias resolves to. */ readonly baseAddress: string; /** The service domain the alias was minted for, normalized. */ readonly serviceDomain: string; readonly nonce: string; /** True when the encoded domain had to be shortened to fit the local-part limit. */ readonly truncated: boolean; } export interface ParsedSignupAlias { /** The address with the signup tag removed, the owner's real inbox. */ readonly baseAddress: string; /** * The decoded service domain, or null when the alias was minted with a truncated * encoding. A null here means "ask the account registry", never "any domain". */ readonly serviceDomain: string | null; readonly encodedService: string; readonly nonce: string; readonly truncated: boolean; } export interface MintAddressOptions { /** Injected nonce, for deterministic tests. Must be 8 lowercase alphanumerics. */ readonly nonce?: string; } /** * Normalize an address for comparison: strip any display name / angle brackets, trim, * lowercase. * * The `+tag` is deliberately preserved, it IS the correlation key. Lowercasing the * local part is technically stricter than RFC 5321 (local parts are case-sensitive on * the wire) but matches how Gmail, Workspace and Cloudflare Email Routing actually * deliver, and being case-insensitive here can only widen what compares equal, never * let a different mailbox pass as the expected one. */ export declare function normalizeEmailAddress(raw: string): string; /** Split a normalized address into local part and domain, or null if it is not one. */ export declare function splitAddress(raw: string): { readonly local: string; readonly domain: string; } | null; /** Normalize a hostname or service domain: lowercase, no trailing dot, no port. */ export declare function normalizeDomain(raw: string): string; /** * Mint a plus-addressed alias for one signup at `serviceDomain`. * * `mintAddressFor('owner@example.com', 'github.com')` * -> `mike+gv-github-com-k3n9x2p4@example.com` */ export declare function mintAddressFor(baseAddress: string, serviceDomain: string, options?: MintAddressOptions): SignupAlias; /** * Mint the same tag as a bare local part, for a catch-all domain. Use when a signup form * rejects `+` in the email field. `parseAlias` handles both shapes. * * `mintCatchAllAddressFor('example.com', 'github.com')` * -> `gv-github-com-k3n9x2p4@example.com` */ export declare function mintCatchAllAddressFor(mailDomain: string, serviceDomain: string, options?: MintAddressOptions): SignupAlias; /** * Parse an address minted by `mintAddressFor` / `mintCatchAllAddressFor` back to the * service it belongs to. Returns null for any address this module did not mint, a * non-alias address is never treated as belonging to a service. */ export declare function parseAlias(address: string): ParsedSignupAlias | null; //# sourceMappingURL=signup-address.d.ts.map