Skip to content

feat(leads): ownership filter, send log, and an admin leads dashboard - #120

Merged
ralyodio merged 1 commit into
masterfrom
feat/lead-dashboard
Jul 26, 2026
Merged

ralyodio merged 1 commit into
masterfrom
feat/lead-dashboard

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Three gaps found by running the campaign against the real audience.

1. Ownership filter

~15 of the 108 captured leads scanned a site they plainly don't own — x.com, github.com, wikipedia.org, linkedin.com, share.google. They were trying the tool, not auditing their property, so "want us to fix it?" reads as a mailshot that didn't look at its own data. New third-party-scan exclusion. They stay leads — they're just the wrong audience for this pitch.

2. Send log

Nothing recorded who had been mailed, so a re-run would mail everyone a second time. Adds campaign_sends with a unique index on (campaign, lower(trim(email))) — the database is the guard; the application-side check is only an optimisation and would race straight past under a concurrent run. The row is written after a confirmed send, so a message that never went out can still be retried.

3. Admin UI — /admin/leads

Results previously lived only in SQL. The page shows captured leads with per-lead status, campaign sends, watches, and live per-segment audience counts with exclusion breakdowns.

Audience counts are computed with selectRecipients — the same function the sender uses — so the dashboard cannot drift from what would actually be sent. Admin-gated with notFound() rather than a redirect, matching /admin, so a non-admin who guesses the URL gets no confirmation the page exists.

Deploy order

20260726140000_campaign_sends.sql must be applied for the dashboard and the already-sent guard to work. No cron involved, so it can be applied right after the deploy.

Verification

npm run typecheck clean, 558 tests pass (5 new), npm run build exits 0 with /admin/leads registered.

🤖 Generated with Claude Code

Three gaps found by actually running the campaign against the real audience.

1. Ownership. ~15 of the 108 captured leads scanned a site they plainly
   don't own — x.com, github.com, wikipedia.org, linkedin.com, share.google.
   They were trying the tool, not auditing their property, so "want us to fix
   it?" reads as a mailshot that didn't look at its own data. New
   third-party-scan exclusion; they stay leads, they're just the wrong
   audience for this pitch.

2. No send log. Nothing recorded who had been mailed, so a re-run would mail
   everyone twice. Adds campaign_sends with a unique index on
   (campaign, lower(email)) — the database is the guard; the application
   check is only an optimisation and would race under a concurrent run. The
   row is written AFTER a confirmed send, so a failed send can still be
   retried.

3. No UI. Results lived only in SQL. /admin/leads now shows captured leads
   and their status, campaign sends, watches, and live per-segment audience
   counts with exclusion breakdowns — computed with selectRecipients, the
   same function the sender uses, so the page can't drift from what would
   actually go out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit a3d04fb into master Jul 26, 2026
8 checks passed
@ralyodio
ralyodio deleted the feat/lead-dashboard branch July 26, 2026 08:44
@github-actions

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant