← All blueprints

Lead Enrichment & Outreach LangGraph

blueprint · 4 agents · 4 MCP servers
1310★ stars
189forks
4.7rating

What it does

A web-enrichment agent and a CRM-lookup agent run in parallel on a new lead. A scorer combines what they find into a fit score against the ideal-customer profile and marks the lead qualified or not. Qualified leads flow to a drafter that writes a specific first-touch email grounded in the enrichment, and the workflow pauses for human approval before sending. Point the CRM server at your own instance and adjust the ICP.

The cast · 4 agents

Scorerscorerreasoning

Combine the web and CRM findings into a fit score against the ideal-customer profile. Return an object with a 'qualified' boolean, the score, and the top reasons. Do not qualify a lead that is already an active opportunity owned by someone else.

no tools · reasoning only
Drafterdrafterreasoning

Write a short, specific first-touch email grounded in the enrichment — reference something real about them, not a template. One clear ask. Save it as a draft; do not send.

mail.draft_email
CRM LookupcrmLookupfast

Look up the lead in the CRM. Report whether they or their company already exist, prior touches, deal stage, and owner. If new, say so.

crm.get_contactcrm.get_company
Web EnricherwebEnricherfast

Research the lead's company and role from the open web: what the company does, size signals, recent news, and the person's likely priorities. Report facts with sources. Do not guess.

search.web

Flow

⇉ parallel
Web EnricherwebData
CRM LookupcrmData
Scorerscore
⑂ branch
score.qualified == true
Drafteremail
askoutcome

Interface

Inputs
leadobject
The inbound lead: at least a name, email, and company.
Outputs
outcomestring
Whether the lead qualified and, if so, the approved email that was queued to send.

Depends on · 4 MCP servers

HubSpot MCPio.github.Servosity/hubspot-mcpcrmBVerified
io.github.brave/brave-search-mcpnot in the registry yetsearch
Multi-account MCP for Gmail, Calendar, Drive, Docs, and Sheets — 61 tools, tokenio.github.adelaidasofia/google-workspace-mcpmailAVerified
io.github.slack/slack-mcpnot in the registry yetchat
⤓ Export runnable code
# Generated by @socketcat/compiler for target: langgraph
# blueprint: com.socketcat/lead-enrichment v1.0.0   schema: socketcat.dev/blueprint/v0
# This code is yours. Edit it freely. The socketcat_runtime helper is optional and can be vendored.

from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import socketcat_runtime as _rt

class State(TypedDict, total=False):
    lead: object
    outcome: object
    webData: object
    crmData: object
    score: object
    email: object

def node_Fork_0(state):
    return {}

def node_Join_1(state):
    return {}

def node_AgentInvoke_2(state):
    return _rt.run_agent("webEnricher", state, {"lead":"lead"}, "webData")

def node_AgentInvoke_3(state):
    return _rt.run_agent("crmLookup", state, {"lead":"lead"}, "crmData")

def node_AgentInvoke_4(state):
    return _rt.run_agent("scorer", state, {"crmData":"crmData","webData":"webData"}, "score")

def node_Branch_5(state):
    return {}

def node_Join_6(state):
    return {}

def node_AgentInvoke_7(state):
    return _rt.run_agent("drafter", state, {"score":"score","webData":"webData"}, "email")

def node_Ask_8(state):
    return _rt.run_ask("Review the drafted outreach email. Approve to queue it for send, or edit first.", state, "outcome")

def node_AgentInvoke_9(state):
    return _rt.run_agent("scorer", state, {"score":"score"}, "outcome")

def route_Branch_5(state):
    if _rt.cond("score.qualified == true", state): return "AgentInvoke_7"
    return "AgentInvoke_9"

def build():
    b = StateGraph(State)
    b.add_node("Fork_0", node_Fork_0)
    b.add_node("Join_1", node_Join_1)
    b.add_node("AgentInvoke_2", node_AgentInvoke_2)
    b.add_node("AgentInvoke_3", node_AgentInvoke_3)
    b.add_node("AgentInvoke_4", node_AgentInvoke_4)
    b.add_node("Branch_5", node_Branch_5)
    b.add_node("Join_6", node_Join_6)
    b.add_node("AgentInvoke_7", node_AgentInvoke_7)
    b.add_node("Ask_8", node_Ask_8)
    b.add_node("AgentInvoke_9", node_AgentInvoke_9)
    b.add_edge(START, "Fork_0")
    b.add_edge("Fork_0", "AgentInvoke_2")
    b.add_edge("Fork_0", "AgentInvoke_3")
    b.add_edge("Join_1", "AgentInvoke_4")
    b.add_edge("AgentInvoke_2", "Join_1")
    b.add_edge("AgentInvoke_3", "Join_1")
    b.add_edge("AgentInvoke_4", "Branch_5")
    b.add_conditional_edges("Branch_5", route_Branch_5, {"AgentInvoke_7": "AgentInvoke_7", "AgentInvoke_9": "AgentInvoke_9"})
    b.add_edge("Join_6", END)
    b.add_edge("AgentInvoke_7", "Ask_8")
    b.add_edge("Ask_8", "Join_6")
    b.add_edge("AgentInvoke_9", "Join_6")
    return b.compile()

