What Nortinia Sales AI does end-to-end on a lead
Nortinia Sales AI is not a chatbot, and it is not a list-import script. It is a closed-loop orchestrator that drives a lead through its entire life â from the moment a row arrives via CSV upload or a webform POST, to the moment a human sales rep takes over the conversation. This piece walks the full path, names the components, and shows a few real numbers from a mid-tier customer tenant.
1. Ingest
Three entry points: CSV upload from the sales-ai-admin UI, public webform POST (POST /api/v1/leads/intake), or a third-party CRM webhook (HubSpot / Pipedrive / Salesforce). All three land in the same eng_lead_ingest table as normalised JSON. The tenantId is resolved from the Host header or bearer token â never trust client-side input.
At ingest there are three required fields: company name, related URL or email domain, source. Everything else is optional â enrichment fills it in.
2. Web enrichment
The Python FastAPI service (enrichment-svc, port 3021) picks up the lead and launches a Playwright + browser-use session. The steps:
- robots.txt check (if disallowed, drop and use public sources only)
- Landing page scrape (hero copy, CTAs, nav structure)
- About / Team page scrape (headcount signals)
- Tech stack detect (BuiltWith-like heuristics from HTML + HTTP headers)
- LinkedIn company page (public only, no login-walled scrape)
- Recent news (Google News API)
The scraped output goes through a structured LLM extract (gpt-4.1-mini, cost-optimised). Average cost: 0.02 USD per company. Average latency: 38 seconds.
3. Score
14 features feed a gradient-boosted tree (LightGBM) that returns a 0-100 score. The model was trained on 8,000 historical labelled examples and retrains monthly. LLM re-rank only nudges scores in the 60-65 borderline zone â calling an LLM for every lead would be wasteful.
Based on score, the lead lands in a bucket: A (80+), B (60-79), C (40-59), D (below 40 â auto-discard, retarget pool only).
4. Assign
The bucket picks the downstream pipeline:
- A bucket: immediate human assignment + email sequence start
- B bucket: email sequence, optional voice call after step 3
- C bucket: nurture content, retarget ad audience
- D bucket: ad-retarget only, no active outreach
Assign logic is tenant-configurable â one customer hands both A and B to humans immediately.
5. Outreach
Three channels run in parallel, all through the engine:
- Email â personalised, generated from enrichment output. Pulls from the brand-voice matrix.
- Voice â outbound calls via Telnyx, permission-based only (see the separate piece on the cold-outbound ethical framework).
- Ad retarget â Meta + Google audience seed (see the separate piece).
Every touch event is recorded in eng_lead_touch and feeds back into the score.
6. Handoff
When the qualified threshold (default: B+ bucket plus at least 2 positive touch responses) is reached, the lead is handed to a human sales rep. The handoff is a Slack DM plus an admin UI ping. The context â full enrichment, touch history, suggested next step â appears on one summary card.
The orchestrator state machine
All of this is driven by an XState-like state machine with 24 transitions. Main states: INGESTED â ENRICHING â SCORED â ASSIGNED â OUTREACH_ACTIVE â QUALIFIED â HANDED_OFF. Side branches: ENRICHMENT_FAILED, OPT_OUT, BOUNCED, STALE. The state machine lives in Postgres and every transition is audited.
Numbers from a mid-tier tenant
One customer, a mid-size B2B SaaS:
- 1,420 leads / week average throughput
- 67% successful enrichment (the rest is either robots-blocked or has too little public data)
- 18% A+B bucket share
- 7.3 days average ingest-to-handoff time
- 0.41 USD average full-pipeline cost per lead (models + Telnyx + ad seed)
What the system does not do
It does not send cold email without opt-in. It does not call unknown numbers. It does not share scores with third parties. It does not buy lead lists. The system only does what the tenant is explicitly authorised to do â and we underline that in every piece for a reason.