Satrio Systems Consulting → Portfolio → CRE Lead Factory → Insights
Case Study · Architecture Portfolio · CRE & B2B Services

Zero-Cost
AI Sales
Machine

Sales Ops → AI GTM → n8n → HubSpot → Brevo

Most consultants sell you a $500/month tool stack. This case study proves you don't need it. Full AI automation — lead capture, enrichment, classification, scoring, routing, nurturing — for $0 to $5 a month. Built on n8n, Gemini API, HubSpot Free, and Brevo. Every node documented. Every prompt included.

Architecture documented
$0–$5/month total stack
10 workflow nodes · 25-var scoring model
8 production AI prompts
Dummy project · Portfolio asset
Full stack Sales Ops AI GTM n8n Automation Gemini API HubSpot CRM Brevo Email Google Sheets
Layer 01 · Foundation

Sales Ops
Blueprint & ICP Design

Before a single automation runs, the architecture needs a brain. ICP definition, pipeline stage design, SLA framework, and CRM schema — built from Sales Operations principles. This is the foundation that makes every downstream AI decision meaningful.

Sales Ops Framework ICP Matrix 5-Stage Pipeline SLA Design Google Sheets Schema · 25 fields
❌ Before
No ICP. Sales team chased residential buyers, students, anyone who inquired. 200+ leads/month, all treated equally. 70% of pipeline was wrong-fit leads burning rep time.
⚙️ Built
Two ICPs defined and formalized. 5-stage MEDDIC-aligned pipeline with hard entry/exit criteria. SLA framework with response times. 25-column Sheets schema as the system's data warehouse.
📈 Output
Every lead now enters with a defined path. The AI classifier uses these exact ICP definitions as its scoring rubric. Pipeline stages map directly to HubSpot deal stages. Automated routing follows the SLA.
icp-definition.json · ICP-A · CRE Investor / Developer
{ "icp_id": "ICP-A", "label": "CRE Investor / Developer", "firmographic": { "revenue_min": "IDR 50B+ (≈ USD 3M)", "headcount": "20–500", "location": ["Jakarta","Surabaya","Medan"], "property_portfolio": "≥ 5 commercial properties" }, "demographic": { "title_targets": ["CEO","CFO","Director Asset Mgmt","Investment Head"], "seniority": "C-Suite or Director minimum" }, "pain_points": ["Portfolio yield","Tenant vacancy","Lease structuring","ROI on CRE assets"], "buying_triggers": ["Lease expiry","New acquisition","Regulatory change","Portfolio expansion"], "deal_value": "IDR 150M – 500M / engagement", "deal_cycle": "60–120 days · committee decision" }
ICP-A · CRE Investor / Developer
The primary ICP — high deal value, longer cycle, committee decision
This JSON is fed directly into the Gemini API ICP Classifier prompt. The AI uses these exact criteria to score each incoming lead against ICP-A. Any lead matching ≥80% of criteria gets icp_segment: "ICP-A" and routes to the CRE nurture sequence.
icp-definition.json · ICP-B · B2B Professional Services
{ "icp_id": "ICP-B", "label": "B2B Professional Services", "firmographic": { "revenue_min": "IDR 20B+ (≈ USD 1.2M)", "headcount": "15–200", "location": "Any major Indonesian city", "industry": ["Consulting","Legal","Finance","SaaS","Agency"] }, "demographic": { "title_targets": ["CEO","COO","Head of BD","Operations Director"], "seniority": "C-Suite, VP, or Director" }, "pain_points": ["Scaling without headcount","CAC reduction","Process chaos","Lead gen failure"], "buying_triggers": ["Revenue plateau","VC pressure","Failed in-house sales","Ops inefficiency"], "deal_value": "IDR 50M – 200M / engagement", "deal_cycle": "30–60 days · champion-driven" }
ICP-B · B2B Professional Services
Cross-sell ICP — faster cycle, champion-driven, smaller deal size
Secondary ICP with faster deal velocity. The AI classifier checks this in the same prompt run. A lead can match ICP-B even with low CRE exposure — the routing logic then assigns a different nurture sequence and email tone than ICP-A leads receive.
pipeline.config · 5-Stage MEDDIC-Aligned Pipeline
STAGE 0 Raw Lead → Entry: any contact in system | Exit: has email + company + score ≥ 40 STAGE 1 MQL → Entry: ICP match ≥60% + ≥1 behavioral signal | Exit: discovery call booked STAGE 2 SQL → Entry: call done, pain + budget confirmed | Exit: proposal sent <48h STAGE 3 Proposal → Entry: proposal reviewed, stakeholder mapped | Exit: negotiation opened STAGE 4 Closed W/L → Entry: decision made | Exit: CRM tagged + win/loss AI debrief triggered
Pipeline · Entry/Exit Criteria · HubSpot Stages
Every stage has a hard entry rule — no subjective advancement
These 5 stages map 1:1 to HubSpot deal stages. n8n updates deal stage automatically when key events fire (e.g. calendar booking → Stage 1 MQL, proposal sent → Stage 2 SQL). No manual stage advancement required.
2
ICPs defined with AI rubric
5
Pipeline stages with hard criteria
25
Sheets fields · full data schema
Layer 02 · Intelligence