INPUTS = ["lead"]
OUTPUTS = ["outcome"]

if __name__ == "__main__":
    _rt.main(build, INPUTS, OUTPUTS)
blueprint.json (the portable format)
{
  "id": "com.socketcat/lead-enrichment",
  "flow": {
    "type": "sequence",
    "blocks": [
      {
        "type": "parallel",
        "blocks": [
          {
            "in": {
              "lead": "lead"
            },
            "out": "webData",
            "use": "webEnricher",
            "type": "agent"
          },
          {
            "in": {
              "lead": "lead"
            },
            "out": "crmData",
            "use": "crmLookup",
            "type": "agent"
          }
        ]
      },
      {
        "in": {
          "crmData": "crmData",
          "webData": "webData"
        },
        "out": "score",
        "use": "scorer",
        "type": "agent"
      },
      {
        "else": [
          {
            "in": {
              "score": "score"
            },
            "out": "outcome",
            "use": "scorer",
            "type": "agent"
          }
        ],
        "type": "branch",
        "cases": [
          {
            "when": "score.qualified == true",
            "blocks": [
              {
                "in": {
                  "score": "score",
                  "webData": "webData"
                },
                "out": "email",
                "use": "drafter",
                "type": "agent"
              },
              {
                "out": "outcome",
                "type": "ask",
                "prompt": "Review the drafted outreach email. Approve to queue it for send, or edit first."
              }
            ]
          }
        ]
      }
    ]
  },
  "tags": [
    "sales",
    "lead-gen",
    "orchestrator-workers",
    "human-in-the-loop"
  ],
  "title": "Lead Enrichment & Outreach",
  "agents": {
    "scorer": {
      "model": {
        "hint": "reasoning"
      },
      "title": "Scorer",
      "output": {
        "type": "object",
        "required": [
          "qualified"
        ],
        "properties": {
          "score": {
            "type": "number"
          },
          "reasons": {
            "type": "string"
          },
          "qualified": {
            "type": "boolean"
          }
        }
      },
      "instructions": "Combine the web and CRM findings into a fit score against the ideal-customer profile. Return an object with a 'qualified' boolean, the score, and the top reasons. Do not qualify a lead that is already an active opportunity owned by someone else."
    },
    "drafter": {
      "model": {
        "hint": "reasoning"
      },
      "title": "Drafter",
      "tools": [
        "mail.draft_email"
      ],
      "instructions": "Write a short, specific first-touch email grounded in the enrichment — reference something real about them, not a template. One clear ask. Save it as a draft; do not send."
    },
    "crmLookup": {
      "model": {
        "hint": "fast"
      },
      "title": "CRM Lookup",
      "tools": [
        "crm.get_contact",
        "crm.get_company"
      ],
      "instructions": "Look up the lead in the CRM. Report whether they or their company already exist, prior touches, deal stage, and owner. If new, say so."
    },
    "webEnricher": {
      "model": {
        "hint": "fast"
      },
      "title": "Web Enricher",
      "tools": [
        "search.web"
      ],
      "instructions": "Research the lead's company and role from the open web: what the company does, size signals, recent news, and the person's likely priorities. Report facts with sources. Do not guess."
    }
  },
  "estate": {
    "email": {
      "type": "object",
      "description": "The drafter's tailored first-touch email."
    },
    "score": {
      "type": "object",
      "description": "The scorer's fit read, including a 'qualified' flag."
    },
    "crmData": {
      "type": "object",
      "description": "The CRM lookup's existing record and prior touches."
    },
    "webData": {
      "type": "object",
      "description": "The web enricher's firmographic and role findings."
    }
  },
  "$schema": "socketcat.dev/blueprint/v0",
  "authors": [
    {
      "url": "https://socketcat.com",
      "name": "SocketCat"
    }
  ],
  "license": "MIT",
  "servers": [
    {
      "ref": "io.github.Servosity/hubspot-mcp",
      "alias": "crm"
    },
    {
      "ref": "io.github.brave/brave-search-mcp",
      "alias": "search"
    },
    {
      "ref": "io.github.adelaidasofia/google-workspace-mcp",
      "alias": "mail"
    },
    {
      "ref": "io.github.slack/slack-mcp",
      "alias": "chat"
    }
  ],
  "summary": "Enriches a new lead from the web and CRM in parallel, scores it against your ICP, and drafts a tailored first email for human approval.",
  "targets": [
    "langgraph",
    "*"
  ],
  "version": "1.0.0",
  "interface": {
    "inputs": {
      "lead": {
        "type": "object",
        "description": "The inbound lead: at least a name, email, and company."
      }
    },
    "outputs": {
      "outcome": {
        "type": "string",
        "description": "Whether the lead qualified and, if so, the approved email that was queued to send."
      }
    }
  },
  "extensions": {
    "dev.langgraph": {
      "checkpointer": "memory"
    }
  },
  "description": "A web-enrichment agent and a CRM-lookup agent run in parallel on a new lead. A scorer combines what they find into a fit score against the ideal-customer profile and marks the lead qualified or not. Qualified leads flow to a drafter that writes a specific first-touch email grounded in the enrichment, and the workflow pauses for human approval before sending. Point the CRM server at your own instance and adjust the ICP."
}