feat(leads): ownership filter, send log, and an admin leads dashboard - #120
Merged
Merged
Conversation
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>
vu1nz Security Review0 finding(s) in PR #? No security issues found. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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. Newthird-party-scanexclusion. 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_sendswith 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/leadsResults 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 withnotFound()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.sqlmust 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 typecheckclean, 558 tests pass (5 new),npm run buildexits 0 with/admin/leadsregistered.🤖 Generated with Claude Code