AI GTM
Engine: Data → Brain → Action

AI GTM isn't about plugging ChatGPT into your email tool. It's a structured pipeline: capture → enrich → classify → personalize. Each step adds intelligence before the next step acts on it. By the time a lead reaches the sales rep, the system already knows who they are, how well they fit, and what to say first.

Gemini API Apollo.io Free Hunter.io Free n8n HTTP Request Node $0 AI Budget
❌ Before
Manual LinkedIn research. 22 hrs/week per rep on enrichment. Generic outreach sent to everyone. No AI anywhere in the process.
⚙️ Built
5-step enrichment pipeline. Gemini API classifies every lead against ICP definitions. AI generates personalized email opening lines per contact. Everything runs inside n8n — zero human involvement required.
📈 Output
Each lead arrives at the sales rep with: ICP segment, confidence score, persona label, buying stage, missing data flags, and a personalized email draft — before the rep has seen the name.

5-Step AI Enrichment Pipeline · Runs inside n8n on every new lead

Tally Form
Raw input
E1: Hunter.io
Email verify + domain
E2: Apollo.io
Revenue, headcount, industry
E3: Gemini AI
Infer missing firmographics
E4: ICP Classifier
ICP-A / ICP-B / NO_MATCH
Enriched Lead
Sheets + HubSpot
n8n · HTTP Request · Gemini API config
# Node: HTTP Request Method: POST URL: https://generativelanguage .googleapis.com/v1beta /models/gemini-pro :generateContent Auth: Query Param key: {{$env.GEMINI_KEY}} Headers: Content-Type: application/json # Body temperature: 0.1 maxOutputTokens: 500 responseMimeType: "application/json" text: {{$json["ai_prompt"]}}
n8n · Gemini API · HTTP Node Config
Temperature 0.1 — deterministic classification, not creative
Low temperature forces consistent JSON output. The api_key is stored as n8n environment variable — never hardcoded. The ai_prompt field is assembled in the preceding Set node and injected here dynamically.
n8n · Set Node · Prompt Assembly
// Assemble AI prompt in Set Node ai_prompt = 'You are a B2B lead classifier' + ' for a CRE advisory firm.' + '\nICP-A: Revenue>IDR50B,' + ' C-suite, Jakarta/Sby/Mdn' + '\nICP-B: Revenue>IDR20B,' + ' 20-200 staff, B2B svc' + '\nLead: ' + JSON.stringify({ name: $json.full_name, title: $json.job_title, co: $json.company, rev: $json.revenue_band, city: $json.city }) + '\nReturn JSON only: ' + '{icp_segment,confidence,' + 'persona,buying_stage}'
n8n · Set Node · Dynamic Prompt Builder
ICP definition + lead data merged into one prompt string
The prompt is assembled fresh per lead — each call carries that specific lead's enriched data. The AI never sees stale context. JSON.stringify ensures data is properly escaped before injection.
gemini-response.json · Sample Classification Output
// Input: Budi Prakoso, CFO, PT Graha Nusantara, Revenue IDR 120B, Jakarta // Output from Gemini API (parsed by n8n JSON node): { "icp_segment": "ICP-A", "icp_confidence": 94, "persona": "CRE Portfolio Optimizer", "buying_stage": "Consideration", "missing_data": ["property_count"], "reason": "CFO at IDR 120B company in Jakarta primary market. High CRE exposure likely. Revenue confirms budget capacity. Missing property portfolio count — trigger enrichment.", "recommended_action": "Route to HOT sequence · Assign senior rep · Enrich property count via Apollo" } // This JSON drives ALL downstream decisions: // - icp_segment → which email sequence to enroll // - icp_confidence → feeds into demographic score (+15 if ≥80) // - buying_stage → which CTA to use in email // - missing_data → triggers secondary enrichment workflow
Gemini API · Classification Response · Parsed by n8n
One API call drives 5 downstream decisions — no human judgment needed
n8n parses this JSON response and maps each field to a Google Sheets column and HubSpot custom property. The recommended_action field is logged but not executed directly — the score router (Workflow Node 10) executes the action based on final numeric score.
$0
AI budget · Gemini free tier
1M
Tokens/month free (Gemini)
5
Fields classified per lead in 1 call
Layer 03 · Automation Engine

