What's new
A running log of changes you can feel in the product. Most recent first.
May 2026
Section titled “May 2026”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.
- Usage page — a first-class operator UI for reading auto-tracked API costs (LLM tokens, Google Maps, Tavily) side-by-side with the user-entered Campaign expenses. Today the user-entered side is fully surfaced on the Stats by product and campaign page; the auto-tracked breakdown is recorded but not yet shown. See Usage and costs.
- Self-serve team management — invite teammates by email and change roles from a Settings → Team page. Today, adding teammates is support-driven. See Team and companies → Adding a teammate.
- Plan / quota enforcement — the platform tracks usage but doesn’t block operations when usage hits a threshold. Until that ships, the per-provider dashboards (Google Cloud, OpenAI, Anthropic, Tavily) are the source of truth for “am I about to overshoot my own budget?”
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. The full engineering-level changelog lives in the repository for the curious.