Stage 01 of 05
Lead
Capture
Every inbound signal — form, webhook, chat, or API — is validated, AI-classified, and structured in under 2 seconds. No manual triage. No missed leads.
The Pipeline
5 steps from submission to queue
The Intake Bot (B1) is a BullMQ worker that processes every job deterministically. Each step has clear success and failure handling.
Inbound arrives
Any channel — form, webhook, chat widget, or API — sends a JSON payload to POST /api/triggers/lead.
Schema validation
Zod validates the payload. Malformed or missing-email submissions are rejected with an audit log entry (ESC-INT-001) and never touch the database.
Intent classification
Claude Haiku reads the message and classifies intent into one of 5 labels. Spam (>85% confidence) is terminated here — no DB write, no wasted compute.
Entity extraction
Claude Haiku extracts structured data: name, company, role, budget, use case, and timeline. Falls back to raw form fields if the AI call fails.
DB write + queue
Lead record is written to PostgreSQL with all extracted fields. A lead.qualification job is enqueued for the Qualification Bot (B2) to score via BANT.
Capture Channels
6 ways leads enter the system
Every channel posts to the same endpoint: POST /api/triggers/lead. Source attribution is preserved.
Web Form
The leads app (apps/leads) — a standalone Next.js form at a public URL. Submits via Server Action to avoid CORS.
Landing Page CTA
Any button on the landing page that links to the leads form. Source field is set to "landing-cta" for attribution.
Partner Webhooks
HubSpot, Typeform, or Zapier/Make automations POST directly to /api/triggers/lead with field mapping.
Chat Widgets
Intercom, Crisp, or Drift can trigger a webhook on conversation close — contact data flows straight into the intake queue.
Email Parsing
Postmark inbound email webhooks parse the sender + body and POST to the intake endpoint. Source is set to "email".
Direct API
Any system with an x-api-key header can POST directly. Used for B2B partner integrations and white-label channels.
Payload Schema
What the form sends — what the bot stores
Only email is required. The AI fills gaps — it splits names, infers roles, and normalises budgets.
| Field | Type | Source | Notes |
|---|---|---|---|
| string | Form | Primary identifier — required on every submission | |
| name / firstName / lastName | string | Form → AI split | AI splits full name into first + last |
| company | string | Form → AI infer | AI can infer from email domain if absent |
| phone | string | Form | Optional — used by Scheduling Bot |
| role | string | Form → AI infer | AI infers from message context if not stated |
| budget | string → number | Form → AI parse | AI normalises "$20k" → 20000 (USD) |
| message | string | Form | Free-text — primary input for both AI calls |
| source | string | Auto-set | Channel attribution: "leads-form", "email", "api" |
| intent | INT_INQ | INT_PRT | … | AI | Written to DB after classification |
| followUpCount | int (default 0) | System | Incremented by Follow-Up Bot (B7) |
| meetingBooked | boolean | System | Set true by Scheduling Bot (B3) |
| stripePaid | boolean | System | Set true by Payment Bot (B4) on webhook |
The AI Layer
Two Claude Haiku calls per lead
Using claude-haiku-4-5-20251001 for both calls — fast (<500ms) and cheap. The two-call design is intentional: spam is detected in Call 1 and terminated before Call 2 runs, saving tokens.
Call 2 only runs if Call 1 returns a non-spam intent. For a 30% spam rate, this reduces entity extraction costs by 30%.
Intent Classification
Reads the contact's message and classifies it into one of 5 labels. Runs before any DB write. Spam gate fires here.
INT_INQ
Sales inquiry
Wants pricing, demo, or more info about the platform. Default when intent is ambiguous.
INT_PRT
Partnership
Wants to resell, white-label, integrate, or co-market. Must be explicitly stated.
INT_SUP
Support ticket
Existing customer with a bug or technical issue. Redirected to Support Bot — no lead record created.
INT_SPM
Spam
Automated submission, pharma/casino vocabulary, or no real name. Terminated silently (ESC-INT-002) at >85% confidence.
INT_OOS
Out of scope
Unrelated question with no business value. Logged and discarded.
You are an intent classifier for Motus AI, a B2B AI automation platform.
Classify the contact's submission into exactly one of these intent labels:
INT_INQ — Sales inquiry: wants pricing, a demo, or more information about the platform
INT_PRT — Partnership: wants to resell, white-label, integrate, or co-market
INT_SUP — Support ticket: existing customer reporting a bug or requesting technical help
INT_SPM — Spam: automated submission, irrelevant commercial offer, no real name, or keyword spam
INT_OOS — Out of scope: unrelated question with no business value
Classification rules:
1. Default to INT_INQ when intent is ambiguous but shows genuine interest
2. Mark INT_SPM only when confidence > 0.85 — requires strong signals: no real name,
suspicious URLs, pharma/casino/SEO vocabulary, or clearly automated
3. Mark INT_PRT only when the contact explicitly mentions reselling, white-labeling,
integrating, or co-marketing
4. Mark INT_SUP only when the contact references an existing account, mentions
"not working", "error", "broken", "my subscription", or similar support language
Return valid JSON only — no markdown, no explanation:
{ "intent": "INT_INQ", "confidence": 0.92, "reason": "Contact asks about pricing and team size" }Contact name: {name}
Contact email: {email}
Company: {company}
Message: "{message}"
Classify this contact's intent and return JSON.// Expected JSON response
{
"intent": "INT_INQ",
"confidence": 0.92,
"reason": "Contact asks about pricing and mentions their team size"
}Entity Extraction
Extracts 7 structured fields from the free-text message and form data. Fails gracefully — if extraction fails, raw form fields are used as fallback.
You are an entity extractor for the Motus AI sales qualification system.
Extract structured data from the contact's form submission.
Return null for any field not mentioned — never guess or fabricate.
Extraction rules:
- firstName / lastName: split the full name field; if only one word, put it in firstName
- company: use the form value; if absent, infer from email domain (e.g. john@acme.com → "Acme")
only if domain is not gmail/yahoo/outlook
- role: use the form value if present; otherwise infer from message context
("I run the company" → "CEO/Founder", "our dev team" → "Developer")
- estimatedBudget: extract as a number in USD
"$20k" → 20000 | "around 15k/month" → 15000 | "under $50k" → 50000
budget range → use midpoint | return null if not mentioned
- useCase: 3–6 word summary of what they want automated
e.g. "lead qualification automation", "customer onboarding workflows"
- timeline: when they want to start — normalised string
"Q3 2026" | "immediately" | "within 3 months" | null if not mentioned
Return valid JSON only — no markdown, no explanation:
{
"firstName": "Sarah",
"lastName": "Chen",
"company": "Acme Corp",
"role": "Director of Operations",
"estimatedBudget": 20000,
"useCase": "customer onboarding automation",
"timeline": "Q3 2026"
}Name: {name}
Email: {email}
Company: {company}
Phone: {phone}
Role: {role}
Budget: {budget}
Message: "{message}"
Extract structured entity data and return JSON.// Expected JSON response
{
"firstName": "Sarah",
"lastName": "Chen",
"company": "Acme Corp",
"role": "Director of Operations",
"estimatedBudget": 20000,
"useCase": "customer onboarding automation",
"timeline": "Q3 2026"
}Escalation Matrix
4 failure modes, all handled
Every failure path is explicit. Nothing is swallowed silently.
Trigger: Schema validation fails — missing email or malformed payload
Action: Audit log written. Job thrown back to BullMQ for retry (max 4 attempts, exponential backoff). Lead never written to DB.
Trigger: Intent classified as INT_SPM with confidence > 0.85
Action: Terminate cleanly. No DB write. No retry. Audit log entry only. BullMQ marks job as completed.
Trigger: Intent classified as INT_SUP
Action: Enqueue support.ticket_create on bot-support queue. No Lead row created — routed to Support Bot (B5).
Trigger: Entity extraction fails (AI timeout / malformed JSON)
Action: Non-fatal fallback: use raw form fields. Lead is still created and enqueued for qualification.
What Happens Next
Intake completes → Qualification starts
The final action of the Intake Bot is to enqueue a lead.qualification job. The Qualification Bot (B2) runs the BANT scoring model and assigns HOT / WARM / COLD.
≥ 75
HOT
Direct to Scheduling Bot (B3)
50 – 74
WARM
Outreach Bot (B6) for nurturing
< 50
COLD
Archived + CRM task logged
See it live
Submit a lead — watch the pipeline run
Fill out the form. Your submission goes through every step on this page in real time.