n8n
The GHL Replacement. $0.

GoHighLevel charges $97/month for a visual workflow builder. n8n is free, self-hosted, and more powerful. Three modular workflows run the entire automation stack — lead intake, behavioral scoring, and daily decay — independently, so failure in one never breaks the others.

n8n Community Self-Hosted Free 3 Workflows · 15+ Nodes JS Code Node Railway.app Free Tier
❌ Before
Zero automation. Manual data entry after every lead. Follow-up depended on whoever remembered. No routing, no scoring, no alerts.
⚙️ Built
Workflow 1: 15-node master intake pipeline. Workflow 2: real-time behavioral event scoring. Workflow 3: daily decay engine that runs at 08:00 WIB and updates all inactive leads.
📈 Output
Lead form submitted → WhatsApp rep alert in under 60 seconds. Email sequence enrollment automatic. HubSpot deal created. Sheets row written. All without a single click from the sales team.
1
Webhook Trigger — Tally form POST received
n8n listens on a POST endpoint. Tally form submits on lead entry → n8n receives the raw JSON payload immediately.
n8n Webhook NodePath: /lead-intakeAuth: Header Token
2
Parse & Validate — Extract and clean fields
Set Node extracts: name, email, company, message, UTM source. Validates email format. Trims whitespace. Flags missing required fields.
n8n Set NodeOutput: cleaned lead object
3
Dedup Check — Is this lead already in Sheets?
Google Sheets Read looks up email. IF found → update existing row. IF new → continue intake pipeline. Prevents duplicate contacts in CRM.
Google Sheets nodeLookup column: email
4
Apollo Enrich — Append firmographic data
HTTP POST to Apollo.io /people/search. Returns: revenue_band, headcount_band, industry, LinkedIn URL, tech stack. Appended to lead object.
HTTP Request NodeApollo.io API (50 credits/mo free)
5
Build AI Prompt — Assemble classification context
Set Node combines ICP definition (hardcoded) + enriched lead data (dynamic) into single prompt string. Injected as ai_prompt field for next node.
n8n Set NodeExpression mode
6
Gemini Classify — ICP segment + persona + buying stage
HTTP POST to Gemini API. Receives JSON response with: icp_segment, confidence, persona, buying_stage, missing_data, recommended_action.
HTTP Request NodeGemini API (1M tokens/mo free)Temp: 0.1
7
Calculate Score — 25-variable JS scoring function
Code Node runs the full scoring function. Adds firmographic, demographic, and behavioral sub-scores. Returns: firm_score, demo_score, behav_score, lead_score, lead_tier.
n8n Code NodeJavaScriptMax: 200 pts
8
Write to Sheets — Full row append
All 25 fields written as a new row: timestamp, lead_id, enriched data, AI classification, scores, tier, rep assigned, action taken.
Google Sheets AppendMaster lead database
9
HubSpot Upsert — Create or update contact + set all properties
HubSpot node creates/updates contact with all custom properties: lead_score, lead_tier, icp_segment, behavioral_events. Creates deal if score ≥100.
HubSpot node (free API)Upsert by email
10
Score Router — 4-path IF/ELSE decision tree
IF score ≥160 → HOT path. IF 100–159 → WARM path. IF 50–99 → NURTURE path. IF <50 → COLD path. Each path has its own node chain below.
n8n IF Node4 branches
HOT Path (≥160): CallMeBot WhatsApp → rep alert → Brevo transactional email → HubSpot deal created Stage=SQL → tag 'PRIORITY'. Rep alerted within 60 seconds of form submission, with full lead intel, AI classification, and a suggested email opener.
WARM Path (100–159): Email alert to assigned rep → enroll in 'warm-close-7' Brevo sequence → HubSpot deal Stage=MQL. Rep given 4 hours to follow up before system escalates.
workflow-3.json · Daily Decay Engine · Schedule: 08:00 WIB
TRIGGER Schedule: every day 08:00 WIB STEP 1 Sheets Read — filter tier NOT IN ['DQ','COLD'] STEP 2 Code Node — days_inactive = TODAY() - last_activity_date STEP 3 Switch Node: 30 days inactive → -15 pts 60 days inactive → -25 pts 90 days inactive → -40 pts Active → no change STEP 4 Code: new_score = MAX(0, current - delta) STEP 5 Recalculate tier → IF downgraded: log change STEP 6 Batch update Sheets + HubSpot → sync complete
Workflow 3 · Decay Engine · Runs independently daily
Stale leads lose score automatically — pipeline stays honest
This is the most important workflow nobody talks about. Without decay, a lead who opened one email 90 days ago stays "WARM" forever, inflating the pipeline forecast. Decay keeps scores current without any human intervention. Decayed leads re-enter normal scoring if they become active again.
3
Independent workflows
<60s
Lead → WhatsApp alert (HOT)
$0
n8n Community Edition
Layer 04 · Intelligence Model

