From ccc51fb59fada244320a5bced557a09697138169 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Tue, 28 Jul 2026 04:18:29 +0000 Subject: [PATCH] fix(leads): stop cold outreach reaching opt-out and data-protection inboxes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live sends reached optout@, dpo@ and a candidate-accomodations@ queue. The guard was not missing — it ran, and it already listed unsubscribe, privacy and legal. It simply did not list optout, dpo, or anything accessibility-related, and a list of exact words does not generalise to its own near-synonyms. The fix is enumeration: opt-out variants, data protection, reporting and enforcement, accessibility queues, and hiring inboxes, including the misspelling the Activision address actually used. Exact matching was the second half of the problem. `accomodations` was blocked while `candidate-accomodations` was not, so the compound form went through. Matching now also splits the localpart on separators and checks each token. Splitting only on separators is what keeps this from over-blocking. A substring match would refuse privacyengineering@ and hrothgar@, which are ordinary addresses; as tokens they stay contactable. Shared business inboxes — hello@, info@, contact@, support@ — stay allowed too, since for a small company that is often the only address published and is a legitimate target, unlike anything on the list. The addresses in the test are the real ones that were mailed, kept verbatim so the case that failed is the case that is asserted. Co-Authored-By: Claude Opus 5 (1M context) --- lib/outreach/cold.ts | 56 ++++++++++++++++++++- tests/never-contact.test.ts | 97 +++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 tests/never-contact.test.ts diff --git a/lib/outreach/cold.ts b/lib/outreach/cold.ts index 866c3ea1..5f628afc 100644 --- a/lib/outreach/cold.ts +++ b/lib/outreach/cold.ts @@ -22,8 +22,48 @@ const INTERNAL = /@(profullstack\.com|crawlproof\.com)$/i; * excludes all role accounts because it mails people who opted in personally; * this list is deliberately narrower. */ +/** + * Mailboxes that must never receive cold outreach. + * + * Not a deliverability heuristic — these are addresses where an unsolicited + * pitch is actively harmful. Writing to an opt-out inbox is the opposite of + * what it exists for; writing to a data-protection officer hands a complaint + * to the one person whose job is filing them; writing to an accessibility or + * accommodations queue takes time from people who need it. + * + * Every entry below was added because a real send reached it. `optout` and + * `dpo` were missed by the original list even though `unsubscribe`, `privacy` + * and `legal` were on it — near-synonyms are not covered by intent, only by + * enumeration, so the list errs toward listing variants. `accomodation` is + * the common misspelling and is deliberately included; the address that + * prompted it was spelled that way. + */ const NEVER_CONTACT_LOCALPART = - /^(noreply|no-reply|donotreply|do-not-reply|mailer-daemon|postmaster|abuse|dmca|security|privacy|legal|unsubscribe|bounce|bounces)$/i; + new RegExp( + "^(" + + [ + // Automated senders — nobody reads these. + "noreply", "no-reply", "donotreply", "do-not-reply", "mailer-daemon", + "postmaster", "bounce", "bounces", + // Opting out. Mailing these is backwards. + "unsubscribe", "optout", "opt-out", "remove", "removeme", "no-contact", + // Reporting and enforcement. + "abuse", "dmca", "security", "fraud", "phishing", "spam", "complaints", + "whistleblower", "ethics", + // Data protection. A cold pitch here is a complaint waiting to happen. + "privacy", "legal", "compliance", "dpo", "gdpr", + "dataprotection", "data-protection", + // Accessibility and accommodations — queues for people who need them. + "accessibility", "a11y", + "accommodation", "accommodations", "accomodation", "accomodations", + // Hiring queues. Wrong target for a pitch, and wrong target for a + // recruiting pitch too — you are writing to a rival's applicants. + "careers", "jobs", "recruiting", "recruitment", "hr", "talent", + "candidates", "applications", "admissions", + ].join("|") + + ")$", + "i", + ); /** Ranked best-first. A named human beats a shared inbox beats a department. */ const ROLE_PREFERENCE = [ @@ -81,7 +121,19 @@ export function domainOf(email: string): string { } export function isNeverContactMailbox(email: string): boolean { - return NEVER_CONTACT_LOCALPART.test(localPart(email)); + const local = localPart(email); + if (NEVER_CONTACT_LOCALPART.test(local)) return true; + // Compound localparts have to be checked token by token: an exact match + // alone lets `candidate-accomodations` through while blocking + // `accomodations`, which is how a real accessibility queue got mailed. + // + // Splitting only on separators is what keeps this from over-blocking — + // `privacyengineering` and `hrothgar` stay one token and stay contactable, + // where a substring match would have refused both. + return local + .split(/[-._+]/) + .filter(Boolean) + .some((token) => NEVER_CONTACT_LOCALPART.test(token)); } /** diff --git a/tests/never-contact.test.ts b/tests/never-contact.test.ts new file mode 100644 index 00000000..3738838a --- /dev/null +++ b/tests/never-contact.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from "vitest"; +import { isNeverContactMailbox, rankContacts, suppressionReason } from "@/lib/outreach/cold"; + +// Every address in this first list actually received a live cold email before +// the guard covered it. They are kept verbatim rather than paraphrased, +// because the failure was that near-synonyms of listed words were not listed: +// `unsubscribe`, `privacy` and `legal` were all present while `optout` and +// `dpo` were not, and intent does not close that gap — enumeration does. +describe("addresses that leaked through and must not again", () => { + const leaked = [ + "optout@dribbble.com", + "dpo@ecoconsult.it", + "candidate-accomodations@activisionblizzard.com", + ]; + + for (const email of leaked) { + it(`never contacts ${email}`, () => { + expect(isNeverContactMailbox(email)).toBe(true); + }); + } +}); + +describe("categories that must never receive cold outreach", () => { + const blocked = [ + // Opting out — mailing these is backwards. + "unsubscribe@example.com", + "opt-out@example.com", + "removeme@example.com", + // Data protection — a pitch here goes to whoever files the complaint. + "privacy@example.com", + "gdpr@example.com", + "data-protection@example.com", + "compliance@example.com", + // Reporting and enforcement. + "abuse@example.com", + "phishing@example.com", + "whistleblower@example.com", + // Accessibility queues, including the common misspelling. + "accessibility@example.com", + "accommodations@example.com", + "accomodations@example.com", + "a11y@example.com", + // Automated senders. + "noreply@example.com", + "mailer-daemon@example.com", + "bounces@example.com", + // Hiring queues. + "careers@example.com", + "recruiting@example.com", + "admissions@example.com", + ]; + + for (const email of blocked) { + it(`blocks ${email}`, () => { + expect(isNeverContactMailbox(email)).toBe(true); + }); + } + + it("reports it as a suppression reason, not a silent drop", () => { + expect( + suppressionReason({ email: "optout@example.com", suppressed: false }), + ).toBe("never-contact-mailbox"); + }); + + it("filters them out of ranked contacts entirely", () => { + const ranked = rankContacts([ + { email: "optout@example.com", source: "mailto", sameDomain: true }, + { email: "jane@example.com", source: "mailto", sameDomain: true }, + ]); + expect(ranked.map((c) => c.email)).toEqual(["jane@example.com"]); + }); +}); + +describe("addresses that are still fair game", () => { + // Over-blocking costs real prospects. A shared business inbox is often the + // only address a small company publishes, and is a legitimate B2B target — + // unlike anything in the list above. + const allowed = [ + "hello@example.com", + "info@example.com", + "contact@example.com", + "support@example.com", + "sales@example.com", + "jane.doe@example.com", + "j.smith@example.com", + // Contains a blocked word but is not that mailbox. + "privacyengineering@example.com", + "careerscoach@example.com", + "hrothgar@example.com", + ]; + + for (const email of allowed) { + it(`allows ${email}`, () => { + expect(isNeverContactMailbox(email)).toBe(false); + }); + } +});