NEG AG Lead Gen — Developer Handoff

What we're building

A lead generation and cold outreach pipeline targeting German real estate companies (developers + brokers, starting in Leipzig). Fully automated from discovery to first reply — once a lead responds, automation stops and the conversation is handed off to Huy Ngo (Sales Lead, NEG AG).

Stack: n8n (orchestration), NocoDB (master data + analytics), Pipedrive (CRM), Google Maps Places API (source), Crawl4AI + SearXNG + Hunter.io (enrichment), SMTP with tracking pixel (outreach), IMAP (reply listener).

The pipeline

 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 │ WF-1            │    │ WF-2            │    │ WF-3            │
 │ Google Maps     │───►│ Enrich Lead     │───►│ Pipedrive Sync  │
 │ Scraper         │    │ (crawl website, │    │ (create Contact │
 │ → NocoDB (raw)  │    │  extract email) │    │  + Deal)        │
 └─────────────────┘    └─────────────────┘    └─────────────────┘
                                                        │
                                                        ▼
                        ┌─────────────────┐    ┌─────────────────┐
                        │ WF-5            │◄───│ WF-4            │
                        │ Reply Handler   │    │ Email Outreach  │
                        │ → stop +        │    │ (4-stage German │
                        │   notify Huy    │    │  sequence)      │
                        └─────────────────┘    └─────────────────┘

 Every step writes to NocoDB. Errors go to an error log table.
 Send / delivered / reply / booking events feed the analytics table.
 No tracking pixel — first-party events only (GDPR/TTDSG safe).

The 5 workflows

WF-1 — Google Maps Scraper

Trigger: Daily 06:00

Job: Query Google Places API across 50 German cities × 5 search terms (Immobilienmakler, Projektentwickler, Bauträger, Immobilienentwickler, Hausverwaltung). Runs 10 queries/day → full Germany rotation every 25 days. Each place is saved to NocoDB with enrichment_status = pending.

Dedup: by Google place_id.

Writes: NocoDB leads table (raw row per company).

Cost: ~$10/month.

WF-2 — Enrich Lead

Trigger: Runs every 15 minutes, picks up leads where enrichment_status = pending.

Job: For each lead, enrich contact info:

  1. If no website → SearXNG search by company name + city, LLM picks best domain
  2. Crawl4AI scrapes /impressum, /kontakt, /imprint
  3. LLM extracts: email, phone, contact person, Geschäftsführer, address
  4. If still no email → Hunter.io fallback (domain search)
  5. LLM classifies: Developer vs Broker vs Unknown
  6. Update NocoDB: enrichment_status = enriched (or failed)

Writes: updates leads row with email, phone, contact_person, website, type, impressum_content.

WF-3 — Pipedrive Sync

Trigger: Runs every 15 minutes, picks up leads where enrichment_status = enriched AND pipedrive_deal_id IS NULL.

Job: For each qualified lead:

  1. Create Pipedrive Organization (company + website + address)
  2. Create Pipedrive Person (contact + email + phone, linked to org)
  3. Create Pipedrive Deal in pipeline "Leipzig – Developers" or "Leipzig – Brokers" based on type, stage = "New"
  4. Write pipedrive_org_id, pipedrive_person_id, pipedrive_deal_id back to NocoDB

Only leads with a valid email proceed. Everything else stays in NocoDB for manual review.

WF-4 — Email Outreach

Trigger: Weekdays 09:00

Job: Pull leads ready for their next stage. LLM generates a personalized German email using company context (Impressum, website, type). Send via SMTP. Log email_sent event to analytics. Update lead state.

4-stage sequence:

Stage Day Tone
10Cold opener, value prop, soft CTA
2+3Follow-up with market insight
3+7Direct, "letzte Nachricht"
4+14Breakup, door open

Rate limit: 30 emails/day across all stages (SMTP warm-up). Random 20–60s delay between sends.

Filter: only send if last_email_date != today (prevents double-send on re-runs).

GDPR: opt-out link + NEG AG Impressum in every email footer.

WF-5 — Reply Handler

Trigger: IMAP inbox listener (always-on) + Cal.com booking webhook

Job on any incoming reply:

  1. Match the sender to a lead via NocoDB (by email)
  2. Set reply_status = received, reply_date = now
  3. Stop all future automation for this lead — flag outreach_stage = handed_off
  4. Move Pipedrive deal to "Reply Received"
  5. Notify Huy Ngo via email with the full thread + lead context

No LLM classification, no auto-reply. Every reply goes to Huy to handle personally.

Cal.com webhook branch: on booking → move Pipedrive deal to "Meeting Booked", create Pipedrive activity, update NocoDB with meeting_date, log to analytics.

NocoDB tables

leads (master)

Single row per company. Key fields:

analytics (new — pipeline metrics)

Append-only event log. One row per tracked event.

Field Purpose
lead_idFK to leads table
event_typelead_created / enriched / pipedrive_synced / email_sent / email_delivered / email_bounced / reply_received / meeting_booked / error
stagestage_1 / stage_2 / stage_3 / stage_4 (where relevant)
workflowWF-1 … WF-5
metadataJSON — SMTP response, bounce reason, error message, etc.
created_attimestamp

NocoDB views on top aggregate: send volume per stage, bounce rate, reply rate by type, conversion funnel, drop-off per workflow.

errors (new — error fallback log)

Field Purpose
workflowWF-1 … WF-5
noden8n node name that failed
lead_idFK if applicable
error_messageraw error
input_dataJSON of the failing item (for replay)
retry_count0–3
resolvedbool
created_attimestamp

Analytics — first-party events only (no tracking pixel)

Decision: we do not embed tracking pixels or wrap outbound links. German law makes this a bad tradeoff:

Instead, we use first-party signals only. These don't touch the recipient's device and don't require consent:

Signal Source Logged as
SentWF-4 SMTP send successemail_sent
DeliveredSMTP server confirmationemail_delivered
BouncedBounce-inbox listener (IMAP on the bounce address)email_bounced
RepliedWF-5 IMAP listenerreply_received
Meeting bookedCal.com webhookmeeting_booked

These four events cover the funnel that actually matters: did it land, did they answer, did they book. Open rate and click rate are intentionally out of scope.

Cal.com already provides its own booking analytics (visit → booking conversion) on their dashboard — use that for CTA effectiveness instead of wrapping the link.

Error handling pattern (all workflows)

Every workflow follows the same shape so they stay readable and recoverable:

  1. continueOnFail = true on every external call (HTTP, NocoDB, Pipedrive, LLM, SMTP)
  2. Error branch from every failure-prone node → Log to errors table node → noop
  3. 3× retry with exponential backoff on transient failures (HTTP 5xx, timeouts)
  4. Dead-letter: after 3 retries, mark the lead enrichment_status = failed (or equivalent per workflow), keep moving with the rest of the batch
  5. Daily digest: a lightweight scheduled workflow emails Huy a summary of unresolved errors from the last 24h

Rule: a single bad lead never kills a whole run.

What's left to do

P0 — Core build

  1. Create NocoDB analytics and errors tables with fields above
  2. Wire first-party event logging into every workflow (sent / delivered / bounced / replied / booked)
  3. Set up bounce-inbox IMAP listener to capture email_bounced events
  4. Split current 58-node WF-1 into the clean 5-workflow structure above
  5. Set real SMTP credential for WF-4 outreach (currently placeholder)
  6. Simplify WF-5 to "any reply → stop + notify Huy" (remove current LLM sentiment branching)

P1 — Hardening

  1. Add the error-handling pattern (continueOnFail + error branch + retries) to every external node in every workflow
  2. Add last_email_date != today filter to WF-4 query so double-runs don't double-send
  3. Build the daily unresolved-errors digest workflow

P2 — Analytics polish

  1. NocoDB views: funnel (lead → enriched → synced → sent → delivered → replied → booked), bounce rate, reply rate per type
  2. Simple weekly email to Huy with the numbers

Conventions