Lead Scoring
200-Point Model · Live Calculator

A scoring model is only as good as the logic behind it. This 25-variable, 200-point model has three dimensions: who the company is (firmographic), who the contact is (demographic), and what they've done (behavioral). Each dimension feeds into a JavaScript function running inside n8n — try the live calculator below.

n8n Code Node · JS AI-enhanced demographic 200-point max Decay logic included Auto-routes to HubSpot stage
Live Score Calculator · Same logic as the actual n8n Code Node
0
Firmographic
/80
0
Demographic
/60
0
Behavioral
/60
0
SELECT FIELDS
n8n-code-node.js · Lead Scoring Function
const lead = $input.first().json; let firm = 0, demo = 0, behav = 0; // ── FIRMOGRAPHIC (max 80 pts) ───────────────────── const rev = lead.revenue_band; if (rev === '>IDR100B') firm += 30; else if (rev === 'IDR50-100B') firm += 22; else if (rev === 'IDR20-50B') firm += 12; else if (rev === ') firm -= 10; const city = lead.city?.toLowerCase(); if (['jakarta','surabaya','medan'].includes(city)) firm += 20; else if (['bandung','semarang','bali'].includes(city)) firm += 8; const props = parseInt(lead.property_count) || 0; if (props >= 10) firm += 20; else if (props >= 5) firm += 12; else if (props >= 2) firm += 5; // ── DEMOGRAPHIC (max 60 pts) ────────────────────── const title = lead.job_title?.toLowerCase() || ''; const cS = ['ceo','cfo','coo','founder','owner']; const dir = ['director','vp','head of']; const mgr = ['manager','senior manager']; if (cS.some(t => title.includes(t))) demo += 30; else if (dir.some(t => title.includes(t))) demo += 20; else if (mgr.some(t => title.includes(t))) demo += 8; else if (!title) demo -= 10; if (lead.icp_confidence >= 80) demo += 15; if (lead.icp_confidence >= 60 && lead.icp_confidence < 80) demo += 8; if (lead.multi_stakeholder === true) demo += 10; // ── BEHAVIORAL (max 60 pts) ─────────────────────── const pts = { calendar_booked: 50, pricing_page: 25, download: 20, webinar_full: 20, email_reply: 15, form_submit: 15, email_click: 10, email_open_3x: 8, page_visit_2x: 5 }; (lead.behavioral_events || []).forEach(e => { behav += pts[e] || 0 }); behav = Math.min(behav, 60); // ── TOTAL ───────────────────────────────────────── const total = Math.max(0, Math.min(200, firm + demo + behav)); const tier = total >= 160 ? 'HOT' : total >= 100 ? 'WARM' : total >= 50 ? 'NURTURE' : 'COLD'; return [{ json: { firm_score:firm, demo_score:demo, behav_score:behav, lead_score:total, lead_tier:tier }}];
n8n Code Node · JavaScript · Runs on every new lead
This exact code runs in production — not pseudocode, not a flowchart
The scoring function is deterministic: same inputs always produce the same outputs. This is intentional — scoring must be auditable. If a rep questions why a lead was rated COLD, we can trace every point. Behavioral events are passed as an array from Workflow 2 and accumulated over time.
Score RangeTierAutomatic Action TriggeredBrevo SequenceSLA
160 – 200🔥 HOTWhatsApp + email to rep · HubSpot deal Stage=SQL · tag PRIORITYhot-close-5 (7 days)< 60 sec automated
100 – 159⚡ WARMEmail alert · Enroll warm-close-7 · HubSpot Stage=MQLwarm-close-7 (14 days)Rep follow-up < 4h
50 – 99🌱 NURTUREEnroll educate-12 sequence · tag NURTURE · no rep alerteducate-12 (60 days)Automated only
1 – 49❄️ COLDAdd to quarterly broadcast list · no active sequenceQuarterly broadcastNo rep time spent
< 0 / DQ🚫 DQTag Disqualified · exit all workflows · archive HubSpotNoneArchived < 5 sec
200
Point max score
25
Scoring variables
4
Auto-routing paths
Layer 05 · Prompt Engineering

