Your GTM belongs in gitRegister
Cookbooks
Engagement

Inbound qualification

Turn the website's demo form into qualified inbound: each submission runs a Cargo tool that identifies the company by work email, qualifies it against the ICP, lands account and contact in the shared GTM models, posts it to Slack, and answers on the page with a booking link or a thank-you; an optional play then tiers and summarises each qualified contact.

Installation
Set up the inbound-qualification cookbook in this project. 1. If the Cargo CLI is not installed yet, follow every step in https://api.getcargo.io/INSTALL.md 2. From the Cargo project, run: cargo-ai cdk add cookbook/inbound-qualification 3. Then follow .claude/skills/inbound-qualification/SKILL.md. Stop for my approval before any paid call, and stop at "cargo-ai project plan" before deploying anything.
Paste it into Claude Code, Cursor, Codex, Gemini CLI, Devin or Replicas.

Say this to your agent

Add a demo form to www.fabrikam.example: qualified companies get our booking link, the rest a thank-you, and the sales channel hears about every request.

Illustrative output #

Fictional records, to show the shape of what comes back.

text
tool:inbound_form        deployed   public form on https://www.fabrikam.example
POST /contact            ada@northwind.example    -> qualified      (320 employees, United States)
  gtm_accounts           northwind.example        upserted
  gtm_contacts           ada@northwind.example    lead_source website, inbound_status qualified
  #inbound               "New inbound, qualified from Ada Lovelace at Northwind"
  page                   "Thanks, let's find a time."  [Book a demo]
play research_qualified_leads (minutes later, once enabled)
  gtm_contacts           ada@northwind.example    inbound_tier A, inbound_brief written
  #inbound               ":mag: Ada Lovelace: tier A. Head of RevOps at Northwind, ..."
POST /contact            sam@gmail.com            -> work_email_required   (nothing paid or written)

Done when #

  • node --import tsx evals/contract.mjs passes: one tool whose public form is enabled for exactly the canonical origin with no CAPTCHA secret in code, a workflow that refuses personal email before the enrichment call and writes only gtm_accounts and gtm_contacts, both models matching the shared definitions
  • the plan shows the tool, the models and the connectors, and the app update with the env token
  • a work-email submission on the live site returns the right answer on the page, upserts the account and the contact (with account_id, lead_source website, inbound_status), and posts once to the Slack channel
  • a personal-email submission is refused on the page, with no enrichment call and no row written
  • a submission from another origin is refused with 403, and one sent faster than the minimum fill time is refused
  • loading the contact page sets no cargo_anon_id cookie until a submission
  • the privacy page has a contact form section the operator reviewed
  • the research play, run by hand on one qualified contact, wrote inbound_tier, inbound_brief, inbound_rationale and inbound_researched_at onto it and posted one note; a second manual run on the same contact did nothing
  • every fact in the brief traces to the record or a listed source

What it costs #

Each submission from a work email pays for one LinkedIn company enrichment; a refused personal email pays nothing. Read the live price from the integration (cargo-ai connection integration get linkedin) and say it per submission. The server’s rate limit caps a single address at ten submissions a minute, and the origin allow-list keeps other sites from spending it. The models and the Slack post add nothing. The deep research, once enabled, adds one agent run per qualified contact (LLM tokens on the Anthropic connector plus up to four web searches); a not-qualified or personal-email submission never reaches it.

The website’s demo form, wired to a Cargo tool: each submission is qualified, lands in the shared GTM accounts and contacts, is posted to Slack, and is answered on the page.

text
visitor submits /contact/ (headless form SDK, the site's own markup)
  -> public form API: origin, honeypot, time-trap, rate limit
  -> tool inbound_form: personal email? -> company from LinkedIn -> ICP rules
       -> gtm_accounts + gtm_contacts -> Slack
  <- booking link or thank-you, when the run finishes (async: the SDK polls)

later, for each contact marked qualified (play research_qualified_leads, ships disabled)
  -> agent: ICP + tiering rubric from context, web research -> tier, brief
  -> inbound_tier / inbound_brief / inbound_rationale / inbound_researched_at on the contact
  -> one note to the same Slack channel

Why it is built this way #

A tool, not a backend. Cargo tools take a public form: the workflow’s input is the field list, publicForm holds the origin allow-list and the spam guards, and each submission runs the workflow. Nothing else to host.

The shared GTM models. The account and the contact land in gtm_accounts and gtm_contacts, declared exactly as every pipeline declares them, so the worked example deploys on a bare workspace and other pipelines read the same inbound contacts. A CRM is the crm-backed variation.

Cheapest refusal first. A personal email is refused before the paid company lookup; the lookup comes before any write. The company is enriched, never the person.

Rules, not a model call. Headcount band and countries from the ICP: free, explainable, the same answer twice. An agent judgement is a variation.

Research after the answer. The deep research is a separate play on the contact row, never part of the form’s workflow, so the visitor never waits on a model call. It ships disabled and runs once per qualified contact.

The SDK loads after hydration. Its load time is what the server’s minimum fill time counts from, so it loads when the form becomes usable. It sets its id cookie only on submit, so a visitor who never submits gets none.

Placeholders (edit before deploy) #

  1. ICP rules: icpMinEmployees, icpMaxEmployees, icpCountries in infra/tools/inbound-form.ts.
  2. Booking link: bookingUrl.
  3. Slack channel: slackChannelId in infra/settings.ts, shared by the form and the research note.
  4. Origin: publicForm.allowedOrigins, the site’s canonical origin.

Files #

PathWhat it is
infra/tools/inbound-form.tsThe tool, its workflow and its public form
infra/plays/research-qualified-leads.tsThe deep-research play, shipped disabled
infra/agents/lead-researcher.tsThe research agent: tier and brief, as JSON
infra/settings.tsThe Slack channel both post to
infra/connectors/anthropic.tsThe model the research agent runs on
infra/models/gtm-accounts.tsThe shared GTM accounts model
infra/models/gtm-contacts.tsThe shared GTM contacts model, with the inbound columns
infra/connectors/linkedin.tsLinkedIn company data on Cargo’s credits
infra/connectors/slack.tsThe Slack workspace the inbound channel lives in
site/components/inbound-form.tsxThe form, headless, loading the SDK after hydration
site/app/contact/page.tsxThe page that holds it
references/form.mdWiring the site, the spam checks, privacy
references/data.mdThe rows, querying, the crm-backed mapping
evals/contract.mjsGraph contract

Verify #

shellscript
node --import tsx inbound-qualification/evals/contract.mjs
Version0.2.0
LicenseMIT
Source
Deploys
  • agents
  • connectors
  • models
  • plays
  • tools
For
  • Marketing
  • Sales development
  • GTM engineering
Integrations
  • Anthropic
  • LinkedIn
  • Slack
More in Engagement

Give your agents a runtime

Bring the agents you have.Start free, deploy in one command.