---
name: hubspot-ghost-pipeline
description: "Fridays: open deals with no email, meeting or Gong call with the buyer in 14 days, and this week's external meetings the CRM never heard about. Use when the user asks for this, or when it runs on a schedule."
---

# Ghost pipeline: CRM deals with no real contact

Fridays: open deals with no email, meeting or Gong call with the buyer in 14 days, and this week's external meetings the CRM never heard about.

A deal can look alive in the CRM for weeks. A rep moves the close date, adds a task, edits the amount, and the record stays freshly touched. None of that is a conversation with the buyer. The evidence of a real conversation sits in the inbox, the calendar and the call recorder, and nobody lines those up with the pipeline.

Every Friday the agent takes each open deal, collects its contacts' emails and the company domain, and looks for a thread, a held meeting or a recorded call with those people in the last 14 days. Deals with none are ghost pipeline. It also runs the check the other way round: external meetings this week whose company has no deal, or no update logged since the meeting. Founders and sales leads get both lists before the forecast call.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- Read access to open deals, their contacts and companies in HubSpot, Pipedrive or Salesforce (whichever you use), with the buyer's email on each deal's contacts: the whole join runs on those emails and domains.
- Read access to the Gmail and Google Calendar whose threads and meetings count (`gmail.readonly`, `calendar.events.readonly`), and a Gong API access key if you record calls.
- Permission to create tasks or activities in the CRM, only if you want the agent to act on your answer.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **Collect open deals and their people** (HubSpot): Use whichever CRM you have. HubSpot: `POST /crm/v3/objects/deals/search` with `hs_is_closed EQ false` and properties `dealname`, `amount`, `dealstage`, `hubspot_owner_id`, `createdate`, `notes_last_updated`, then `POST /crm/v4/associations/deals/contacts/batch/read` and `/crm/v4/associations/deals/companies/batch/read` for contact `email` and company `domain`. Pipedrive: `GET /api/v2/deals?status=open`, then `GET /api/v2/persons?deal_id={id}` for each person's `emails`. Salesforce: `SELECT Id, Name, Amount, StageName, Owner.Name, CreatedDate, LastActivityDate, Account.Website FROM Opportunity WHERE IsClosed = false` and `SELECT OpportunityId, Contact.Email FROM OpportunityContactRole WHERE Opportunity.IsClosed = false`.
2. **Build the match keys**: Lowercase and trim every email. A deal's domain is the company domain (or its website without `https://`, `www.` and anything after the first `/`), else the domain of its contacts' emails. Free-mail domains (`gmail.com`, `outlook.com`, `hotmail.com`, `yahoo.com`, `icloud.com`) and your own domain never count as a deal domain, so those deals match on exact contact emails only.
3. **Gather contact evidence from the last 14 days** (Gmail): Gmail: per deal, `users.threads.list` with `q` like `{from:@contoso.com to:@contoso.com cc:@contoso.com} newer_than:14d` (exact addresses for free-mail contacts); skip threads whose only outside sender is a `noreply` or notification address. Google Calendar: one `events.list` on `primary` with `timeMin` 14 days ago, `timeMax` now and `singleEvents=true`, dropping `status: cancelled` events and those every outside attendee declined. Gong: one `POST /v2/calls/extensive` for the same 14 days with `exposedFields.parties: true`, keeping parties with `affiliation: External`.
4. **Join the evidence to the deals**: Match calendar attendee `email` and Gong party `emailAddress` to a deal's contact emails first, then to its domain; a Gmail hit already belongs to the deal it was searched for. A deal with no thread, no held meeting and no Gong call is a ghost; leave out deals created in the last 14 days. For each ghost, find the last real contact with the same Gmail search at `newer_than:90d` and the calendar back 90 days, or write "none in 90 days". Evidence that matches no deal feeds the next step.
5. **Run the check the other way** (Google Calendar): Take this week's held meetings with an outside, non-free-mail domain and Gong calls whose `metaData.scope` is `External`, and group them by domain. Flag a domain with no open deal, or whose deal has nothing logged after the meeting started (HubSpot `notes_last_updated`, Pipedrive `GET /api/v2/activities?deal_id={id}&updated_since=<meeting start>`, Salesforce `LastActivityDate`). Skip the domains on a list the person keeps (investors, vendors, recruiters).
6. **Ask, then task owners or log meetings** (HubSpot): Post the ghosts largest amount first, with CRM last activity beside last real contact, plus the unlogged meetings, and ask what to do. On the answer, create a task per ghost for its owner (HubSpot `POST /crm/v3/objects/tasks`, Pipedrive `POST /api/v2/activities`, a Salesforce `Task`) or log the meetings on their deals. Never close a deal or change its stage.

## Route B: the browser

### What you need