8 Production
AI Prompts · Ready to Deploy

Prompts are the interface between your data and the AI. A bad prompt gives you hallucinations and generic output. A good prompt returns structured JSON, respects data constraints, and drives consistent downstream decisions. These 8 prompts run in production — each designed to return exactly what the next automation node expects.

Gemini API Claude.ai JSON-first output Bahasa Indonesia support n8n-injectable variables
❌ Before
Generic ChatGPT prompts. Output varies every run. No structured JSON. Downstream nodes can't parse the output reliably. Manual editing required per email.
⚙️ Built
8 production prompts designed for deterministic JSON output. Each prompt has a specific role, a specific return format, and injects variables from n8n dynamically. Low temperature (0.1) enforces consistency.
📈 Output
Every prompt returns parseable JSON or structured text. n8n parses the response and routes it directly to the next node without human review. Prompts are versioned — updates don't break the pipeline.
Design principle: Every prompt starts with a role definition, ends with a strict output format instruction, and never says "feel free to" or "you can also." Ambiguity in prompts creates unparseable outputs that break automation chains.
Prompt 01 · ICP Classifier · Returns JSON
SYSTEM: You are a B2B lead qualification expert for a CRE advisory firm in Indonesia. Classify leads against two ICPs. Return ONLY valid JSON — no preamble, no explanation. If data is insufficient, set confidence below 40. Never hallucinate. USER: Classify this lead: Name: {{full_name}} | Title: {{job_title}} | Company: {{company}} Industry: {{industry}} | Revenue: {{revenue_band}} | City: {{city}} Message: {{lead_message}} ICP-A (CRE): Revenue >IDR50B, commercial property owner, Jakarta/Surabaya/Medan, C-suite ICP-B (B2B): Revenue >IDR20B, 20–200 staff, consulting/legal/finance/SaaS Return: { "icp_segment": "ICP-A"|"ICP-B"|"NO_MATCH", "icp_confidence": 0-100, "persona": "string", "buying_stage": "Awareness"|"Consideration"|"Decision", "missing_data": ["field1","field2"], "recommended_action": "string" }
Prompt 02 · Cold Email Generator · Bahasa Indonesia
Write a cold email in Bahasa Indonesia to {{full_name}}, {{job_title}} at {{company}}. Context: {{trigger_event}} (e.g. "baru mengumumkan ekspansi portofolio Q3") Rules: - Max 120 words total · 3 paragraphs - Tone: consultative, not salesy, not generic - P1: Reference their specific situation or trigger (1–2 sentences) - P2: One specific relevant capability (2–3 sentences) - P3: Low-friction CTA — 15-minute call (1 sentence) Output: Subject line + email body ONLY. No preamble. No "Here is your email."
Prompt 03 · Email Reply Sentiment Analysis · Returns JSON
Classify this email reply sentiment for a B2B CRE sales context. Reply: "{{email_reply_text}}" Return ONLY: { "sentiment": "POSITIVE"|"NEUTRAL"|"NEGATIVE"|"OBJECTION"|"NOT_INTERESTED", "score_delta": -40 to +50, "detected_intent": "string", "next_action": "CALL"|"SEND_PROPOSAL"|"CONTINUE_NURTURE"|"PAUSE_30D"|"DQ" }
Prompt 04 · Objection Handler · 3 Styles
B2B CRE advisory sales objection: "{{objection_text}}" Write 3 response options in Bahasa Indonesia, each max 60 words: 1. Empathy-first: Acknowledge → validate → reframe 2. Data-backed: Relevant statistic or benchmark → repositioning 3. Story-based: Similar client situation (anonymized) → outcome Format: numbered list. No preamble. No "Here are your options."
Prompt 05 · Win / Loss Debrief Analyzer · Returns JSON
Analyze these {{n}} closed deals: {{deal_notes_json}} Return: { "top_objections": [{"objection": "string", "frequency": number, "counter": "string"}], "funnel_death_stage": "string", "icp_mismatches": [{"pattern": "string", "count": number}], "scoring_model_gaps": ["string"], "fixes": [{"finding": "string", "action": "string"}] }
Prompt 06 · LinkedIn DM
Write a LinkedIn connection request (MAX 200 characters) to {{full_name}}, {{job_title}} at {{company}}. Reference this specific detail: {{recent_activity}} Language: Bahasa Indonesia NO generic openers. NO "saya menemukan profil Anda." Be specific and human.
Prompt 07 · Subject Line A/B
Generate 5 email subject lines for a {{icp_segment}} lead. Topic: {{email_topic}} | Persona: {{persona}} Language: Bahasa Indonesia One each: Professional · Curiosity-gap · Data-led · Question · Urgency Max 9 words each. Numbered list. No preamble.
8
Production prompts
0.1
Temperature · deterministic output
100%
JSON-parseable · automation-ready
$0
Monthly stack cost
(base setup)
10
Workflow nodes
in master pipeline
25
Lead scoring
variables
8
AI prompts
production-ready
60s
Form submit →
WhatsApp alert (HOT)
Ready to Build This for Your Business?

Same System.
Your Leads.

This architecture is modular — it adapts to your ICP, your tools, your industry. You don't need a $500/month stack. You need the right design. Let's build it.

// Related Reading
→ How to Build an AI Lead Gen System → B2B Lead Automation Workflow → Make vs Zapier vs n8n All Insights →