What's new
A running log of changes you can feel in the product. Most recent first.
November 2026
Section titled “November 2026”A simpler dashboard: one place for each job (11 November)
Section titled “A simpler dashboard: one place for each job (11 November)”The product does the same things; the work now takes fewer pages and fewer clicks.
- A campaign is one place with tabs: Overview, Review, Outreach, Board and Settings & costs. The Overview shows what’s waiting (to review, shortlisted, awaiting a reply, replied) and each number opens the right list. Renaming, pausing, changing the purpose, the scoring brief, expenses, archiving and deleting moved to Settings & costs.
- One panel per account in a campaign: the conversation, the next step, the review decision and the board stage together. It opens from a row in Review, from the Outreach inbox, from the Follow-up view and from a board card, so logging a reply and setting the next step no longer means changing page.
- Outreach has two views: Inbox (conversations) and Follow-up (who has had how many attempts and who is due another; this was the Outreach review page). In Follow-up you can now tick several rows and close or snooze them together.
- The account page has tabs: Activity (next steps, the conversation with a composer right there, brief and notes), Profile, People and Admin. You can add, edit and archive the people at an account from People, and add the account to a campaign with Add to campaign.
- The Accounts list has one Filter panel, a Saved views menu and a Clear all button. Every filter is in the page address, so you can bookmark a filtered list or send it to a teammate. Tick rows to add just those accounts to a campaign or to rank them. After an import, Rank these N accounts takes you straight to ranking.
- A shorter sidebar: Today, Accounts, Campaigns, Clients, Marketing, Insights, then Runs and Settings. Boards is under Campaigns, Find new accounts is a button on Accounts, the settings pages share one menu, and Organization overview is a tab of Insights.
- Today has a third scope, All my projects, a section In your campaigns with what’s waiting in each one, and a count beside Today in the sidebar. The list of dated to-dos is now called Next steps (it was Follow-ups), with Today / Tomorrow / In a week buttons when you add one.
- One word per thing: fit reads Strong fit, Good fit, Weak fit or Poor fit (it was Excellent / Moderate / Mismatch); Purpose (was goal), Client (was customer), Replied (was responded). The API keeps its field names and values.
- Old links keep working: a bookmarked page of an account in a campaign opens its conversation in the Outreach inbox.
- For agents:
GET /api/dashboard/campaign-work/returns what’s waiting in each campaign of a project. See the API reference.
Outreach review: follow up without the noise (5 November)
Section titled “Outreach review: follow up without the noise (5 November)”- A campaign now has a follow-up queue (since 11 November the Follow-up view of its Outreach tab): how many attempts each account has had, who is due another, who replied, and who to close as no response. It works without a board, and opening it creates no tasks or notifications. See Review your outreach.
- Menus low on a page now open upwards when there’s no room below, so every item stays reachable.
A scoring progress banner you can read (5 November)
Section titled “A scoring progress banner you can read (5 November)”- On Review and shortlist, the bar and the counter now glide together as each batch of scores lands, and a “+N” shows what just arrived. While nothing is scored yet the bar sweeps instead of sitting empty or full, and the finished state stays up for a few seconds, saying so when some accounts couldn’t be scored.
October 2026
Section titled “October 2026”Workflow boards for campaigns (29 October)
Section titled “Workflow boards for campaigns (29 October)”- A campaign can now have a workflow board: one card per account, columns from a template (Plain outreach or Backed trial), moves that record their evidence, automatic moves from the messages you log, deadlines with next steps, a work queue and weekly numbers. A combined view joins every board on the same template. See Workflow boards, Run a plain outreach board and Run a backed-trial board; for agents, the Boards API.
- Inbound messages can be classified as a reply, a refusal, a bounce or an auto-reply. A reply logged by an agent waits on the board for a person to confirm it.
- An account with a started card can’t be removed from its campaign, a campaign whose board is in progress can’t be deleted (archive it), and an account holding an active trial reservation can’t be deleted; merges and import undos that would break a card are refused with the reason.
- The message list takes
campaign_lead=<id>,none(a campaign account’s messages plus its account’s messages without a campaign) andstatus=draft,sentfilters, so the board’s evidence picker pages through every eligible message.
Rank thousands of accounts, review them at scale, start on Today (8 October)
Section titled “Rank thousands of accounts, review them at scale, start on Today (8 October)”LeadHunter now does its main job without a campaign in the way: rank a list of thousands against your fit criteria, shortlist the best in bulk, and start each day on Today.
Ranking and fit criteria
- Rank (top of Accounts) scores any filtered Accounts list, or a sample of it, against a product’s fit criteria for one purpose, without a campaign: estimate first, a budget cap, progress under Runs, results best fit first. See Rank a list without a campaign.
- A ranking run’s budget cap covers everything the run pays for, finding websites included, and is checked before each step that costs money. A run needs a price for its model to start, keeps the model it started with, and stops if a cost can’t be recorded. See Set a budget cap.
- A ranking run checks its spend before every AI call (website lookups and scoring batches) and stops when the cap is reached. Calls already in flight finish, so a run can end a little over its cap, up to one call per running chunk.
- Accounts merged or deleted while a ranking run is going stay in the run as merged or deleted, so its totals still reach 100 %; accounts it had already scored stay in its results.
- When a restarted worker picks up a ranking run or campaign scoring run, the slower copy can no longer overwrite the scores the newer one wrote.
- Fit criteria (what used to be the target persona or ICP) live under Settings → Products, one per purpose, and can be generated straight from a product; the campaign wizard’s step is now Define fit criteria. See Fit criteria and fit score.
- Every change to the fit criteria is a new version; scores made with an older one carry an old criteria badge, and a campaign’s review page offers Rescore for them. Two people editing the same fit criteria at once both get their change, as two versions.
- A campaign with its own scoring brief now keeps its own scores: rescoring it no longer changes what other campaigns, the Accounts list or Today show.
- Fit for on Accounts adds a sortable Fit column with a minimum-score and a scored / not-scored filter, and the account page has a Fit scores card with every score and its reasons.
- An account without a fit score now reads Not scored yet, in grey, instead of Mismatch with a 0.0.
- Score N remaining, Give feedback & rescore and the campaigns list’s Score remaining show the accounts, batches, model and estimated cost, and start only after you confirm.
Review thousands of accounts
- The campaign’s scoring page is now Review and shortlist: Fit score
bands (8–10, 6–8, 4–6, 0–4, not scored yet) with how many are decided in
each, a dense table best first (cards one click away) and keyboard
shortcuts (
j/k,s,d,x,u). See Review thousands of accounts. - Approve / Reject became Shortlist / Not a fit, plus Defer for “maybe later”. Decide one account at a time, or the selected rows, every matching account or the top N at once; a bulk change shows its count first and can be undone.
- Only decisions made one account at a time teach the scoring; bulk decisions never do. Choosing a decision by hand on an account a bulk change set makes it your own, so undoing that bulk change keeps it.
- Editing a campaign account’s notes no longer undoes a Shortlist, Defer or Not a fit decision a colleague made at the same moment.
- The campaign stat High-ICP reached is now Strong fits reached (accounts scored 8 or more that you’ve worked).
Today, Runs and Insights
- LeadHunter opens on Today: next steps due (yours or the team’s, with Mine / Team), replies waiting for an answer, clients past their contact cadence, then the best-scored accounts nobody is working yet. See Use Today.
- The sidebar is shorter: Today · Accounts · Campaigns · Clients · Marketing · Insights · Runs · Settings. Old links still work.
- Runs replaces the Tasks page: campaign scoring runs, ranking runs and imports, enrichment and searches, with progress, cost and cancel.
- Insights holds the outreach funnel (the old home), the breakdown (the old Stats page) and usage and costs (the old Usage page).
Imports and duplicates at any size
- The import’s Map columns step opens already mapped from your column headers, with real sample values; Suggest the rest with AI only fills the columns left on Skip. See Import accounts.
- The import result adds Rows not imported and Duplicates skipped CSV downloads (each duplicate with the account it matched), and View these N accounts opens exactly what that import created.
- Undo this import removes exactly what that import created, even when the same file was imported again a minute later; for older imports that can’t be told apart, it refuses instead of deleting the other import’s accounts. See Undo this import.
- Find duplicates works on projects of any size, pages its groups, has Exact matches and Similar names tabs, and the bulk merge shows what each merged account will hold before you confirm. See Merge duplicates.
- Merging two accounts of the same campaign keeps the review decision and its history: bulk decisions still count in their batch, and their Undo still works.
- When the survivor’s own review decision wins a merge, every decision on the absorbed account (who, when, the reason, the bulk batch) is kept in the merge history instead of being dropped; it can’t undo the survivor’s decision.
- A Similar names group now shows the similarity of its least similar account, so Merge ≥90% on this page never takes along an account that is only 85% similar.
Marketing actions
- Marketing → Actions tracks every collaboration with a partner (a reel, an article, a fair booth, a newsletter slot, a giveaway): type, stage, dates, reach, content links, costs (in-kind included), tracked links and a promo code. See Marketing partners.
- Each action shows its results: reach → clicks → accounts → trials → clients, revenue, cost per client and ROI.
- Attribute accounts links many accounts to an action at once (paste names, preview, confirm); one account at a time from the account page’s Acquisition.
- The Marketing dashboard compares actions, action types and partner kinds over a period, and the Partners list shows each partner’s actions, clients and ROI.
API
- New for agents: fit criteria (
/api/fit-criteria/, withgenerate,approveandversions), ranking runs (/api/ranking-runs/, withestimateandcancel),bulk-review/undo-review/review-batchesandscore-bandson campaigns, thedeferandresetactions on campaign accounts,scoring-estimate(andexpected_countonscore_leads/batch-score-remaining),GET /api/dashboard/today/and/api/dashboard/scoring-runs/, marketingsummary/partner-resultsand channelattribute-accounts, import reports (import-report.csv) and dry-run bulk merges. See API for agents. - The accounts list takes
fit_product/min_fit/ordering=-fit_score,import_researchandranking_run; the campaign-accounts list takesscore_bandandordering=-effective_score, and itsmin_score/max_scorenow read the score including your per-campaign override.
Scores and campaign briefs (simplify round)
- A campaign with its own scoring brief shows the shared score, marked out of date, for any account it hasn’t scored with the brief yet, instead of showing it as not scored. See A campaign’s own scoring brief.
- Rescore for out-of-date scores rescores only those accounts (API:
score_leadsandscoring-estimatetakestale_only); campaign-account rows say where each score comes from (score_source). - The outreach inbox filters the same way as the review list: the score filters use your per-campaign override, “Unscored” is a match option, and search matches every word you type, notes included.
- API:
GET /api/dashboard/today/takessectionsto fetch only some sections; the scoring runs’api_costhas the same shape as everywhere else (input and output tokens, per-model breakdown).
Merging no longer changes an account’s status by override (7 October)
Section titled “Merging no longer changes an account’s status by override (7 October)”The merge dialog used to offer the status as one more field you could pick, which let a merge turn a do-not-contact account back into a prospect without any record. A merge now always keeps the most advanced status (do not contact wins); change it afterwards on the account page if needed.
API: stricter list filters and request bodies (7 October)
Section titled “API: stricter list filters and request bodies (7 October)”For API clients: GET /api/accounts/ answers 400 for an unknown
status or relationship_types value (it used to return an empty list);
every write endpoint refuses a request body that isn’t a JSON object;
adding to a campaign from the list reports blocked_counts per reason,
and a preview over the 10,000-account limit returns only the count.
Long scoring runs are no longer cut off after two hours (7 October)
Section titled “Long scoring runs are no longer cut off after two hours (7 October)”How you hit it: score a campaign with thousands of accounts (Score remaining on the scoring page), which takes more than two hours. What happened: the server’s safety net treated any run older than two hours as wedged and marked it failed, even while it was still scoring batch after batch; you had to click Score N remaining to finish it. Now: a run is judged by its signs of life: it is flagged only after 30 minutes without scoring a batch or making progress preparing accounts, however long it has been running. A run interrupted by a server restart picks up where it stopped instead of starting over.
The new-campaign wizard no longer assumes a US SaaS audience (7 October)
Section titled “The new-campaign wizard no longer assumes a US SaaS audience (7 October)”How you hit it: create a campaign with Campaigns → New campaign, approve the persona, and on step 3 (Database Config) click Save and go to scoring without touching anything. What happened: step 3 came pre-filled with example values (United States and Canada, SaaS and FinTech, 1-200 employees, $1M-$10M revenue), they were saved on the campaign, and every later Regenerate of the target persona was nudged toward US software companies, whatever your product sold. Now: step 3 starts empty and saves only what you enter, and the persona sees those values as optional hints. The product description placeholder also no longer mentions finding emails: LeadHunter reads each account’s website and scores it against your pitch.
”Add matching accounts to campaign” adds exactly what the list shows (7 October)
Section titled “”Add matching accounts to campaign” adds exactly what the list shows (7 October)”How you hit it: on Accounts, narrow the list with the search box, a country, the quick-filter chips or More filters (say Germany plus an Imported from source), then Add matching accounts to campaign →. What happened: only the Advanced filter was applied, so the dialog matched far more accounts than the list showed (1,475 for a list of 0), and with one unfinished Advanced condition it matched every account in the project. Now: the dialog counts and adds exactly the list’s accounts (every filter, the task scope and the hidden marketing partners included), shows N accounts match the list before you confirm, and asks before adding a list with no filter at all. The button now appears next to the quick-filter chips whenever the list is narrowed. The campaign panel’s Add all N works the same way, and Export no longer includes archived accounts the list hides. The preview now reads N new · M already in campaign · K blocked; one add takes at most 10,000 accounts; Include marketing partners and Include trials are new options in the dialog.
Marketing partners: influencers, press, communities and events (7 October)
Section titled “Marketing partners: influencers, press, communities and events (7 October)”A new Marketing → Partners list keeps the influencers, press and media, communities and event organizers you work with, or want to, in one place: type, stage, audience, socials, last contact and the next step due. Two new relationship types, Community and Event organizer, join Influencer, Press / Media, Partner, Affiliate and Analyst. Partners have their own stage (To contact → Contacted → Talking → Agreed → Active → Past, or Declined / Not a fit) with a history of every change, and an Audience size. Pure partners stay out of the Accounts list (a Hiding marketing partners chip shows it) and out of the client stats, and sales-shaped campaigns now soft-block influencers, communities and event organizers the way they already did press. See Marketing partners.
The docs describe LeadHunter as it is now (7 October)
Section titled “The docs describe LeadHunter as it is now (7 October)”The Welcome page, the FAQ and the manuals now describe LeadHunter as AI that ranks thousands of prospects so you work the best ones first, then keeps every relationship moving: prioritise, work the relationship, measure what works, and let agents log through the API.
Quick-add templates for next steps (7 October)
Section titled “Quick-add templates for next steps (7 October)”The Next steps card now offers Quick add chips for the steps you write most often (follow up, book a demo, send the proposal, onboarding session…), ordered for the account’s status, with the rest under More…. A chip fills in the add form, due date included. When you mark a template step done, LeadHunter suggests the usual next one (Next: Prepare the demo after booking a demo) with one-click Add. Edit, reorder, hide or add your own under Settings → Next step templates. See Quick add from a template.
Next steps, reminders and a calendar feed (6 October)
Section titled “Next steps, reminders and a calendar feed (6 October)”Every account has a Next steps card: what has to happen next, whose move it is (Our action or Waiting on them), who owns it and when it is due, with Done and Reschedule on each step. The new bell in the top bar counts your steps that are overdue or due today, the Follow-ups page lists them by due date (yours across all projects, or everyone’s in the project), a morning email lists what is due, and Profile → Reminders gives you a private calendar link for Google Calendar, Apple Calendar or Outlook. Steps show on customers too, not only prospects. See Next steps and reminders.
Every conversation with an account on one page (5 October)
Section titled “Every conversation with an account on one page (5 October)”Each account has a Conversations page (Open conversations on the account’s Conversations card): every message with the account in one stream, across all its campaigns plus the ones logged without a campaign, with filters by channel and campaign and Load older for long histories. You can now log messages there without a campaign — trade-fair follow-ups, customer chats — and the composer’s Log under picks a campaign when you want the AI drafts. On an account that opted out of some kinds of outreach, a message without a campaign asks which purpose it serves, so logging outside a campaign can’t get around an opt-out. See Log a conversation.
Brief & Notes read better (5 October)
Section titled “Brief & Notes read better (5 October)”An account’s Brief & Notes now render full Markdown (numbered and nested lists, to-do checkboxes, tables, quotes, code) and keep every line break; dated [YYYY-MM-DD name] lines show as a log with a date per entry, and long notes fold behind Show all. See Notes are operator-only.
AI steps now run on GPT-6 Luna
Section titled “AI steps now run on GPT-6 Luna”Target personas, lead scoring, enrichment, outreach drafts and translation
now run on OpenAI’s gpt-6-luna (through OpenRouter) instead of Gemini
Flash-Lite. On our tests with real campaigns it scored higher on every
step: fewer details invented in account notes and drafts, scoring that
returns every account in a batch, and personas written in the campaign’s
language. It also costs less per call. Scoring a batch takes a few seconds
longer. Website discovery keeps using Gemini with Google Search. Cost
reports under Usage and costs show
openrouter:openai/gpt-6-luna on calls made from 3 October. (The August
note below announced Gemini 3.5 Flash-Lite; production stayed on Gemini
3.1 Flash-Lite until this change.)
Deleted contacts are archived, and merges keep their details
Section titled “Deleted contacts are archived, and merges keep their details”Deleting a contact through the API now archives it instead of
erasing it: it disappears from the account and from contact lists, and
POST /api/contacts/{id}/restore/ brings it back. Merging two accounts
that share a contact (same email) now combines that person’s details —
surname, phone, title and notes — instead of dropping the duplicate’s.
See Contacts live inside accounts.
Review fixes (3 October, evening)
Section titled “Review fixes (3 October, evening)”- Tracked links keep redirecting while our cache is down. A short link in an outreach email answered an error instead of opening the page whenever the cache service was unavailable; it now always redirects (the click is still logged).
- Cancel task stops a running import. A cancelled CSV import used to keep writing the rest of the file; it now stops before the next chunk, so Undo this import removes exactly what landed. See Big files run in the background.
- A sync push never re-subscribes someone who opted out themselves:
upsert-by-imported-idkeeps that opt-out and lists it underdnc_kept. Opt-outs can be pushed asdo_not_contact_purposes. See Pushing data back in. - Creating an account with an imported ID or Google Place ID another account already has answers a clear error on that field instead of a server error, Create anyway included.
- Removing low-fit accounts removes exactly the number you confirmed: if scoring changed the count while the dialog was open, nothing is removed and you’re asked to try again.
- Merging accounts keeps more of a shared contact: its source, verification status and primary flag (when it doesn’t replace the account’s own primary contact).
- Deleting a project only deletes its organization if you own the organization. See Team, projects and organizations.
- “S. L.” and “S.L.” name the same company again when imports and the new account form look for duplicates.
- A confirmation dialog closes when you press Back, so it can no longer delete something on the page you just left; a double click on a delete or Undo this import sends one request; Client Pulse settings save in the order you click them; switching project on the Accounts page never shows the previous project’s saved views or campaigns; account notes typed just before you leave the page are saved; Tab moves out of an open row menu.
Fixes and polish (2026-10-03)
Section titled “Fixes and polish (2026-10-03)”The last round of fixes from the September review: safer deletes, visible errors, imports that catch more duplicates, and an API that can’t create twins.
Deleting and dialogs
- Deleting a project, a campaign, or an account from its own page asks you to type its exact name in a dialog; the Delete button stays disabled until the text matches. Deleting several campaigns at once asks for
DELETE 3 CAMPAIGNS(with your count). See Campaign. - Dialogs close with Esc, keep keyboard focus inside while open and give it back when they close.
- A click on the backdrop only closes a dialog when the click starts and ends outside it, so selecting text in a form and releasing over the backdrop no longer throws the form away. A dialog never closes while it is saving.
- Marking a campaign Complete asks for confirmation, since a completed campaign can’t be resumed.
- Delete, save, approve / reject, export, snooze and archive buttons ignore a double click instead of running twice.
- The search palette (⌘K / Ctrl-K) and the project switcher work from the keyboard (arrow keys, Enter, Esc).
Errors and stale data
- A page that fails to load says so, with a Retry button, instead of showing an empty list or zero counters that looked like “no data”.
- Switching project or organization never leaves the previous project’s data on screen.
- Unsaved account notes are saved when you leave the page.
- A target persona that fails to generate shows the error instead of waiting forever.
Outreach and dates
- Each composer mode keeps its own text, so switching tabs no longer wipes a half-written message. See Log a conversation.
- Expense dates, and the date in Clients → Log touch, default to your local day, not the UTC one (no more yesterday’s date after midnight in Europe).
- Chart day labels match the server’s UTC days, and the funnel’s response and close rates line up with the right initiation day.
Accounts and imports
- The accounts page works on a phone: the project name stays readable at 360–390 px wide, the filter row wraps and wide tables scroll sideways.
- Deleting the last account on a page steps back to the previous page instead of showing an empty one.
- Imports find duplicates inside the same file, not only against accounts you already have. See Import accounts.
- Names that differ only by a letter or number are no longer treated as the same account: Radio 3 and Radio 5 in the same city are two accounts.
- The duplicate rules are documented as they work: nothing merges automatically. An import skips rows that match on Google Place ID, imported ID or name + city, without copying their values into the existing account, and imports rows that only share a phone, a website or a similar name, so run Find duplicates after a big import. The new account form refuses any match, and Google Maps research only checks the Place ID. See What happens on a match.
- Two imports (or an import and an API push) writing the same Google Place ID or imported ID at once skip the clashing row as a duplicate instead of rolling back its whole chunk.
- Imports handle Excel “CSV UTF-8” files, blank column headers and repeated headers (
Column 4,Phone (2)) without losing a column. - Status, acquisition channel and employee-count values are matched regardless of case, and an unknown value is cleared instead of stored as-is.
- The import result explains every changed value under “N values adjusted or cleared”, with the reason per field (shortened to fit, not a number, out of range, not a valid option). See What the result panel shows.
- Undo this import on a cancelled run no longer reaches a later import of the same file.
API, research and settings
- An
imported_idnow belongs to one account per project: parallel first pushes to the upsert API create one account and apply the other as an update, a Google Place ID another account already has answers 400, and the old"warning": "duplicate imported_id"key is gone. The upsert also acceptsrelationship_typesandis_verified. See Pushing data back in. - Manual research entries can’t be started (there is nothing to run); starting one says to add the accounts by hand. See Research.
- Text, long-text and URL custom fields hold up to 10,000 characters. See Custom fields.
- Deleting a project removes its organization when it was the organization’s last project and nobody else belongs to it; an automatically created organization gets the project’s owner as its owner. See Team, projects and organizations.
- Two simultaneous invites of the same address no longer fail: a registered person is added once and the second answer is “already a member”; an unregistered address gets a single pending invitation.
- Wording: the app calls the companies you work with accounts everywhere (no more “lead” or “prospect” for the record itself), and no longer suggests that LeadHunter sends emails for you.
September 2026
Section titled “September 2026”Security, reliability and fixes (2026-09-28)
Section titled “Security, reliability and fixes (2026-09-28)”A review round of fixes across sign-in, research runs, imports, scoring and the dashboard.
Signing in and your data
- Sign-in needs a confirmed email address; until then the sign-in page says “Email address not verified. Check your inbox for the confirmation link.” See Signing in and out.
- Invites take effect once the invitee confirms their email address, so nobody can claim a seat by registering someone else’s address.
- Sign-in shows the real reason it failed (wrong password, unconfirmed email, too many attempts, no connection) and has a Forgot password? link.
- Sign-in pauses after 10 attempts a minute from one network.
- Logout ends the session on the server and clears what the dashboard kept on that device (active project, recent items, unsent drafts, cached data).
- An expired session sends you to sign in and back to the page you were on, instead of showing empty pages.
- The installed app never caches your data for offline use, so the next person on the device can’t see it.
- AI and data-provider actions share a limit of 60 per minute per user, to stop runaway scripts; see the FAQ.
- Webhook destinations and account lookups refuse private and internal network addresses.
- An account’s status and per-purpose opt-outs change only through their own actions (with history); a plain API update ignores them.
- Accounts, products and custom fields can’t be moved to another project after creation.
- Turning a draft message into a sent one runs the do-not-contact checks.
Research and imports
- Google Maps research works again from Research → New Research → Google Maps API; it had stopped creating accounts.
- Google Maps and Tavily runs end partial when some searches fail and failed when all do (Tavily used to say completed).
- Retry on the Tasks page restarts the run for Google Maps, Tavily and enrichment; a failed CSV import shows Re-import the file instead. See Retrying a run.
- The Tasks page’s Completed card now includes partially completed runs.
- Imports are capped at 10 MB and 200,000 rows per file.
- Import reports invalid rows under “Invalid rows skipped” instead of dropping them silently; see Import accounts.
- A non-numeric or out-of-range number in an import is cleared and counted, instead of failing its whole chunk.
- Enrichment stores cleaner data: emails and phones only from what the page shows, social links only from the real platform, no image file names as emails.
- One over-long scraped value no longer fails an enrichment batch.
- A temporary AI-provider outage no longer marks an account’s website search as tried; Find & Enrich says to try again later.
- Merging duplicates keeps Client Pulse history and the last-contacted date, snooze and cadence; see Merge duplicates.
Campaigns and scoring
- The new-campaign wizard does what it says: Approve Definition approves the persona, example URLs are saved to the product, Save and go to scoring opens scoring, and every step has Back. See Run your first campaign.
- Slow website preparation no longer gets a scoring run flagged as stuck, as long as it keeps making progress.
- A long rescore interrupted by a restart resumes where it stopped instead of starting over.
- Scoring and persona generation always see your latest edits (a brief saved in the same click is no longer missed).
- Quick-score results show their reasons instead of a blank screen.
- Campaign actions show errors inline instead of a “Campaign not found” page, and a paused campaign shows Paused.
- Bulk-add explains accounts it skipped because they belong to another project (“not in this project”).
- Rejecting an account with a reason adds the reason to your note instead of replacing it.
Dashboard
- Unsent message drafts survive switching conversations in the outreach inbox.
- Removing a per-purpose opt-out asks for confirmation, and setting Do not contact or Customer takes an explicit Apply.
- The project switcher shows on mobile.
- Removed a notification bell that never had notifications, and a Settings menu entry that led nowhere.
- Fixed dead links (research detail, account edit, campaign pages opened without a campaign).
- Error pages let you retry instead of a white screen.
API: editable contacts, safe retries, append-only notes
Section titled “API: editable contacts, safe retries, append-only notes”Improvements for anyone driving LeadHunter from scripts or AI agents:
- Contacts are editable after they’re created. New
/api/contacts/endpoint to list, add, update and delete the people at an account. A contact’s last name is now optional. See Contacts live inside accounts. - Log messages without duplicates. Pass the sending service’s message
id as
external_idwhen logging a message; a retry returns the existing message instead of logging it twice. See Messaging. - Append to account notes with
POST /api/accounts/{id}/add-note/: a dated line, without overwriting what’s already there. ?project=takes the project slug or its id everywhere, and an unknown project now answers with an error instead of an empty list that looked like an empty project./api/projects/{id}/works with the id too. Messages, campaign accounts, target personas and expense lists now honour?project=— before, they returned rows from every project you belong to.- The merge endpoint’s response now carries the merged record under
account, as documented.
Organizations: several projects under one roof
Section titled “Organizations: several projects under one roof”What the dashboard used to call a Company is now a Project, and projects can be grouped under an Organization — for example an agency with one project per client, or a business with one project per line of business. Each project still keeps its own accounts, campaigns, conversations, stats and costs, fully separate from the others.
- The top-bar project switcher shows the organization above the active project and lists your projects grouped by organization.
- Organizations in the sidebar shows an outreach funnel added up across an organization’s projects, plus a grid to jump into any of them.
- New Project lets you pick the organization it belongs to; the organization’s members are added to the new project automatically.
- Access is still granted per project, so you can give someone just one project of an organization.
Already a user? Each of your existing projects got its own organization with the same name and members — nothing changes until you add a second project. See Organization, project and product and Team, projects and organizations.
August 2026
Section titled “August 2026”Default LLM upgraded to Gemini 3.5 Flash-Lite
Section titled “Default LLM upgraded to Gemini 3.5 Flash-Lite”Every AI step now runs on gemini-3.5-flash-lite (was
gemini-3.1-flash-lite). Cost reports under
Usage and costs show the new model name
on calls made after the rollout.
July 2026
Section titled “July 2026”Fixed: adding an account manually with the default status
Section titled “Fixed: adding an account manually with the default status”The Add Account → Manual Entry form rejected its own default status (“New” wasn’t a valid choice). The Status dropdown now offers the full relationship lifecycle — Prospect (default), Contacted, In negotiation, In trial, Customer, Lost, Do not contact — so you can also log pre-existing customers correctly at creation time.
Promo-code attribution for channels
Section titled “Promo-code attribution for channels”Channels can now carry a promo code (e.g. LAIA20). When your
checkout or signup flow pushes a conversion to the account upsert API
with that code, the account attributes to the channel automatically —
covering purchases where nobody clicked a tracked link. Codes are
case-insensitive, unique per company, and an unknown code never
breaks the push. Combined with captured value and a status in the
same call, a single API request lands a fully-attributed,
fully-valued customer.
Influencer collabs: tracked links, owned channels, and attribution
Section titled “Influencer collabs: tracked links, owned channels, and attribution”The influencer channel now closes the whole loop:
- “This channel is…” — link a channel to the influencer’s own account; their account page shows the collab’s spend, value, ROI, and clicks next to your conversation history with them.
- Tracked links — generate short links from the channel page, hand them to the creator to post, and watch human clicks roll up per link and per channel (bots and link previews never count).
- Smarter attribution — pick the exact channel on any account, new inbound accounts auto-attach when only one channel of the kind exists, acquired dates are stamped automatically, and captured value is editable right on the account page.
See Tracking influencer collabs.
Influencer acquisition channel
Section titled “Influencer acquisition channel”Accounts and channels can now be attributed to influencer / creator
promotions with the new influencer acquisition channel. Create one
channel per influencer (label it with their handle), log what you pay
them as channel expenses, and the existing cost / value / ROI rollups
tell you what each collab actually returned. Accounts created on the
influencer channel auto-promote to Contacted like every other
inbound channel. See Acquisition channels.
Client Pulse — stay close to your existing clients
Section titled “Client Pulse — stay close to your existing clients”Client Pulse is LeadHunter’s answer to “we won the deal… now what?”. It closes the loop after an account becomes a customer:
- Usage signals from your own products. Your product’s backend sends LeadHunter a tiny ping when a client opens the app, uses a feature, starts a trial, or converts — authenticated with a simple per-company key. LeadHunter shows you, per client, when they were last active and which features they actually use.
- A new “In trial” lifecycle stage between In negotiation and Customer, driven automatically by those pings (or set manually).
- A contact cadence you configure per company — for example
“talk to every client at least once every 6 weeks”. Clients you
haven’t contacted within the cadence surface on the Clients
page, in a weekly email digest, on the dashboard home, and
(optionally) through the
client.contact_dueoutbound webhook so you can route the nudge to Slack or your own tools.
The combination is the point: heavy product usage with no recent contact is an upsell conversation waiting to happen; silence on both fronts is churn risk you can still act on. Full walkthrough: Stay close to your clients — Client Pulse.
Invitation emails and the Sign up link work again
Section titled “Invitation emails and the Sign up link work again”Team-invitation emails and the login page’s Sign up link pointed at a page that no longer existed, so new teammates hit a dead end. Both now land on the real account-creation page. If you invited someone recently and they couldn’t register, re-send the invite from Settings → Team (revoke the pending one first — re-inviting does this automatically).
The sidebar shows your real API spend
Section titled “The sidebar shows your real API spend”The sidebar footer used to show a placeholder credits counter. It now shows the active company’s actual month-to-date API spend, and when you’ve set a monthly budget it becomes a progress bar toward it (amber once you cross your alert threshold). Click it to open the full Usage page.
Reliability and data-safety round-up
Section titled “Reliability and data-safety round-up”A batch of fixes from a full review of the platform, all live now:
- Sturdier CSV exports — exported cells can no longer be misread as spreadsheet formulas by Excel, Google Sheets, or LibreOffice, so opening an export is always safe.
- Duplicating a campaign keeps its setup — the copy now preserves the goal, scoring brief, outreach brief, and communication language instead of resetting them to defaults.
- Merging accounts preserves everything — conversation history now moves to the surviving account, and every newer field (opt-out purposes, relationship types, custom fields, social links, captured value, and more) carries over instead of being dropped.
- Clearer errors on bad filter values — a typo in a filter or a malformed value now returns a clear validation message instead of a failed request.
- Import wizard keeps your file after a failed run — if an import fails, you can fix the mapping and retry without re-uploading.
- Clear message for legacy
.xlsfiles — the import wizard now tells you up front to save as.xlsxor CSV instead of failing with a generic error. - Team management hardened — the older team endpoints were removed; all member and role changes now go through the owner-controlled Settings → Team flow.
June 2026
Section titled “June 2026”Override the translation languages per conversation
Section titled “Override the translation languages per conversation”The From → To direction at the top of the composer is now editable. It still pre-fills from your resolved languages (account → campaign → product → you → English), but you can change either side for the conversation in front of you — type any language or pick one from the dropdown, and a swap button flips the direction. It steers both the AI drafts and type-&-translate for that conversation only and isn’t saved; a reset link puts the resolved languages back. Handy when a lead’s detected language isn’t quite right for one message. For a durable change, edit the account/campaign/product/your language as before. See Messaging → Override the languages for one conversation.
Invite your team — self-serve
Section titled “Invite your team — self-serve”No more emailing support to add a teammate: Settings → Team now handles it. Invite by email (existing users join instantly; new ones get an invitation that auto-attaches when they sign up with that email), change roles, remove members, revoke pending invites — with the last-owner guard keeping every company administrable. Invitations expire after 14 days. See Team and companies.
Budget alerts — know before the bill does
Section titled “Budget alerts — know before the bill does”Set a monthly API budget on the Usage page and LeadHunter watches it for you: a progress bar tracks month-to-date spend, and the whole team gets an email when spend crosses your threshold (default 80%) and again at 100% — once per level per month. It’s an early-warning system, not a cut-off. See Usage and costs → Budget alerts.
The funnel now shows who’s clicking
Section titled “The funnel now shows who’s clicking”The dashboard funnel gained a Clicked stage between Initiated and Responded, fed by tracked links: how many contacts clicked something you sent, before any reply. It comes with its own daily chart, a cohort click-through rate alongside the response and close rates, and a clicked column on the per-product / per-campaign stats tables. Bot clicks stay filtered out, and the maturity caveat applies as with the other cohort rates — fresh cohorts haven’t had time to click yet.
Close the loop: push data back into LeadHunter
Section titled “Close the loop: push data back into LeadHunter”The integrations story is now round-trip. POST /api/accounts/upsert-by-imported-id/ lets your CRM or automation tool push state into LeadHunter keyed on the same imported_id the webhook payloads carry: only the fields you send change, custom fields merge per key, unknown accounts are created, and status changes run through the normal pipeline (audit trail + webhooks fire, repeated pushes are no-ops). Webhook out, upsert in — see Push events to your tools → Pushing data back in. Webhook delivery history is now kept 90 days and pruned automatically.
Webhooks — LeadHunter now pushes events to your tools
Section titled “Webhooks — LeadHunter now pushes events to your tools”Connect your CRM, Zapier, n8n, Make, or your own backend: Settings → Webhooks lets you register HTTPS endpoints that receive account.created, account.status_changed, and message.inbound events the moment they happen. Deliveries are HMAC-signed (verify with your per-webhook secret), retried with backoff, replayable one click at a time from the delivery log, and a dead endpoint switches itself off after 20 straight failures instead of retrying forever. Payloads carry your own imported_id, so events arrive pre-matched to your CRM records. A built-in Test button fires a synthetic event through the real pipeline. See Push events to your tools.
One Usage page for every cost
Section titled “One Usage page for every cost”The long-promised Usage page is live (sidebar → Usage): this-month / last-30-days / all-time API spend, a 90-day daily trend, 30-day breakdowns by model and by provider, the top campaigns and tasks by AI spend (each linking straight through), and your user-entered campaign + channel expenses per currency — all side by side, per company, in one view. Auto-tracked costs are in USD; manual expenses stay in the currency you entered them (no silent FX). See Usage and costs.
Tracked links — see who clicks before they reply
Section titled “Tracked links — see who clicks before they reply”Every link you paste into outreach can now be a tracked short link. Create one from the new Tracked links panel in the conversation pane (it pre-fills with the account’s website), copy it into your message, and LeadHunter records every click — the conversation shows the count and the inbox row gets a clicks chip, so a contact who clicked twice yesterday floats to the top of your attention even though they haven’t replied yet. Link-preview bots are filtered out of the counts, no personal data is stored about the clicker, and destinations are immutable once created so the click record stays honest. See Messaging → Tracked links.
Outreach inbox: load more rows, steadier progress bars
Section titled “Outreach inbox: load more rows, steadier progress bars”A batch of quality-of-life fixes across the campaign pages:
- The outreach inbox’s contact list now has a Load more button instead of stopping at the first 50 rows — page through a big campaign without narrowing the search.
- The scoring page’s progress bar no longer freezes on long runs (it used to stop updating after 10 minutes even though scoring kept going), and a failed approve/reject now tells you instead of silently doing nothing.
- The campaigns list’s live scoring banner now sees runs on any page of the list — previously a run on page 2, or one hidden by an active search, showed no banner.
- Fast filter/tab switching can no longer briefly show stale results in the outreach inbox.
May 2026
Section titled “May 2026”Install LeadHunter as an app
Section titled “Install LeadHunter as an app”LeadHunter is now an installable web app. Install it from Chrome / Edge on desktop, Add to Home screen on Android, or Share → Add to Home Screen in iOS Safari, and the dashboard runs in its own window with its own icon — no browser tabs to lose, faster loads. When a new version ships, the app updates itself in the background and offers a one-click Reload. Step-by-step instructions per platform: Install LeadHunter as an app.
Outreach inbox: search notes, and share a link to a conversation
Section titled “Outreach inbox: search notes, and share a link to a conversation”Two quality-of-life upgrades to the campaign outreach inbox:
- The search box now also matches the internal note and AI draft directions on each row — not just names and emails. Stash an order number, CRM id, or reminder in a contact’s note and you can jump straight to them by searching it.
- Opening a conversation now updates the URL. Refresh and you stay on the same thread; copy the link and send it to a teammate and they land right on that account’s conversation. No more losing your place or describing “the third one down”.
Three AI drafts to choose from — with one-click copy
Section titled “Three AI drafts to choose from — with one-click copy”Hit Draft with AI and you now get three drafts, each a different approach — a direct one-liner, a warm relationship-first opener, an outcome/value pitch — each labelled with its angle and a one-line “why”. Pick the register that fits the account instead of wrestling a single draft into shape. Every draft has a Copy button (puts the exact text on your clipboard to paste into your channel — the normal flow, since LeadHunter logs rather than sends) and a Use this button (loads it into the editable box to tweak and log). The first approach is selected automatically, and Redraft gives you three fresh angles. See Messaging → Three approaches, pick one.
Tell the AI how to write your outreach
Section titled “Tell the AI how to write your outreach”You can now hand the AI drafter directions in your own words, at two levels. A campaign-wide AI outreach brief (a panel at the top of the campaign’s outreach inbox) applies to every draft in the campaign — “keep it under 60 words, friendly, no emojis; lead with the integration angle”. And a per-account AI draft directions box (at the top of each conversation) handles one-off context for a single account — “they demoed last week, reference it; don’t bring up pricing yet”. Per-account directions layer on top of the campaign brief and win on any conflict. Both save as you click away and take effect on the next draft — nothing to regenerate. They’re distinct from the internal note, which the AI never reads. See Messaging → Steering the draft.
See an account’s campaigns — and jump straight into the conversation
Section titled “See an account’s campaigns — and jump straight into the conversation”The account detail page now has a Campaigns card listing every campaign the account is part of, with each campaign’s goal and where the account stands in it (review + outreach status). Two links per row: the campaign name opens the campaign, and an Outreach button jumps straight into that campaign’s outreach inbox with this contact already open — so going from “looking at the account” to “writing the next message” is one click, no hunting through tabs and pages. The deep link finds the contact even when it’s filtered out of or sits past the inbox’s current view. See Account → Which campaigns this account is on.
Custom fields that deep-link into your other tools
Section titled “Custom fields that deep-link into your other tools”A url custom field can now carry a deep-link template — a URL with {placeholders} LeadHunter fills in per account — so one click jumps from an account straight into your CRM, billing system, or admin panel. Set it up in Settings → Custom fields: choose type URL, write something like https://crm.example.com/contact/{imported_id}, and give it a link label (e.g. “Open in CRM”). Placeholders can be a value you type per account ({value}), another custom field ({cf.<key>}), or the account’s own data — and the standout is {imported_id}, which deep-links every imported account back into its source system with zero extra typing. Links open in a new tab, are checked to be safe web addresses, and show up in CSV exports as the finished URL. You can also now scope a custom field to one product so product-specific fields stay grouped. (Two-way sync — pushing data out on account events — is on the roadmap next.) See Custom fields → Deep links into external systems.
Find that campaign you can’t quite find
Section titled “Find that campaign you can’t quite find”Two complementary upgrades:
- The Campaigns list (
/dashboard/campaigns) now has a search box above the table. Type a partial name (or a product name) and the list narrows as you type — debounced 250 ms, server-side, so even a project with hundreds of campaigns finds the match. Pagination resets to page 1 with the new query so you don’t have to click through; clear the box to get the full list back. - The Cmd+K palette (and the matching search field in the top bar) now also reaches archived rows. The whole point of the palette is “I know this exists, just take me there” — hiding archived accounts and campaigns defeated that. Each result row now tags itself with
Archivedin the sublabel so you know what you’re opening.
If “calif” wasn’t finding Enacast California before — both surfaces now will.
Find duplicates straight from the campaign scoring page
Section titled “Find duplicates straight from the campaign scoring page”You no longer have to bounce out to Accounts → Find duplicates when you spot two scored rows that look like the same business. The scoring review page (/dashboard/campaigns/{id}/scoring) now has a Find duplicates button in the header that runs the same scanner — but scoped to this campaign. It shows the count first (“found 3 duplicate groups in this campaign”), then lets you AI-review, bulk-merge ≥90%, or merge group-by-group exactly like the project-wide flow. Cross-campaign twins still appear in the same group so the merge keeps the full picture. See Merge duplicates.
Download a CSV of the campaign’s approved accounts
Section titled “Download a CSV of the campaign’s approved accounts”The campaign detail page now has a Download CSV button next to Add new account. By default it pulls the four columns most people want — Name, Website, Location (City, Country), and AI Score — and only the rows you’ve marked Approved. A modal lets you widen the row filter (Approved + Pending, or every status), and tick on/off any of ~40 optional columns grouped by topic: contact info, address parts, account socials, business profile, pipeline & tags, workflow / audit. Project-defined custom fields show up automatically in a dedicated section, so per-company schemas (e.g. “Annual revenue”, “Industry segment”) are one click away. The file streams as text/csv with a sensible filename like cold-outreach-q3-approved-accounts-2026-05-24.csv.
Pagination + search across long lists — past row 20 is now reachable
Section titled “Pagination + search across long lists — past row 20 is now reachable”Six dashboard surfaces used to silently truncate at 20 rows: Campaigns (/dashboard/campaigns), Projects (/dashboard/projects), Channels (/dashboard/channels), Tasks (/dashboard/tasks), and the campaign / channel expense panels. None of them showed pagination controls, so a company with 30 campaigns / 25 channels / 100 tasks looked correct but quietly dropped the bottom of every list. Each page now carries Next / Previous controls and the StatCards at the top read off /statistics/ endpoints so totals are right regardless of which page you’re on. The Tasks page also now searches server-side, so typing a name finds matches in any page. The Cmd+K / search bar got the same treatment — typing now searches every account, campaign, and product in the project on the server instead of client-filtering the first 20 of each. If you’ve been hunting for a row that “should be there but isn’t showing up”, this is why.
Cancel works during the “Preparing accounts” phase too
Section titled “Cancel works during the “Preparing accounts” phase too”The Cancel run button used to only respond once the worker had finished scraping every account website and started AI scoring batches — on a 500-account campaign that could be 5–10 minutes of unresponsive cancel. Now there are cancel checkpoints inside the website-scraping and AI-fill phases too: clicking Cancel halts the worker at the next safe boundary (one in-flight HTTP request, ~15s — or up to a ~3-minute Gemini call’s timeout if the worker was deep in an AI-fill call). Whatever content was already scraped lands in the cache, so retry skips that work and only does the unfinished part.
Stuck scoring runs now self-recover faster
Section titled “Stuck scoring runs now self-recover faster”The orphan-resume sweep gained a second, much faster detector: a run stuck “in progress” for 30 minutes with zero accounts processed is treated as wedged immediately, rather than waiting the full 2 hours. This catches the most common wedge shape (worker deadlocked inside the enrichment phase) 4x faster, so the Score N remaining button reappears for retry within an hour of clicking Score remaining on a doomed run. The 2-hour hard backstop stays as the fallback for slow-grind wedges that DO write progress but won’t ever finish.
Stuck scoring runs now self-recover within an hour
Section titled “Stuck scoring runs now self-recover within an hour”Previously, if a scoring worker crashed in a way that prevented its own error handler from running (hard time-limit SIGKILL, out-of-memory, or a deep network hang in the enrichment phase), the campaign would sit at “AI scoring in progress…” forever — and the only way out was the Django admin or a direct API call. There’s now an automatic safety-net sweep on the server: any scoring run stuck “in progress” for more than 2 hours is flipped to “failed” with an explanatory note, so the Score N remaining button reappears for retry. The sweep also runs immediately on every server restart, so deploys clear wedged runs without delay.
Live scoring progress banner on the campaigns list
Section titled “Live scoring progress banner on the campaigns list”After kicking off scoring (or batch-scoring) you no longer have to refresh the page or click into each campaign to see how far it’s got. The Campaigns list now shows a live banner at the top whenever any campaign in the active view is mid-scoring — aggregate processed / total accounts, pending count, ETA, and the names of the campaigns currently running. Each name links straight to its score-review page. The banner polls every 5 seconds and ticks the ETA every second so it never feels frozen. Disappears once everything is done.
Score remaining accounts on every campaign in one click
Section titled “Score remaining accounts on every campaign in one click”The Campaigns list page (/dashboard/campaigns) now carries a Score remaining (N) button in the top-right action bar — N is the number of active campaigns that have unscored accounts and a target persona set. Click it once and AI scoring fans out across every eligible campaign in your active company. Confirmation dialog up front, summary alert at the end listing exactly which campaigns dispatched, which were skipped, and why (“no target persona yet”, “all accounts already scored”, “already scoring”). Already-scored accounts aren’t re-scored or re-charged. Archived campaigns are never touched. See Can I score remaining accounts on all my campaigns at once?.
Cancel a stuck scoring run from the scoring page
Section titled “Cancel a stuck scoring run from the scoring page”The scoring review page now carries a Cancel run button in the header while a scoring run is in progress. Until today, if a run wedged (deploy interrupted mid-batch, model API stall, worker recycle) there was no way to clear the “AI scoring in progress…” banner from the UI — the other action buttons stay hidden to prevent double-charging, so a stuck run could only be unblocked from the Django admin. Now you click Cancel run, confirm, and the Score N remaining button reappears immediately for retry. Already-scored accounts aren’t re-charged on the follow-up retry. Same action is also available via POST /api/campaigns/{id}/cancel-scoring/ for scripted recovery — idempotent, tenant-scoped. See My scoring run looks stuck — how do I unwedge it?.
Cancel is cooperative: the worker finishes the current 10-account batch before stopping (~10–30 seconds), so the scores already in flight land in the cache and aren’t thrown away. If the worker is mid-website-enrichment when you click Cancel, it’ll stop after enrichment completes — which can take a couple of minutes on a large worklist.
Internal notes per row on the outreach inbox
Section titled “Internal notes per row on the outreach inbox”Each row in Campaigns → … → Outreach now carries a free-text Internal note field. Use it to track per-lead prep context that doesn’t fit into status / tags: “demo live since Monday — wait a few days before pinging”, “warm intro from Ana coming Thursday”, “called once, no answer, try after 4pm”. Notes show as a sticky-note snippet on the row preview so you can scan the queue and spot which leads have context attached, and edit them inline at the top of the conversation pane (saves on blur). The field is per-campaign-account — the same lead in two campaigns gets two independent notes. See Internal notes per campaign-account.
”Override score” button on the scoring review page actually works
Section titled “”Override score” button on the scoring review page actually works”The Override score link on each prospect card in Campaigns → … → Score review opens a dialog where you can set a manual 0–10 score that supersedes the AI score for this campaign only. Sibling campaigns sharing the same product and goal aren’t affected — the AI score they read is untouched. A Clear override button on the dialog reverts back to the AI score. Before today the link logged to the browser console and did nothing; now it does what it always implied. See override_score on CampaignAccount.
Filters on the outreach inbox
Section titled “Filters on the outreach inbox”The campaign-wide outreach page (Campaigns → … → Outreach) now carries a Filters panel below the search box. Narrow the list by review status (approved / pending / rejected), match label (excellent / moderate / mismatch), score range (min/max on the 0–10 AI score), or channel (Instagram, LinkedIn, email, X, WhatsApp, phone, SMS, other — keeps only accounts you’ve touched on any of the picked channels). Pick any combination — they all AND together. The tab badges stay pinned to the true campaign size; only the visible list shrinks. The panel opens with Approved + Excellent pre-selected so you land on the work-this-now queue; clear those pills to widen the view. See Inbox filters.
Scoring progress bar actually moves now
Section titled “Scoring progress bar actually moves now”The in-progress bar on the scoring review page used to jump straight to 100% the moment you kicked off a rescore — especially the “Give feedback & rescore” flow, where every account already had a cached score and the bar had nothing fresh to count. The bar now tracks this run’s progress: 0 at dispatch, ticks up as each batch lands, lands at 100% when the run finishes. ETA recalculates against the same per-run number, so it stops claiming “less than a minute” the entire time. No action needed — just refresh the scoring page.
Exclude a campaign from global stats
Section titled “Exclude a campaign from global stats”The campaign detail page has a new Aggregate stats row with an Exclude this campaign from global stats checkbox. Tick it for experiments, calibration runs, or one-off imports of pre-existing customer relationships — their outreach and replies stop counting toward the Dashboard funnel, cohort rates, the High-ICP reached tile, and the by-product / by-channel roll-ups on Stats by product and campaign. The campaign’s own detail page still shows real numbers; its row on the breakdown table is tagged with an amber Excluded chip so you can spot it. See Exclude from global stats.
”Give feedback & rescore” on the scoring review page
Section titled “”Give feedback & rescore” on the scoring review page”You can now retune scoring without leaving the review queue. The scoring page has a new Give feedback & rescore action that opens a modal with the campaign’s scoring brief — revise the text, hit save, and every account in the campaign is re-evaluated under the new brief. Approvals, rejections, and per-account overrides are preserved; only the AI score and reasons change.
If you’ve ever scrolled the queue and thought “the AI is missing X” — that’s the workflow this enables. Type “accounts must have a physical retail location” or “we’re scoring chains too high”, save, and the next time you scroll the queue every row reflects that guidance.
The modal warns you if other active campaigns share the same product and goal — scores are cached per (product, goal), so a rescore on this campaign overwrites what those siblings show too. The scoring concept page explains the caveat and the workarounds.
Also rewritten: the What the model sees section now lists every input (21 items) in prompt order with size caps, so you can predict exactly what the AI is reading for any given account.
Per-campaign scoring brief
Section titled “Per-campaign scoring brief”Campaigns now carry an optional Scoring brief — a free-text overlay that’s appended to the scoring prompt below the Product value proposition. Use it to tilt the rubric for one campaign without regenerating the target persona: “down-weight chains; prioritise owner-operated stores in cold-climate cities”, “weight community-event capability heavily”, “this is a partnership push targeting accounts that already sell adjacent gear, not end-customers”. Edit it inline on the campaign detail page; the next rescore picks it up.
The brief is framed as “applies on top of the persona above” — it shifts emphasis, it doesn’t replace the persona. If you find yourself wanting to override the persona entirely, the right move is still to revise or regenerate the target persona.
Editing a Product’s value proposition now feeds scoring directly
Section titled “Editing a Product’s value proposition now feeds scoring directly”Until today the Product description only influenced scoring through the target persona — meaning a sharpened value-prop didn’t reach the scorer until you regenerated the persona. The scoring prompt now carries the Product’s name and description on every score, so an edit to the description shifts results on the next rescore without a persona regen. Example URLs remain the single biggest calibration lever; the description sits below them.
Imports no longer stall on rare unsafe characters
Section titled “Imports no longer stall on rare unsafe characters”Enrichment runs against websites that occasionally serve text with characters the database can’t accept verbatim — typically binary content slipped past the page-type check, or odd unicode escapes returned by the AI extraction step. A single tainted record used to fail the whole batch, the import would retry on the same bytes every minute, and progress would sit pinned near the top end of the bar until the run eventually gave up.
The enrichment pipeline now strips those characters before saving so the rest of the batch lands cleanly. No action needed on your side. If you had an import wedged on the old behavior, hit Retry on the Tasks page and it’ll resume from where it stopped.
Channels, customer value, and ROI per channel
Section titled “Channels, customer value, and ROI per channel”Inbound now has its own home. Acquisition channels (Adwords, Meta, LinkedIn Ads, organic search, organic social, referral, event, partner, cold inbound) are first-class objects with their own monthly spend, funnel counts, and customer-value rollups — separate from Campaigns, which stay focused on outbound efforts. Each channel exposes a Cost summary (per-currency totals, primary currency, CAC per acquired / responded / closed), a Value summary (captured lifetime value across every customer attributed to the channel, grouped by source), and a ROI ratio that drops the math when currencies mismatch instead of producing misleading numbers. See Acquisition channels.
Lifetime value tracking landed in the same change. Set a Default account value on a Product, override at the Campaign level for higher- or lower-value segments, or enter the actual deal value on individual Accounts at close. The chain auto-snapshots when status changes to customer and operator-entered values are sticky — future product price changes never retroactively rewrite historical customer values. The captured value powers the per-channel and per-campaign ROI rollups, plus a new by-channel column on the dashboard breakdown. See Account value and the how-to guide.
Ad spend that used to live on Campaigns (adwords, meta_ads, linkedin_ads, other_ads kinds) was migrated automatically to the channel surface during the rollout — every historical row is preserved, just attached to a Channel instead of a Campaign. Campaign expenses now only carry agency / labor / software / content / event / other.
Conversation messages no longer require a Campaign membership. An inbound lead from Adwords that hasn’t been enrolled in any outbound campaign can still have their email reply logged on the Account directly — the message scopes to the Account and the campaign attribution is optional. Existing logged messages were backfilled automatically.
Tasks page now shows per-run AI cost and token usage
Section titled “Tasks page now shows per-run AI cost and token usage”The Tasks page now carries a Cost column next to “Started At” — every row shows the USD spend on AI tokens and external API calls (Google Maps Places, Tavily) for that run, plus the total tokens used:
$0.0234 · 12.4k tokHover the cell for a per-model breakdown — useful when you want to know whether a costly run was the cheap Gemini lite model running a lot of cheap calls, or a single batch that swapped in a more expensive model. Two-line tooltip shape:
Total: $0.0234 · 18 callsTokens: 11.2k in / 1.2k out
google-gla:gemini-3.1-flash-lite: $0.0180 · 12 calls · 11.2k tokgoogle_maps: $0.0054 · 6 callsThe number is actual spend, not a forecast — it grows live while a run is in progress and lands at its final value when the run completes. Sort the Tasks page by Cost desc when auditing AI spend, or after a long enrich batch to spot the one outlier that cost ten times the rest. Past runs that pre-date AI cost tracking show — (we don’t have data for them); every new run is tracked.
This is the same per-model rollup that already lived on the Django admin Research detail page; now it’s surfaced on the operator-facing Tasks page so you don’t need admin access to see it.
Download the docs as an offline manual — EPUB, Kindle-sized PDF, or A4 PDF
Section titled “Download the docs as an offline manual — EPUB, Kindle-sized PDF, or A4 PDF”These docs now ship as a downloadable manual in three formats, all generated from the same source so the content stays in sync. Grab the one that fits your reading setup:
- EPUB (~140 KB) — the right answer for any e-reader. Reflowable text scales to the screen, so it’s readable on a Kindle, Kobo, Boox, Apple Books, or your phone’s reading app. Kindle users: Amazon’s Send to Kindle (email, web uploader, mobile share, or browser extension) accepts the EPUB directly and converts it on their end. No DRM.
- Pocket-edition PDF (~2.6 MB, 5.5”×8”) — fixed-layout PDF in mass-market paperback dimensions, sized for 6-7” e-ink readers (Kindle, Kobo, Boox) so text is readable without zooming. Use this if you prefer PDFs over EPUBs or your reader doesn’t reflow them well.
- PDF for A4 print or laptop reading (~2.5 MB, A4) — the standard letter-paper variant. Best for printing a section to mark up, or reading on a tablet / laptop.
Each format has a cover page, table of contents, and every concept / how-to page as a chapter. The PDFs are regenerated on every docs deploy; the EPUB is regenerated when a docs change ships. No login required.
Wrong-kind URLs are auto-rediscovered, and Research runs show what they spent
Section titled “Wrong-kind URLs are auto-rediscovered, and Research runs show what they spent”If an imported account’s website field turns out to be an audio stream, a PDF, a download link — anything that isn’t a webpage — enrichment now detects that on the first byte of the response, skips the 15-second timeout the old code suffered, and runs a rediscovery pass to find the company’s real homepage. Free hints come first: many streaming servers expose a homepage URL in their headers (Shoutcast/Icecast icy-url), and that hint wins when present. If there’s no hint, the website-discovery agent runs and proposes a candidate URL, which gets verified against the same first-byte check before the swap happens. The original URL is preserved in the account’s notes with a one-line audit explaining what it was (audio stream / PDF / …) — nothing is silently lost. Particularly useful for radio-station / podcast / streaming-service imports where the source CSV stamped the playback URL instead of the company website.
The Research admin detail page now carries an API cost rollup field showing every model that ran during the research, with per-model call count and dollar cost, plus a separate Enrich result field with a per-run audit table — input accounts, sites scraped, fields filled, LLM-fallback fills, URLs rediscovered, duration. Same dashboard you already get for campaigns under “Cost summary”, now scoped to one research run instead of one campaign. Most operators won’t notice the change; if you’re triaging “this run took an hour, what did it actually do?” this is where you look.
Enrich button now has three modes — and the Tasks page shows real progress
Section titled “Enrich button now has three modes — and the Tasks page shows real progress”The Enrich button on the Accounts page (next to Import / Export / Find Duplicates) now opens a small dialog with three radio choices: Only non-enriched accounts (default — skips every account that already has scraped website content), First 50 (test run) (a cheap smoke test against the 50 oldest non-enriched accounts before you commit to a multi-hour project-wide pass), and Currently filtered accounts (re-applies the page’s filter set — search, status, country, business type, the advanced filter DSL — and enriches exactly those rows). The “currently filtered” mode is what you reach for when you want to enrich every prospect in Spain before launching a campaign, or every account with website is empty so the discovery agent has a chance to find one.
The Tasks page now renders a real progress percentage instead of parking every in-flight run at 50% until it finishes. Tasks update every few seconds with rows-processed / rows-total so a long enrich actually looks like it’s making progress.
Large enrich runs are now safe across deploys
Section titled “Large enrich runs are now safe across deploys”Enrichment runs that take longer than a worker’s lifetime — a 4,000-account batch can easily take an hour or more on a slow scrape day — used to risk losing in-flight work if LeadHunter redeployed mid-run. The current pipeline slices large jobs into chunks small enough to each finish within the worker’s time limit, persists the chunk plan, and auto-resumes any chunk that gets interrupted. A redeploy in the middle of an enrich no longer parks the task at “in progress” forever; the new worker picks up where the old one left off automatically. An orphan-sweep also runs hourly as a steady safety net, so even silent worker deaths get cleaned up without operator action.
Behind the scenes the broker (Redis) now persists every queued task to disk and the worker only acknowledges a task once it actually finishes — so a SIGTERM mid-task re-queues for the next worker instead of being silently dropped.
Accounts now carry socials, year founded, and a headcount bucket
Section titled “Accounts now carry socials, year founded, and a headcount bucket”Three structured fields landed on every account — beyond the free-form business_type you already had — to make segmentation and outreach cleaner. year_founded is a numeric year you can filter on (“companies established 10+ years”); employee_count_estimate is one of five buckets (1-10, 11-50, 51-200, 201-1000, 1000+) and feeds into the scoring rubric so a 5-person shop and a 5,000-person enterprise are scored against the right shape; and six per-platform social URLs (linkedin_url, instagram_url, x_url, facebook_url, youtube_url, tiktok_url) replace the old free-text “Social:” line in the notes field. The account detail page now renders the socials as clickable chips, and the top stat row shows Founded + Employees alongside Business Type. CSV imports recognise common header variants — LinkedIn, IG, Twitter, Founded, Employees, Company Size — so a re-import from another CRM doesn’t need user-side column renames. Auto-enrichment fills all seven when the website states the value; manual edits are preserved on re-enrich. See Account → Company profile.
Big imports now survive a single bad row, and you can undo them in one click
Section titled “Big imports now survive a single bad row, and you can undo them in one click”Large CSV / XLSX imports (anything above ~2,000 rows) now run in chunks of 2,500 rows, each in its own database transaction. Before, a single rejected row in a 50k-row file would roll back every row that had already been imported — you’d start over from scratch. Now, only the chunk containing the bad row rolls back; everything that landed before it stays. The result panel tells you exactly which chunks committed and which didn’t, with the row numbers of the offending lines so you can fix them in the source.
Numeric fields are now also clamped pre-import instead of crashing the chunk. The previous behavior: if the AI column-mapper picked a “Votes” or “Bitrate” column as your rating field (because both are integers), every row whose value exceeded the rating’s 0–9.9 range hit a database overflow and rolled the chunk back. Now those values just become blank in the imported row and land in the same amber truncation summary that already covered over-long names and URLs (rating: 41,086 · language: 126 · name: 17). The import succeeds; you decide whether to fix the mapping and re-import, or to leave the misMapped column blank.
We also added an “Undo this import” button on the import result. One click, one confirmation, and every account that landed from the wrong file is gone — together with any campaign memberships, conversation messages, and scores that got attached to those rows. The import history record stays as an audit trail; only the account rows are wiped. It uses the file’s MD5 fingerprint (stamped invisibly on every imported row) to find exactly what to delete, so it never reaches into accounts entered manually or imported from a different upload. Destructive and cannot be reversed — most operators want this right after an oops; after you’ve started using the data, fixing duplicates one-by-one in Accounts → Duplicates is usually safer.
Dashboard now says “Target persona” everywhere
Section titled “Dashboard now says “Target persona” everywhere”The shift from “Ideal Customer Profile” → “Target persona” started in the public docs and the Django admin a few releases back; the dashboard UI has now caught up. Page titles, form labels, error messages, empty states, and the workflow stepper all use the goal-neutral “Target persona” wording — sales prospecting still works the way you remember, but a press, recruiting, or investor campaign no longer has a sales-shaped label staring back at you. The buttons read “Generate target persona with AI” / “Regenerate target persona” / “Define persona”; the campaign detail page heading reads “Target persona”; the new-campaign wizard step reads “Define persona”. The score-review page header reads “Score review” instead of “ICP Model Validation”.
The change is copy-only — every URL, API endpoint, and saved-filter field still uses the same icp_* names. The stable “High-ICP Reached” StatCard label is unchanged so the metric stays recognisable next to the older docs and reports.
Goal-change controls + audit trail on the campaign detail page
Section titled “Goal-change controls + audit trail on the campaign detail page”The campaign goal is now a click target, not just a label. On the campaign detail page, the goal chip beside the campaign name opens a Change campaign goal modal: pick from the eleven goals (the current one is flagged), and a short reason is required so the audit trail explains why the change happened. The picker shows the goal’s one-line hint inline so a switch from sales → press is grounded in what each goal actually changes (ICP framing, scoring rubric, outreach intent, guardrails).
Every change lands in a new Goal history accordion that sits right above the stale-score banner — collapsed by default, mirrors the status-history pattern from the Account page. Each entry shows from → to as colored chips, the timestamp, the source (api / signal), and the reason in italics. Useful when months later you wonder “why does this campaign show stale scores?” — a goal flip is usually the answer, and now it has a paper trail.
Outreach by goal — breakdown table on the campaigns list
Section titled “Outreach by goal — breakdown table on the campaigns list”The Outreach by goal table now sits below the top-of-page stat cards on the campaigns list. One row per goal you’ve actually run a campaign at, with campaigns / accounts / outreached / responses / response rate per goal, sorted by outreached count descending. Response rates ≥5% render in green, between 0 and 5% in amber, zero in muted. The breakdown stays hidden until you have something other than the default sales goal in flight (so it doesn’t add noise for single-goal users).
This complements the existing per-product / per-campaign breakdown on /dashboard/stats — the campaigns-list table answers “which goal is doing the work this quarter?” at a glance, while the per-campaign breakdown drills into individual campaign performance.
Default LLM moved to the GA Gemini 3.1 Flash Lite
Section titled “Default LLM moved to the GA Gemini 3.1 Flash Lite”The platform’s default model has shifted off the -preview channel onto the now-GA gemini-3.1-flash-lite. Same architecture, same per-token price, but on the stable production endpoint that won’t be retired without notice. Cost reports under Usage and costs will show the new model name on calls fired after the rollout. Two internal agents (Tavily-backed deep research, and the website-discovery fallback) that had been pinned to a hardcoded model name now also follow DEFAULT_LLM_MODEL, so a single env var changes every agent’s model in lockstep.
URL lookup now works on more bot-protected sites
Section titled “URL lookup now works on more bot-protected sites”The Look up a website flow had been quietly degrading on a slice of sites — mostly Spanish car-trade, real-estate, and travel directories — that ship aggressive bot-protection. The lookup would fall back to a plain HTTP fetcher behind the scenes and get bounced with a 403. Two fixes shipped together: the headless browser is now actually installed in the production image (it wasn’t, on the deploy that introduced this regression), and the plain-HTTP fallback now sends the full set of headers a real Chrome / Firefox / Safari sends — including HTTP/2, Brotli compression, and Client Hints — with a one-shot retry from a different browser family on the “forbidden” responses bot-protection vendors use to fingerprint cheap scrapers. Sites that previously refused us now load and extract correctly.
Contract end date input on the Edit Account form
Section titled “Contract end date input on the Edit Account form”The contract_end_at field — already wired into saved-filter DSL and CSV imports — now has a first-class form input. Edit any account and a new Contract end date field (datetime picker) appears at the bottom of the form. Setting it opens up renewal-flow filters like “customers whose contract_end_at is in the next 60 days” without needing an import or an API call. Helper copy under the field reminds you it drives renewal-goal campaigns.
UI controls for per-purpose DNC and per-campaign role override
Section titled “UI controls for per-purpose DNC and per-campaign role override”Two recently-shipped backend features (per-purpose opt-outs and per-campaign role overrides) previously needed an API call to drive. They now have first-class controls on the existing detail surfaces:
- Per-purpose opt-outs, on the Account detail page (right column, between Relationship Status and Relationship Type): current opt-outs render as colored chips for the goals the account opted out of (sales / press / recruiting / …) — click the X on any chip to opt back in. “Record an opt-out” opens an inline picker with goal + source (manual / inbound request / import) + reason; saving appends an audit-trail entry visible in the new accordion below the picker. The blanket
do_not_contactstatus above is unchanged — the new panel is the goal-scoped layer for the GDPR / CASL case where an account opted out of one purpose but is still happy to engage on another. - Per-campaign role override, on the CampaignAccount row (in the campaign’s accounts panel, beside the score and outreach chips): a compact chip shows the override when set (typed in the relationship-type’s color); when unset, a faint dashed
+ roleaffordance lets you set one. Clicking opens a popover with all 13 relationship types — pick one to tell this campaign’s outbound drafter to treat the account through that lens regardless of its other tags. Useful when one Account plays multiple roles across campaigns (a customer who’s also a podcast guest who’s also a research participant). “Use the account’s tags (no override)” clears it.
Goal-aware UX shipped — every campaign surface adapts to the goal you picked
Section titled “Goal-aware UX shipped — every campaign surface adapts to the goal you picked”The campaign goal you set when creating a campaign now reshapes a lot more than just the AI prompts:
- The new-campaign wizard reorders. Goal is the first field on step 1 (it’s the most defining decision — it changes ICP framing, scoring, message intent, guardrails). The page hero adapts: sales reads “Let’s find your next customer”, press reads “Let’s get your story covered”, recruiting reads “Let’s source your next hire”, win-back reads “Let’s win back what was lost”, and so on for all eleven goals. Step 2 (“Define ICP”) similarly drops the sales-only heading in favor of “Define your <goal-label> target”.
- Bulk-add explanations name the goal. When a bulk-add hits a guardrail, the response now includes a goal-aware reason per blocked bucket: a Sales campaign tells you “this Sales campaign soft-blocks press accounts; pass
include_press=trueor switch the goal to Press / Influencer”. A Press campaign would never produce that message because press accounts aren’t blocked there. The frontend shows the reason directly under each blocked count. - Stale-score banner. Switching a campaign’s goal mid-flight no longer auto-rescores existing accounts (the cached scores were rated against the previous rubric). The campaign detail page now shows an orange “X accounts have scores from a different goal” banner with a one-click Rescore link when this happens.
- Cost panel third card. The Costs & expenses panel gains a third CAC card whose label adapts per goal: “Cost per closed-won customer” for sales, “Cost per press reply” for press, “Cost per candidate engaged” for recruiting, “Cost per renewal” for renewal, etc. Same denominator pattern as cost-per-outreached / cost-per-responded; the close count routes per goal (status=customer for sales-shaped goals, first-inbound for press/investor/recruiting/event/research/influencer/partnership).
ICP is now per (Product, Goal) — sales and press personas are independent
Section titled “ICP is now per (Product, Goal) — sales and press personas are independent”Until now the Ideal Customer Profile (now also called Target persona) lived on the Product alone — one ICP per product. With goals, that broke down: a sales campaign’s persona (“decision-makers at SaaS companies 50-200 employees”) and a press campaign’s persona (“publications and journalists covering our space”) on the same product were forced to share one row.
ICPs are now keyed by (Product, Goal). A press campaign of Product X gets its own persona with its own approval state; a sales campaign of the same product gets its own. Two campaigns of the same product and same goal still share one persona — the original “approve once, score once” reuse preserved per goal. Existing single-goal projects see no change (existing personas backfilled to goal=sales).
We also softened the terminology: the model is internally still called ICP for code stability, but admin labels and docs lead with “Target persona” since the goal-aware concept isn’t strictly customer-shaped anymore. See the rewritten Target persona and scoring page.
Per-goal competitor admission — research / event / press can include competitor accounts
Section titled “Per-goal competitor admission — research / event / press can include competitor accounts”The competitor relationship tag used to be a hard block on every campaign. That was sales-shaped thinking: a trade publication that covers your competitors is still a valid press pitch target; a roundtable invite legitimately includes peers; market research routinely interviews competitor employees.
Now competitor is per-goal: hard-blocked in sales / partnership / investor / influencer / recruiting / customer-expansion / win-back / renewal; admitted in press / event / research. personal_network remains the only universal hard block (bulk-blasting warm contacts via a campaign is always wrong).
Account model: contract dates + per-campaign role overrides + contact role kind
Section titled “Account model: contract dates + per-campaign role overrides + contact role kind”Three small but useful Account-side additions:
contract_end_at— optional renewal-cycle date. Renewal-goal campaigns can build saved filters like “customers whose contract_end_at is in the next 60 days”. NULL for accounts not on a renewal cycle (most prospects, all press / candidates / investors). Added to the saved-filter DSL and CSV column-import mapping.role_in_campaign(on CampaignAccount) — optional per-campaign lens. The same Account can be a paying customer AND a press contact AND a research participant; different campaigns view the account through different lenses. When set, the outreach-draft prompt receives a “Role for this campaign: treat this account as<role>regardless of its other tags” directive so the generated message respects the lens.Contact.role_kind— tags how to interpret the existingseniority/department/is_decision_makerfields. The sameseniority='c_level'value reads as “C-Suite executive” for a buyer, “Editor-in-chief” for a journalist, and “Senior career level” for a candidate. Defaultbuyerpreserves all pre-existing contacts. Seven kinds: buyer / journalist / candidate / investor / partner / influencer / other.
Per-goal breakdown on /api/campaigns/statistics/
Section titled “Per-goal breakdown on /api/campaigns/statistics/”The campaign statistics endpoint now returns a by_goal field — same shape as the top-level totals (campaigns / leads / outreached / responses / response_rate) but sliced per goal. “We did 5,000 outreaches this month” mixes sales pitches with press pitches with candidate sourcing; the breakdown surfaces the right slice per goal so analytics make sense. Single GROUP BY query — no extra round-trips.
Goal-change audit trail
Section titled “Goal-change audit trail”Every time a campaign’s goal changes, a {from, to, at, by_user_id, source, reason} entry lands in the campaign’s new goal_history field — same shape as Account.status_history. The PATCH endpoint accepts an optional goal_change_reason body field that gets stamped into the entry. Useful for debugging “why does this campaign show stale scores?” — a goal flip is usually the answer.
Per-purpose do-not-contact — opt-outs scoped to the right campaign types
Section titled “Per-purpose do-not-contact — opt-outs scoped to the right campaign types”Until now, marking an account as “do not contact” was all-or-nothing: blocking sales contact also blocked press pitches, research interview asks, event invites, everything. That’s not how real consent works. A customer who unsubscribes from your sales emails may still be happy to participate in a product research interview. A journalist who asks to be removed from a PR list may still attend an industry event.
LeadHunter now supports per-purpose do-not-contact alongside the blanket flag:
status='do_not_contact'remains the blanket opt-out — covers every campaign goal. Use for the strict GDPR / unsubscribe-everything case.do_not_contact_purposesis a list of campaign goals the account opted out of. An account with['sales']is blocked from sales prospecting campaigns but admissible in press, research, event, partnership, recruiting, expansion, renewal — anything that isn’t sales.
Recording an opt-out is a single API call (POST /api/accounts/{id}/record-dnc/ with {purpose, action}) — it appends a full audit entry to the account’s new dnc_history field. Same paper-trail shape as status_history: who, when, why, where it came from (manual, inbound_request, import). The campaign-add guardrail blocks any campaign whose goal appears in the per-purpose list, in the same blocked_dnc bucket as the blanket case — both layers are sticky, neither is overridable.
Existing status='do_not_contact' accounts behave unchanged. The new field is additive: empty by default, opt in to the granular layer as your consent flows mature. See Account → Per-purpose opt-outs.
Dashboard funnel routes “closed” per campaign goal
Section titled “Dashboard funnel routes “closed” per campaign goal”Until today the dashboard’s closed line counted exactly one thing: an account transitioning to status=customer. That definition only fits sales-shaped goals (sales prospecting, customer expansion, win-back, renewal) — every other goal showed closed: 0 forever, even when the campaign was succeeding.
As of this release the funnel routes per goal:
- Sales, customer expansion, win-back, renewal still close on the customer transition — unchanged.
- Partnership, press, influencer, investor, recruiting, event, research close on the first inbound. For these goals “they replied” is the win — a journalist taking the pitch, a candidate saying yes, a fund booking a meeting.
So a press campaign with one pitch sent + one reply now shows closed=1. A sales campaign with the same shape still shows closed=0 until the account transitions to customer. The initiated and responded columns are unchanged.
The new routing applies to both the daily totals and the cohort views (where outcomes are attributed back to the initiation date). Per-product / per-campaign breakdowns on the Stats by product and campaign page also use the new resolver.
Partnership and customer-expansion currently route to the inbound-response proxy — the more precise signals (partner relationship-type acquired, customer usage expanded) aren’t tracked in audit-log form yet, so we use the next-best signal. See Outreach reasons and targets → Success semantics for the full table.
Goal-keyed scoring — sales and non-sales campaigns no longer poison each other
Section titled “Goal-keyed scoring — sales and non-sales campaigns no longer poison each other”Running a Sales campaign and a Press campaign on the same product? Until today, scoring an account in one would silently overwrite the other — they shared a single score row keyed by (product, account). That was correct under “every campaign is sales” and quietly wrong the minute you mixed goals: the score you saw in your Sales campaign could be a press-fit number labelled as buyer fit, and vice versa.
As of this release, scores are keyed by (product, account, goal). Two campaigns of the same product at the same goal still share one score row (the original “score once” reuse — no extra Gemini cost). Two campaigns at different goals get independent score rows: rescoring one no longer touches the other.
The review-feedback few-shot — your approvals and rejections that calibrate the model — is also goal-scoped now. A press approval no longer contaminates sales scoring, and vice versa. See ICP and scoring → Score caching for the full mechanics.
Migration is automatic: existing scores were backfilled to goal=sales (the only meaningful pre-rollout value). If you’re running multi-goal campaigns on one product and want each goal to have its own scores, just trigger a rescore on the non-sales campaigns — they’ll get their own rows under their goal.
Campaigns now have a goal — sales is no longer the only reason to outreach
Section titled “Campaigns now have a goal — sales is no longer the only reason to outreach”Every campaign now carries an explicit goal that captures why you’re reaching out: sales prospecting (the default), partnership / reseller, press / visibility, influencer, investor outreach, recruiting, customer expansion, win-back / reactivation, event invitation, research interviews, or renewal. Eleven goals in total, covering the reasons businesses actually do outbound.
Picking a non-default goal changes four things downstream:
- The ICP gets reframed. A press campaign’s ICP describes the kind of publications and journalists you want to reach; a recruiting campaign’s ICP describes the candidate profile; a partnership campaign’s ICP describes complementary companies. The structure is consistent so the review-and-revise flow is identical — only the content shifts.
- Fit scoring rates the right axis. A 9 in a Press campaign means “highly relevant outlet, will probably take the pitch”; a 9 in Partnership means “strong shared-audience partner”. The 0-10 scale and the positive/negative/neutral reasons stay consistent so you can sort and review the same way.
- The draft outbound message matches the goal. A press pitch leads with the newsworthy angle, not a sales hook. A partnership pitch proposes collaboration, not a sale. A recruiting note is role-specific and asks for a 15-minute intro. Same product, same account, different message because the goal differs.
- The bulk-add guardrails flip. A Press campaign admits press / influencer / analyst accounts by default — they’re the target. Customer expansion and Renewal campaigns admit customers; sales and win-back keep blocking them as today. Competitor and personal-network accounts stay hard-blocked in every goal, and
do_not_contactis always sticky.
Existing campaigns defaulted to sales on rollout, so nothing changes for in-flight work. Pick the goal when you create a new campaign — there’s a goal selector on the new-campaign form right under the name field, and a coloured chip on the campaign card and detail header showing which goal is active. Switch the goal mid-flight if the angle shifts; the change re-applies to subsequent bulk-adds (existing scores stay as last computed — rescore explicitly if you want fresh ones under the new framing).
See the dedicated Outreach reasons and targets page for the full per-goal table and a worked example of three campaigns on the same product with three different goals.
Re-imports of the same file now skip everything (and tell you about it)
Section titled “Re-imports of the same file now skip everything (and tell you about it)”The bulk-import wizard learned to recognise files it has already seen.
- Pick a file → the Source label now defaults to the filename (
radio_stations_radiobrowser.xlsxbecomesradio stations radiobrowser). Beats the old'import'default for provenance. Edit it to anything you prefer — once you type, we stop auto-defaulting. - Re-uploaded the exact same file? The mapping step shows a yellow banner up top: “You’ve already imported this exact file” — with the prior import’s date, who ran it, how many rows landed, and how many of those accounts still exist. Cancel if it was a mistake; proceed if it was on purpose (the dedupe stack will skip the duplicates anyway).
- Map the source’s stable id column to
imported_id(Station UUID, CRM Record ID, Customer ID — whatever uniquely identifies a row in the source dataset). Dedupe now has a dedicated Imported ID level at the top of the stack, just below Google Place ID. A re-import resolves here first — even when name + city are missing or different. This is the level that makes monthly CRM re-exports clean: 52,000 unchanged rows skipped in 30 seconds instead of creating 52,000 duplicates because the export didn’t include a City column. - Every imported row now remembers the file’s fingerprint (an MD5 of the bytes). The account detail page can answer “which upload did this row come from?”, and you can slice by the same source even when multiple imports share the same label.
If the file’s been imported and the source has a stable id, a re-import is now safely a no-op — useful for monthly directory refreshes and CRM resyncs. See Import accounts → Imported ID for the mapping details and Deduplication, in order for where this slots into the existing stack.
LLM cost per campaign and per account, broken down by model
Section titled “LLM cost per campaign and per account, broken down by model”The campaign Costs & expenses panel now shows a second card next to your user-entered expenses: LLM & API auto-cost. It’s a USD total of every tracked LLM / Maps / Tavily call the platform fired on the campaign’s behalf, with a per-model breakdown (top four models by spend, plus a “+N more” tail) and a call count.
Two reasons this matters:
- Where the money went, by model. A campaign that used Gemini Flash for scoring and GPT-4o for outreach drafting now shows both lines separately — you can see at a glance whether the GPT-4o spend was worth it. Sub-cent totals format with four decimals so $0.000456 doesn’t read as $0.00.
- Per-account enrichment cost. Opening an account now shows its own auto-tracked LLM cost on the detail payload — answering “how much did this one row cost to enrich?” (website discovery + AI fill-in + any single-lead rescores). Detail-only, so the accounts list endpoint stays fast.
CAC is still computed from the user-entered side only — the auto-tracked side is reported in USD next to it, not folded in (your manual expenses may not be in USD, and we don’t FX-convert). The two cards together answer the “what’s this campaign actually burning?” question. Full picture in Usage and costs.
Big account imports run in the background, with live progress
Section titled “Big account imports run in the background, with live progress”The Accounts → Import wizard used to wait for the server to finish before you got a confirmation, which meant a 50k-row file could time out mid-flight and the modal would lie about how many rows it actually saved.
Three changes fix that:
- Files above ~2,000 rows hand off to a background worker, so the request returns immediately. The modal stays open with a live progress bar that ticks every few hundred rows; you can close it and the import keeps running. The result panel appears automatically when it finishes (or you can revisit it via the Tasks page).
- Over-long values are truncated to fit the column instead of failing the whole row. Each Account field has a max length (Name 255, Website 500, Language 50, etc.). Previously, one row with a 200-character
Languagevalue would silently roll back the entire 50k import. Now the value is shortened, the row imports, and the result panel shows a per-field count of what got trimmed. - Database-rejected imports are loud, not silent. If the batch fails for a reason truncation can’t fix, the result panel turns red, says “Import failed — nothing saved”, and lists the actual error so you can fix the source file. Counts that would have landed (had it succeeded) are struck through so the number can’t be misread.
Plus: a new Enrich accounts after import checkbox on the mapping step queues each imported account for website discovery + scrape (logo, email, phone, social links, site language) once the rows land. It’s off by default — tick it for trusted lists where the post-import enrich is worth the Gemini cost; leave it off for big directories. The enrich shows up as its own job on the Tasks page. Full walkthrough in Import accounts.
High-ICP reached stat on the dashboard and per campaign
Section titled “High-ICP reached stat on the dashboard and per campaign”A new High-ICP Reached card now sits next to the outreach funnel on the home dashboard, and next to the campaign stats on the campaign detail page. It answers a question that was previously a manual cross-reference: of the accounts the AI flagged as excellent matches (fit score 8 or higher), how many have actually been worked?
- On the dashboard (whole company): denominator is every distinct account with at least one excellent score in any product; numerator is the subset whose lifecycle status has moved past prospect (so
contacted,in negotiation,customer, orlost). A low percentage means there’s untapped high-fit inventory sitting in prospect waiting to be reached out to. - On a campaign: same idea but campaign-scoped. Denominator is the excellent-match accounts in that campaign; numerator is the subset whose campaign outreach status is
sent,bounced, orresponded. Useful for noticing when a campaign has scored well but stalled before outreach.
Alongside each percentage we now show an ETA to 100% — a rough projection of how many days you’d need to reach every high-ICP account if you keep going at your last-30-day pace. Hover the ETA for the math: remaining ÷ (last-30d-newly-reached ÷ 30). If you reached 6 new excellent-match accounts in the last 30 days and have 60 left, that’s about a year at this pace. ETA shows — when the last-30-day count is zero (no progress = no projection); restart outreach to see a real number again. Use it as a kick-yourself dial: a fast-moving campaign needs days, a stalled one needs years.
Remove low-fit accounts from a campaign in one click
Section titled “Remove low-fit accounts from a campaign in one click”After a campaign scores its accounts, you’ll often want to clear the low-fit tail before working the list — there’s no point spending outreach time on accounts the model already flagged as a poor match. The Campaign accounts panel now has a Remove low-fit button next to the In this campaign count. Click it, confirm the preview count, done.
What it removes: accounts whose AI fit score landed in the mismatch band (score < 5.0). Anything moderate or excellent stays untouched. Unscored accounts are left alone — they haven’t been judged yet.
What it keeps by default (so you don’t lose work):
- Accounts you’ve manually approved — your judgment beats the model’s.
- Accounts where outreach has already started (sent, scheduled, responded, bounced) — removing them would orphan the conversation thread.
The preview dialog tells you exactly how many are kept on each axis before you commit. The removal hits this campaign only; the same account in a second campaign of the same product is untouched. Details in Campaign → Pruning the low-fit tail after scoring.
Country picker on the filter builder
Section titled “Country picker on the filter builder”The Country filter on the accounts list is now a proper dropdown of country names instead of a free-text input. Pick from the list, no more typos or “Spain” vs “España” vs “ES” mismatches breaking the same filter across teammates. The previous free-text behaviour is retired; saved filters with old free-text country values keep working, but new filters use the picker.
Available operators tightened to match the picker: is / is not / is set / is empty. Comma-separated multi-country selection still works via the is any of operator in the filter DSL (use the DSL editor for that case).
Score distribution chart on campaign pages
Section titled “Score distribution chart on campaign pages”After a campaign finishes scoring, its detail page now shows a 10-bucket histogram of fit scores (0-1, 1-2, …, 9-10). It’s the fastest way to read whether the ICP is sharp — a healthy distribution leans bimodal (excellent + mismatch piles with a modest moderate middle); a flat distribution usually means the Product description or example URLs need work. See Campaign → Score distribution.
Archive and delete on accounts
Section titled “Archive and delete on accounts”Accounts can now be archived (with an optional free-text reason) or deleted from a new Danger Zone card at the bottom of the account detail page.
- Archive soft-hides the account from default lists while keeping every reference intact — campaign rows, scores, conversations, expenses, audit trail. Use it when the row still belongs in your history but you don’t want it cluttering daily views: the business closed, merged into a competitor, pivoted out of your market. The reason (“business closed Q1 2026”, “merged into X”) shows on the account header and in the audit trail. Unarchive is one click — no data loss.
- Delete is permanent — the row and every related campaign row, score, conversation, contact, and expense reference are removed. Requires typing the exact account name to confirm, since there is no undo.
Archive is the right answer for almost every “I want this out of my view” case; delete is for genuine mistakes (a test row, a typo, a manual data-entry slip the merge flow can’t fix). Details in Account → Archive and delete.
Dashboard funnel: tighter “Closed (won)” + deep-link to accounts
Section titled “Dashboard funnel: tighter “Closed (won)” + deep-link to accounts”Two related improvements to the outreach funnel on the dashboard home.
Click the funnel cards. The three card titles — Initiated, Responded, Closed (won) — are now links into the accounts list, filtered to the matching status (e.g. Closed (won) → customer + lost). So you can go straight from “1 won this month” to which one.
A win has to stick. Closed (won) used to count every to=customer entry in an account’s history, even when the operator immediately flipped the account back to prospect (a typical “oops, wrong account” pattern). Now the metric only counts transitions where the account’s current status is still customer or lost. Reverts no longer inflate the number.
Recording a pre-existing customer. When you change an account to Customer, you’ll see a checkbox: “Mark as pre-existing customer.” Tick it when the relationship already existed (you’re loading them from a spreadsheet, they were a renewal client before LeadHunter, etc.). The audit trail still records the change, but the dashboard’s Closed (won) count skips it — so importing your existing book of business doesn’t fake-inflate your win rate. Details in Account → Recording a pre-existing customer.
Track campaign costs, with cost-of-acquisition stats
Section titled “Track campaign costs, with cost-of-acquisition stats”Every campaign now has a Costs & expenses panel where you log everything you spent producing the campaign’s outcomes — Adwords spend, agency fees, internal labor hours, software subscriptions, content production, event costs. Each entry carries amount + currency + date + vendor + optional description. Ten kinds covered.
Three ways to add a line item:
- The inline form on the campaign detail page’s Costs & expenses panel.
- The 💸 quick-add button on every row of the campaigns list — opens a modal pre-filled for that campaign so you don’t have to open it first.
- (Programmatic ingestion via an automation tool is also possible if you need it; ask support.)
Existing entries are editable inline from the panel — click the pencil on any row, change the values, save in place.
LeadHunter then computes cost-of-acquisition (CAC) automatically:
- Cost per outreached account — total spend ÷ accounts reached out to.
- Cost per responded account — total spend ÷ accounts that replied.
- Cost per closed (won) account — visible on the Stats by product and campaign page in a new Cost column that shows total spend + the most meaningful CAC available for that row (closed → responded → initiated, in that order).
The campaigns list also has a Cost column showing each campaign’s running total in its primary currency.
Mixed currencies show per-currency totals without forcing a conversion — there’s no FX layer, so single-currency campaigns get full CAC and multi-currency ones surface a per-currency breakdown with an explanation. See Track campaign costs and CAC.
Track inbound leads from Adwords, Instagram, and referrals
Section titled “Track inbound leads from Adwords, Instagram, and referrals”Every account now carries an acquisition channel — the marketing channel that brought them in (outbound default, plus adwords, meta_ads, linkedin_ads, organic_search, organic_social, referral, event, partner, cold_inbound, other). LeadHunter was originally built around outbound; you can now log everything else with proper attribution.
Three places this shows up:
- Accounts → New account — a dropdown on the form, with a “starts at Contacted” hint when you pick an inbound channel. Inbound accounts skip the
prospectlifecycle stage because the lead has already reached out; the status change is recorded in the audit trail with the channel as the reason. - Account detail page — an Acquisition card on the right column for inline edits, plus a collapsible block showing the free-form acquisition metadata (UTM parameters from an Adwords click, an Instagram thread URL, a referrer’s email — anything useful for attribution).
- Accounts list — a new Channel column with the chip for inbound channels (outbound is hidden so the column stays quiet for the cold-prospecting majority). The filter builder picks the field up automatically.
See Track inbound leads for worked examples covering Google Ads forms, Instagram DMs, and referrals.
Outreach funnel on the dashboard
Section titled “Outreach funnel on the dashboard”The dashboard home now shows the three numbers that actually matter day-to-day: contacts initiated, contacts responded, and contacts closed (won) — each broken down for today, the last 7 days, and the last 30 days. Below that, a 12-month trend chart per metric with 7-day and 30-day moving averages, so you can see whether outreach effort and conversion are trending up or down even on noisy short windows.
Counts are by distinct account, not by individual message — three follow-ups to the same account count once as initiated. “Closed (won)” reads each account’s status history, so a deal that briefly closed-won six months ago still shows up in that month’s bucket even if the account later changed status.
The same numbers split per product and per campaign live on the Stats by product and campaign page — including (as of this release) the per-row cost column described above.
Scoring learns from your approvals and rejections
Section titled “Scoring learns from your approvals and rejections”The ICP Review page (Approve / Reject on each scored account) was always good for filtering — now it also calibrates future scoring. Once you’ve validated about ten accounts on a product, LeadHunter starts feeding your most recent approvals and rejections to the scoring model as concrete examples of “what good looks like here” and “what to skip even if the surface details look fine.” Below that threshold, the few-shot is held back — small samples are noisier than no sample, so the original example URLs stay the only anchor.
Same Account approved in two campaigns of the same product counts once; same Account rescored doesn’t get fed its own past verdict. The signal sticks to the product, so different products keep their own calibration.
Run the same product in different languages
Section titled “Run the same product in different languages”You can now set a communication language on each campaign that overrides the product’s default. Run one campaign of the same product in Spanish for the Spain market and another in English for the UK — without duplicating the product. The campaign’s language drives the AI-generated ICP summary, the score reasons attached to each account, and the outbound messages you draft.
If an individual account has its own language set, that always wins — LeadHunter will write to them in their language regardless of which campaign reaches them. See Campaign → Language for the full fallback chain.
Smarter campaign accounts panel
Section titled “Smarter campaign accounts panel”The campaign’s accounts view now shows in-campaign accounts side-by-side with the available pool. You can compare what’s already in the campaign against what’s still out there without losing context — handy when you’re filling out a campaign in passes rather than all at once.
A new single-account “Add new account” shortcut lets you slot in one specific company on the spot, without going through bulk-add or import.
Reach estimator on saved filters
Section titled “Reach estimator on saved filters”When you build a campaign from a saved filter, LeadHunter now tells you exactly how many accounts the filter will pull in before you commit. The estimator works against the real backend (not a placeholder) and respects all the outreach guardrails — so the number you see is the number that will actually land in the campaign.
Inline campaign renaming
Section titled “Inline campaign renaming”You can now rename a campaign directly from its detail page — one click, no extra modal. Useful when a campaign’s scope shifts mid-run and the original name no longer fits.
ICP “Pending approval” → “Approve” button
Section titled “ICP “Pending approval” → “Approve” button”When LeadHunter generates an ICP for your product, it sits in a Pending approval state. You’ll now see a clear Approve button on that state — review the draft, click approve, and the campaign moves on. Previously this was a small “Save” button buried in the form.
Bug fix: campaign rename no longer crashes the page
Section titled “Bug fix: campaign rename no longer crashes the page”A bug that broke the page for some users when renaming a campaign in production has been fixed.
Earlier in May 2026
Section titled “Earlier in May 2026”Lead is now Account
Section titled “Lead is now Account”Lead has been renamed to Account. Same row, broader meaning: one record per organisation, whether it’s a prospect, a customer, a partner, a press contact, a supplier, or your own personal network. Calling all of those “leads” never fit — and it bled into the UI (“Mark lead as customer” felt off when the lead had been a customer for two years).
You’ll see the new name in the dashboard sidebar and the URL (/dashboard/accounts/). The product itself is still called LeadHunter — that’s the verb (hunting leads, finding leads), not the entity. Full concept: Account.
Relationship types and outreach guardrails
Section titled “Relationship types and outreach guardrails”Accounts now carry relationship types — a multi-select describing what each account is to you (e.g. customer, partner, press, supplier, competitor, candidate). One account can be more than one thing at once.
When you bulk-add to a campaign, LeadHunter now blocks competitors and your personal network automatically (never overridable), and soft-blocks suppliers, investors, press, analysts, candidates with a per-type “include them anyway” toggle. The dashboard tells you exactly which accounts were blocked and why.
Full list of types and the guardrail matrix: Account → Relationship types and Campaign → Guardrails.
ICP moved to Product
Section titled “ICP moved to Product”The Ideal Customer Profile now lives on the Product instead of on the Campaign. That means two campaigns of the same product share one ICP and one fit score per account — adding an account to a second campaign of the same product doesn’t trigger a re-score.
Need a fundamentally different audience for the same product? Create a second product with its own ICP. See Company and Product.
Real campaign outreach inbox
Section titled “Real campaign outreach inbox”The campaign view’s outreach tab is now powered by real data (it used to be a placeholder). You can see every conversation thread in a campaign at a glance, sorted by what needs your attention.
Archive vs delete on campaigns
Section titled “Archive vs delete on campaigns”Archive soft-hides a campaign while keeping every conversation, score, and account link intact — for when a campaign is “done” but you want the history. Delete is now name-confirmed: you have to type the exact campaign name to delete, which cascades to the campaign’s accounts and conversations. No more accidental deletion.
April 2026
Section titled “April 2026”Custom fields and saved filters
Section titled “Custom fields and saved filters”Define per-company custom fields for whatever your business tracks — license tier, contract renewal date, fleet size, certifications, anything. Combine them with standard fields in saved filters to query your account database however you need (e.g. “customers in Spain whose contract renews this quarter and whose tier is Gold”).
Filters can be private (yours) or shared with your team. Use a saved filter to bulk-add a slice of your database to a campaign in one click. See Custom fields and Build a saved filter.
Global Company switcher
Section titled “Global Company switcher”The Company switcher in the dashboard top bar is now persistent across pages — your active company stays selected as you navigate, and the choice survives a browser restart.
Manual communication log + auto-translate
Section titled “Manual communication log + auto-translate”LeadHunter became a communication log — paste outbound and inbound messages from your normal channels (email, IG, LinkedIn, WhatsApp, phone) and the AI keeps the per-account history straight, with auto-translation and AI drafting in five modes. See Messaging and Log a conversation.
Relationship status lifecycle
Section titled “Relationship status lifecycle”Every account carries a single lifecycle status (prospect, contacted, in_negotiation, customer, lost, do_not_contact). Sending an outbound message auto-promotes a prospect to contacted; logging a customer event auto-promotes to customer. do_not_contact is sticky and survives merges so opt-outs stay opt-outs. See Account → Status.
On the roadmap
Section titled “On the roadmap”Known gaps that are referenced elsewhere in these docs as “coming soon”. Listed here so they’re visible in one place. No firm dates.
- Plan / quota enforcement — usage is tracked and budget alerts now warn the team at 80% / 100% of a monthly budget, but nothing blocks operations at a threshold yet. Until enforcement ships, the per-provider dashboards (Google Cloud, OpenAI, Anthropic, Tavily) remain the hard backstop.
When these ship they’ll move from this section up into the chronological list.
The notes above are the user-facing version of every change you’d feel using the product. Questions about a change? Get in touch.