#3 — Duplicate prospect handling: add_lead now checks (campaign_id, url)
before insert; bulk_add_leads skips existing URLs.
#8 — Atomic rate limiting: try_increment_* methods atomically check cap
and increment in a single session; router uses these before send.
#10 — Reply matching via Message-ID: sender generates Message-ID header,
stored on OutreachAttempt; reply monitor parses In-Reply-To/References;
poll_replies matches by message_id first, falls back to from_email.
#11 — Save-to-campaign uses existing store results instead of
re-running expensive deepDiscover.
#12 — Lead status Literal type: Pydantic models enforce valid status
values; backend validates via LEAD_VALID_STATUSES frozenset;
frontend API typed as LeadStatus union.
- Add BacklinkOutreachScraper (Exa + DuckDuckGo deep scraping)
- Extend DB and Pydantic models for lead enrichment columns
- Add StorageService methods for lead CRUD with auto-migration
- Add backend endpoints: deep discover, campaign detail, lead management
- Extend frontend API client and store with discovery + lead actions
- Create BacklinkOutreachDashboard component with campaigns/discover/leads tabs
- Register route at /backlink-outreach under SEO feature flag
- Add nav entry under Enterprise & Advanced in tool categories