# Linkedin Dm Signal Classifier

> Use this skill to scan a team member's LinkedIn DMs daily and turn conversations into routed signals. It classifies each chat into a bucket (customer support, commercial, pipeline, re-engagement, prospect, partner, VC, community, and more), updates account memory and the CRM, scores qualified signals and routes them to your MQL channel, and closes every scored signal with a suggested next move —…

- **Type:** Skill
- **Install:** `agentstack add skill-swan-gtm-gtm-skills-linkedin-dm-signal-classifier`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [swan-gtm](https://agentstack.voostack.com/s/swan-gtm)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [swan-gtm](https://github.com/swan-gtm)
- **Source:** https://github.com/swan-gtm/gtm-skills/tree/main/skills/ariel-cohen/linkedin-dm-signal-classifier

## Install

```sh
agentstack add skill-swan-gtm-gtm-skills-linkedin-dm-signal-classifier
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

## Template placeholders

Replace every `{{...}}` before enabling. See the setup checklist reference for the full setup list.

- `{{TRIGGER_OWNER}}` — Team member whose LinkedIn DMs are scanned (one skill instance per person)
- `{{CRM}}` — Your CRM (e.g. HubSpot, Attio)
- `{{SIGNALS_CHANNEL}}` — Slack channel for all DM signal posts + daily digest
- `{{EXPANSION_CHANNEL}}` — Slack channel for expansion/churn-risk cross-posts (optional)
- `{{MQL_CHANNEL}}` — Slack channel where your lead-scoring alerts post qualified accounts
- `{{PARTNERSHIPS_OWNER}}` — Person to flag partner/agency signals to
- `{{FUNDRAISING_OWNER}}` — Person to flag investor inquiries to (usually a founder)
- `{{LEAD_SCORING_SKILL}}` — Sub-skill reference: your lead scoring & qualification methodology
- `{{OUTREACH_SKILL}}` — Sub-skill reference: your outreach/sequence-building methodology
- `{{HIGH_ACV_THRESHOLD}}` — Company profile that suggests medium-to-high ACV (default: >30 employees, funded, or VP+/C-suite/Founder contact)
- `{{SELF_SERVE_THRESHOLD}}` — Company profile that should route to your self-serve / low-touch motion instead of being scored (default:  **Global rule — Verbatim message required in all Slack posts:**
> Every Slack signal post (all buckets except VENDOR_SPAM) must include the
> **verbatim text of the last inbound message** from the contact, quoted in
> a blockquote. Do not paraphrase or summarize as a substitute. If the
> thread has multiple inbound messages since the last outbound, include the
> most recent one verbatim; you may briefly note context from earlier
> messages separately.

#### VENDOR_SPAM
Skip. No writes, no actions.

---

#### CUSTOMER_SUPPORT

1. Update **account memory**:
   ```
   [CUSTOMER SUPPORT DM] [trigger owner name] ↔ [contact name, title] — [date]
   Issue type: [bug / billing / credits / complaint / at-risk]
   Summary: [1 sentence]
   ```
2. Create or update the contact in {{CRM}} and log a note:
   - Find or create the contact record (name, title, LinkedIn URL, email if
     available), associated with the company
   - Log a note on the contact: `DM via LinkedIn — [date]. [contact name]
     ([title]). Support signal. Issue: [type]. Summary: [1 sentence].`
3. Post to {{SIGNALS_CHANNEL}}:
   - `🚨 Customer Support DM`
   - Company name + domain
   - Contact: name, title
   - Issue type: 🐛 bug / 💳 billing/credits / 😤 complaint / ⚠️ at-risk
   - What they said (verbatim blockquote)
   - ⏰ Awaiting reply line (if applicable)
   - Conversation owner: [trigger owner name]
   - `Note: Urgent — route to your support process`

---

#### CUSTOMER_COMMERCIAL

1. Update **account memory**:
   ```
   [CUSTOMER COMMERCIAL DM] [trigger owner name] ↔ [contact name, title] — [date]
   Signal: [expansion / renewal / pricing / churn risk]
   Summary: [1 sentence]
   ```
2. Create or update the contact in {{CRM}} and log a note (same format as
   above, with the commercial sub-type).
3. Post to {{SIGNALS_CHANNEL}}:
   - `📩 Customer Commercial DM`
   - Company name + domain; contact name, title
   - Signal type: 📈 expansion / 🔄 renewal / 💬 pricing question / ⚠️ churn risk
   - 1-sentence summary; what they said (verbatim blockquote); ⏰ line if
     applicable; conversation owner
4. **If signal is expansion or churn risk:** also cross-post to
   {{EXPANSION_CHANNEL}}.

---

#### ACTIVE_PIPELINE

1. Update **account memory**:
   ```
   [PIPELINE DM] [trigger owner name] ↔ [contact name, title] — [date]
   Deal stage: [current stage]
   Signal: [objection / timeline / pricing / champion re-engagement / positive]
   Summary: [1-2 sentences]
   ```
2. Load and follow {{LEAD_SCORING_SKILL}}. A mid-deal DM is strong
   first-party intent — do not apply any tier cap that normally limits
   passive signals.
3. Create or update the contact in {{CRM}} and log a note:
   - Find or create the contact record (name, title, LinkedIn URL, email if
     available), associated with the company
   - Log a note on the contact: `DM via LinkedIn — [date]. [contact name]
     ([title]). Deal stage: [stage]. Signal: [type]. Summary: [1-2 sentences].`
4. Post to {{SIGNALS_CHANNEL}}:
   - `📩 Pipeline DM Signal`
   - Company + current deal stage; contact name, title; signal summary;
     what they said (verbatim blockquote); tier scored; ⏰ line if
     applicable; conversation owner
5. Continue to Step 6.5.

---

#### RE_ENGAGEMENT

1. Update **account memory**:
   ```
   [RE-ENGAGEMENT DM] [trigger owner name] ↔ [contact name, title] — [date]
   Prior stage: [closed-lost / nurture / self-serve]
   Summary: [1 sentence]
   ```
2. Treat as first-party intent. Load and follow {{LEAD_SCORING_SKILL}}.
3. Post to {{SIGNALS_CHANNEL}}:
   - `🔁 Re-engagement DM`
   - Company + prior stage; contact name, title; what they said (verbatim
     blockquote); tier scored; ⏰ line if applicable; conversation owner
4. Continue to Step 6.5.

---

#### PROSPECT_HIGH_ACV

1. If the company is not yet in the workspace: enrich via research tools and
   add it.

2. **⛔ SELF-SERVE GATE — Run this before anything else. No exceptions.**

   Check employee count (take the lower of enrichment vs LinkedIn) and total
   funding raised.

   **If the company profile meets {{SELF_SERVE_THRESHOLD}}:**
   - Move the account to your self-serve / low-touch stage and tag it
     accordingly (sync the stage to {{CRM}} if you mirror stages there)
   - Post to {{SIGNALS_CHANNEL}} using the PROSPECT_HIGH_ACV format but
     note: `⛔ Self-serve gate triggered — under threshold. No scoring.
     Moved to self-serve.`
   - **⛔ STOP. Do not proceed to memory update, scoring, or MQL routing.**

   If employee count is unavailable: treat as unknown and apply the gate by
   default. A strong funding signal alone (well above threshold) may
   override the gate.

3. Update **account memory**:
   ```
   [PROSPECT DM] [trigger owner name] ↔ [contact name, title] — [date]
   ACV tier: HIGH
   Summary: [1 sentence]
   ```
4. Load and follow {{LEAD_SCORING_SKILL}}. DM = first-party intent.
   **Promotion rule:** A substantive inbound DM (the contact initiated a
   real business conversation) is strong first-party intent. If the
   account's current tier is one below your alert tier, instruct the scoring
   skill that this direct DM interaction justifies promotion one tier up —
   unless there is contrary evidence in the account memory (explicit
   disqualification, stated "not now", churned for cause). **Never lower an
   existing tier as a result of this DM run.**
5. Create or update the contact in {{CRM}}:
   - Find or create the contact record with name, title, LinkedIn URL, and
     email if available
   - Associate with the company record
6. Post to {{SIGNALS_CHANNEL}}:
   - `📩 High-ACV Prospect DM`
   - Company + enriched summary (employees, funding, description)
   - Contact: name, title, what they said (verbatim blockquote)
   - Tier scored; ⏰ line if applicable
   - Close with a note that accurately reflects what actually happened:
     - Both ran: `Note: Enriched + scored.`
     - Enrichment ran, scoring skipped: `Note: Enriched. Scoring skipped.`
     - Enrichment skipped: `Note: Enrichment skipped. [Reason].`
7. Continue to Step 6.5.

---

#### PROSPECT_LOW_ACV

1. Do NOT create a workspace company record. Do NOT run scoring.
2. Post to {{SIGNALS_CHANNEL}}:
   - `📩 Low-ACV Prospect DM`
   - Contact: name, title, company (if any); what they said (verbatim
     blockquote); ⏰ line if applicable; conversation owner
   - `No company — enrichment skipped` (only if no company was identified)
   - `Note: Low ACV signal — no workspace record created.`

---

#### IMPLEMENTATION_PARTNER

1. Update **account memory** (if company record exists):
   ```
   [PARTNER DM] [trigger owner name] ↔ [contact name, title] — [date]
   Status: ACTIVE / POTENTIAL
   Summary: [1 sentence on what was discussed]
   ```
2. Post to {{SIGNALS_CHANNEL}}:
   - `🤝 Implementation Partner DM`
   - Company name, domain, what they do; contact name, title
   - Status: **ACTIVE** or **POTENTIAL**
   - Summary of what was discussed; what they said (verbatim blockquote);
     ⏰ line if applicable; conversation owner
   - `Note: Flagging for {{PARTNERSHIPS_OWNER}}'s awareness`

---

#### COMMUNITY_SPEAKING

1. Post to {{SIGNALS_CHANNEL}}:
   - `🎤 Community / Speaking Opportunity`
   - Contact: name, title, company (if any)
   - Opportunity type: podcast / event / community demo / distribution /
     co-marketing
   - What they proposed (verbatim blockquote); ⏰ line if applicable;
     conversation owner
   - `Note: Surface for review — no action taken.`

---

#### VC_INQUIRY

1. Post to {{SIGNALS_CHANNEL}}:
   - `💼 VC Inquiry`
   - Contact: name, title, fund name; what they said (verbatim blockquote);
     ⏰ line if applicable; conversation owner
   - `Note: Surface for {{FUNDRAISING_OWNER}}'s awareness.`

---

#### COLLABORATION

1. Post to {{SIGNALS_CHANNEL}}:
   - `🤜 Collaboration / Peer Exchange`
   - Contact: name, title, company; brief summary (1 sentence); what they
     said (verbatim blockquote); ⏰ line if applicable; conversation owner

---

#### UNKNOWN

1. Post to {{SIGNALS_CHANNEL}}:
   - `❓ Unclassified DM`
   - Contact: name, title, company (if any); what they said (verbatim
     blockquote); ⏰ line if applicable; conversation owner
   - `Note: Could not classify — needs human review.`

---

### Step 6.5 — MQL Routing & Suggested Next Move

This step applies whenever {{LEAD_SCORING_SKILL}} was called in Step 6 —
i.e., for ACTIVE_PIPELINE, RE_ENGAGEMENT, and PROSPECT_HIGH_ACV signals.

#### MQL channel routing
Let the scoring skill's standard alert routing run in full — do not
suppress it.

**⚠️ CRITICAL — Use your standard lead-scoring alert format for
{{MQL_CHANNEL}} posts. Do NOT post a custom DM-signal format to that
channel.** DM context (verbatim last inbound message, conversation owner,
days awaiting reply) must be embedded **within** the standard format — with
the verbatim DM woven in as the primary signal (e.g., "Inbound DM from
[name] ([title]) — [verbatim quote]") — not posted as a separate custom
block. One channel, one format: readers of the MQL channel should never
need to learn a second layout.

#### Decide the next move
After scoring completes, read the full DM thread, account memory, and
current deal state, then decide the right next action. Apply these rules:

**The person already in the thread — suggest a reply, never enroll in a
sequence:**
The contact is in a live conversation with the trigger owner. Never enroll
them in an automated sequence. Instead, draft a suggested reply in the voice
of the conversation owner (the trigger owner). If the contact is addressing
or asking for someone else on your team, name that person as the right
sender in the draft. Include the draft directly in the Slack post so the
owner can send it manually.

**Other people at the account — outreach is allowed, with checks:**
Outreach to buying-committee members who are NOT already in the thread is
permitted. Before building any sequence:
1. Run a duplicate-outreach check: search all existing sequences for each
   target contact in any status. If a sequence already exists for them —
   finished, running, or awaiting approval — do not build a new one; flag
   for human review instead.
2. Follow {{OUTREACH_SKILL}} for sender selection and sequence construction.
3. All DM-originated outreach must be queued for approval — never
   auto-send, regardless of tier.

**Approval always required:**
A DM thread means a human relationship is in motion. The human decides what
goes out. No exceptions.

#### Slack post closing line
End every ACTIVE_PIPELINE, RE_ENGAGEMENT, and PROSPECT_HIGH_ACV signal's
Slack post with a `Suggested next move:` line — one concrete, actionable
recommendation. Examples:
- `Suggested next move: [owner] should reply today — draft above.`
- `Suggested next move: Enroll [VP Sales name] in a sequence — queued for approval.`
- `Suggested next move: No action — await their reply before following up.`
- `Suggested next move: [teammate] to take over — DM context in thread above.`

---

### Step 7 — End of Run Summary

After processing all chats, **always** post a summary to
{{SIGNALS_CHANNEL}}. This post is mandatory on every run — including runs
where zero signals were found. Silence must mean a failure, never "nothing
found."

**If at least one non-noise signal was found:**
```
📋 LinkedIn DM Scan — [trigger owner name] — [date]
Chats fetched: [total fetched] | Processed: [N after filter]
Signals found: [N] — support: X, commercial: X, pipeline: X, re-engagement: X,
high-ACV prospect: X, low-ACV prospect: X, partner: X, community/speaking: X,
VC: X, collab: X, unknown: X
Vendor spam / noise skipped: [N] ([breakdown: recruiters, vendors, outbound-only, etc.])
⚠️ Memory-full warnings: [N accounts] (list company names if any)
```
Omit the memory-full line if count is 0.

**If zero non-noise signals were found:**
```
📋 LinkedIn DM Scan — [trigger owner name] — [date]. Chats checked: [N]. No new signals.
```

---

### Step 8 — Write the Cursor

Only after all chats have been processed successfully, write the cursor to
the trigger owner's user memory:
`LI DM last run: [ISO timestamp of when this run started]`

Use the timestamp from when the run started (captured at Step 1), not the
end time — this ensures the next run re-checks any chats that became active
mid-run.

---

### Important Constraints

- LinkedIn calls are sequential — never parallel
- Never surface noise signals — filter aggressively
- Never auto-send outreach. The only outreach this workflow may produce is a
  drafted reply surfaced in Slack, or an approval-queued sequence built per
  Step 6.5 — a human sends everything.
- If a company is tagged as competitor or vendor: skip, log in memory only,
  no Slack post
- **Memory-full handling:** If an account memory write fails due to capacity,
  do NOT delete or trim existing entries to make room. Skip the memory write
  entirely. Add `⚠️ account memory full — signal not persisted` to that
  signal's Slack post, and count it in the end-of-run digest.

---

## What good looks like

A great run puts every business-relevant DM in exactly one bucket with the right routing: urgent customer-support signals surface same-day with the verbatim complaint quoted in a blockquote, qualified signals get scored and land in {{MQL_CHANNEL}} in the standard alert format, every scored signal's post closes with one concrete `Suggested next move:` line (often a ready-to-send drafted reply), the 7-day dedup keeps the channel quiet (updates use the short 🔄 format), the end-of-run digest posts even on zero-signal days — silence must mean failure, never "nothing found" — and the cursor only advances after a fully successful run.

Mediocre looks like: vendor spam surfacing as signals, unchanged threads re-posted, a missing digest, awaiting-reply flags on threads where the owner spoke last, custom DM formats polluting the MQL channel, the contact in the thread getting enrolled in an automated sequence, or anything auto-sent without human approval.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [swan-gtm](https://github.com/swan-gtm)
- **Source:** [swan-gtm/gtm-skills](https://github.com/swan-gtm/gtm-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-swan-gtm-gtm-skills-linkedin-dm-signal-classifier
- Seller: https://agentstack.voostack.com/s/swan-gtm
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