- Signed in to the CRM with view access to all open deals, to the Gmail and Google Calendar whose threads and meetings count, and to Gong if you record calls.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://app.hubspot.com/contacts/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **List the open deals**: HubSpot: pick the account, go to **CRM > Deals** in list view, filter **Deal stage** to the open stages and sort by **Amount**. Pipedrive: **Deals** in list view filtered to open deals. Salesforce: **Opportunities**, **All Opportunities**, open stages only. For the 25 largest, note amount, stage, owner, **Last activity date**, the contacts' emails and the company domain.
2. **Search Gmail for each deal**: In `https://mail.google.com/` search `{from:@contoso.com to:@contoso.com} newer_than:14d` with the deal's domain, or the contact's exact address for gmail.com and similar. A thread where they wrote, or you wrote to them, counts; automated notifications do not.
3. **Check Calendar and Gong**: In `https://calendar.google.com/` type the domain in **Search** and look for events in the last 14 days that took place and were not declined. In Gong, search the company name and look for a call in the same 14 days. A deal with none of the three is a ghost; for ghosts, change the Gmail search to `newer_than:90d` to date the last real contact.
4. **Check this week's meetings the other way**: In Calendar's week view, open each meeting with outside guests and take their domain. In the CRM, search the company: is there an open deal, and does its activity timeline show anything logged after the meeting? Skip investors, vendors and recruiters.
5. **Report and ask**: Ghost deals largest first with CRM last activity beside the last real contact and the total, then the unlogged meetings. Ask whether to create a follow-up task for each owner or log the meetings, and do only what the answer says.

Note: Each deal takes three searches, so the browser route suits about 25 deals; past that, use the API route. Gmail and Calendar only show the signed-in person's mail and meetings, so threads that live in a rep's mailbox count only if the rep runs it. Calendar search matches guests' emails as well as titles.

## The report

- **Ghost deals**: open deals with no email, meeting or Gong call with the buyer in 14 days, with total amount.
- **The list**, largest first: amount, stage, owner, when the CRM was last touched and the date of the last real contact.
- **Unlogged meetings**: external meetings this week whose company has no deal or no update since.
- The ask: task the owners, log the meetings, or both.
- What to do first: the largest ghost whose CRM record was touched most recently, since that is the one being reported as alive.

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "hubspot-ghost-pipeline/<date of this run, e.g. 2026-10-16>",
  "item_key": "hubspot-ghost-pipeline",
  "project": "Acme",
  "source": "Ghost pipeline: CRM deals with no real contact skill",
  "kind": "action",
  "title": "11 open deals ($84k) with no real contact in 14 days",
  "summary": "**11 open deals worth $84,000** have no email, meeting or Gong call with the buyer in 14 days, though 9 of them were edited in the CRM in the last 7 days. **3 external meetings** this week have nothing logged in the CRM.",
  "action": {
    "request": "What should I do with the ghost deals and the unlogged meetings?",
    "options": [
      "Task owners and log meetings",
      "Task the owners only",
      "Log the meetings only",
      "Nothing this week"
    ]
  },
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "Deal",
          "Amount",
          "Stage",
          "CRM updated",
          "Last real contact"
        ],
        "align": [
          "left",
          "right",
          "left",
          "left",
          "left"
        ],
        "rows": [
          [
            "Contoso",
            "$18,000",
            "Proposal sent",
            "2 days ago",
            "Email, Sep 29"
          ],
          [
            "Litware",
            "$14,000",
            "Negotiation",
            "4 days ago",
            "Meeting, Sep 24"
          ],
          [
            "Adatum",
            "$12,000",
            "Demo done",
            "1 day ago",
            "Gong call, Oct 2"
          ],
          [
            "Proseware",
            "$9,500",
            "Qualified",
            "6 days ago",
            "None in 90 days"
          ],
          [
            "Coho Winery",
            "$7,000",
            "Proposal sent",
            "3 days ago",
            "Email, Oct 8"
          ],
          [
            "Wingtip Toys",
            "$5,500",
            "Demo booked",
            "5 days ago",
            "Email, Oct 6"
          ],
          [
            "Bellows College",
            "$4,500",
            "Qualified",
            "9 days ago",
            "Meeting, Sep 18"
          ],
          [
            "Northwind Traders",
            "$4,000",
            "Negotiation",
            "2 days ago",
            "Email, Oct 12"
          ]
        ]
      }
    },
    {
      "type": "Text",
      "props": {
        "text": "Showing 8 of 11. Three more under $4,000 add up to $9,500.",
        "variant": "muted"
      }
    },
    {
      "type": "Table",
      "props": {
        "columns": [
          "Meeting this week",
          "When",
          "Company",
          "In the CRM"
        ],
        "rows": [
          [
            "Pricing walkthrough",
            "Tue Oct 27",
            "fourthcoffee.com",
            "No deal for this domain"
          ],
          [
            "Security review",
            "Wed Oct 28",
            "Lamna Healthcare",
            "Nothing logged since Oct 9"
          ],
          [
            "Kickoff call",
            "Thu Oct 29",
            "Trey Research",
            "Nothing logged since Oct 14"
          ]
        ]
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "Start with Contoso ($18,000, Proposal sent): the deal was edited 2 days ago, but nobody at contoso.com has written or met with us since Sep 29.",
        "tone": "warn"
      }
    }
  ]
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

## Run it on a schedule

Suggested: Fridays at 14:00 (`0 14 * * 5`). From cron, with the agent signed in on that machine:

```bash
0 14 * * 5 claude -p "Run the hubspot-ghost-pipeline skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/hubspot-ghost-pipeline#schedule

Source: https://briefkin.dev/skills/hubspot-ghost-pipeline
