114 domains were on the board as about to go dark. The real number was 59.
The final notice in our Cloudflare migration was about to go out, and I rebuilt its audience from scratch first. The last mailer had picked its contacts from dead legacy columns, so the recipients came from fields nobody maintains.
Where the contacts came from
Those legacy columns are still in the schema and still hold values, so a query against them returns rows and nothing looks wrong. Every address it returned was a stored contact field, and none of them was checked against who actually runs the site today.
Rebuilding the list
The fix resolves people through their roles and their actual logins. A domain’s audience is whoever holds a role on it and signs in, no matter what the old contact fields say. Run that way, the go-dark list came out at 59 domains.
Holding the send
I held the send until tomorrow. Customers are still fixing their sites themselves, so the list gets rebuilt off a fresh scan before anything goes out.
AI Skills
Use this lesson with the AI assistant you already use
A board showed 114 domains about to go dark in a Cloudflare migration, but rebuilding the final notice audience gave 59. The earlier mailer had picked contacts from legacy columns stored on each domain record.
Paste the prompt, share only the context needed to answer it, and treat the result as a draft for your review. Do not include confidential information or let an AI assistant make changes without your approval.
Optional: for a visual report and saved memory, run /dxdev first.
Don’t have it? Get it at dxdev.com/skills/dxdev. The prompt works without it.
dxdev LESSON · paste into your AI coding agent
LESSON: Rebuild Audience Counts From Live Roles And Logins Instead Of Trusting Stored Contact Fields And Board Totals
SOURCE: dxdev.com/blog/legacy-schema-ghost-audience
WHAT HAPPENED: While rebuilding the audience for the final migration notice, the author found the previous mailer read contact fields from legacy columns on each domain record. They discarded that audience and rebuilt it by resolving people through their roles and their actual logins. Recounting produced 59 domains on the go-dark list instead of the 114 shown on the board. The author held the send until the next day so the list could be rebuilt from a fresh scan, since customers were still fixing their sites. A separate status badge that flapped to Awaiting DNS was traced to the status check's evidence cache during the one-hour propagation window, and it settled on its own.
THE RULE: Do not act on a headline number or stored contact fields just because they look authoritative. Recompute the audience from the current source of truth, and send only from a fresh scan.
CHECK MY CODE, then report PASS or FAIL with file:line for each:
1. Audience selection resolves recipients through current roles and actual logins, and no code path reads the legacy contact columns on the domain record.
2. Any count shown on a dashboard that drives an outbound send is recomputed independently from source data, and a mismatch such as 114 versus 59 is investigated before sending.
3. A send that depends on changing customer state is built from a fresh scan taken just before the send, not from a list generated earlier.
THEN PRINT: a table (check, PASS/FAIL, evidence, fix) + a verdict (applies / partially / OUT_OF_SCOPE / no) + the single most important next action.