PluginBench
Skill
Review
Audit score 70

customer-retention

hubspot/agent-cli-skills

Identify inactive/at-risk customers and create follow-up tasks at scale via CRM filters.

What is customer-retention?

Finds customers with no recent contact or sales activity, flags at-risk subscriptions, and bulk-creates follow-up tasks with associations. Use this when you need to proactively re-engage dormant accounts or prevent churn across a cohort.

  • Search contacts by inactivity signals (no contact in 60+ days, no sales activity, never contacted)
  • Filter subscriptions by status (past-due, at-risk) and map them back to contacts
  • Create follow-up tasks (calls, check-ins) and associate them to contacts at scale
  • Bulk-create task cohorts from search results using jq pipelines and dry-run preview
  • Audit contact lifecycle-stage history to understand why customers churned or moved stages

How to install customer-retention

npx skills add https://github.com/hubspot/agent-cli-skills --skill customer-retention
Prerequisites
  • Install `bulk-operations` skill first (required for JSONL pipe, pagination, and dry-run patterns)
  • Read `sales-execution/SKILL.md` for activity-property tables and association rules
  • Verify portal-specific contact and subscription properties using `hubspot properties get`
  • Ensure your HubSpot API token has `subscriptions-read` scope if filtering subscriptions
Claude Code
Cursor
Windsurf
Cline

How to use customer-retention

  1. 1.Set a cutoff date (e.g., 60 days ago) and search for inactive contacts using `hubspot objects search` with `notes_last_contacted` or `hs_last_sales_activity_date` filters
  2. 2.Optionally filter subscriptions by status (e.g., past-due) and map them to contacts using `hubspot associations list`
  3. 3.Build task payloads from the search results using `jq` to transform contact records into task properties
  4. 4.Run `--dry-run` on the task creation to preview the bulk operation
  5. 5.Create tasks in bulk and capture the output IDs, then associate each task to its contact using `hubspot associations create`

Use cases

Good for
  • Identify all customers not contacted in 60 days and create a bulk call task for re-engagement
  • Find past-due subscriptions and create high-priority follow-up tasks for the account owners
  • Audit never-contacted customers in a lifecycle stage and flag them for outreach
  • Build a reusable at-risk customer cohort as a CRM segment for workflow enrollment
  • Check which at-risk customers were previously enrolled in a sales sequence
Who it's for
  • Sales operations managers building retention workflows
  • Customer success teams proactively managing churn risk
  • Sales leaders auditing inactive account cohorts
  • Revenue operations engineers automating re-engagement campaigns

customer-retention FAQ

What inactivity signals can I filter on?

Use `notes_last_contacted` (no calls/notes/meetings in N days), `hs_last_sales_activity_date` (no emails/tasks/activity), or `!notes_last_contacted` (never contacted). See `resources/customer-health-signals.md` for more signals like email opt-out, stale tickets, and subscription status.

How do I find which customers are at highest churn risk?

Filter by multiple signals: no recent sales activity AND email opt-out, or past-due subscription status. There is no native churn-score property — track risk via a custom property you maintain.

Can I automate re-engagement workflows after creating tasks?

Yes. Use `hubspot segments create` to save your at-risk cohort as a reusable CRM list, then enroll it in a workflow via `hubspot workflows create` (see `workflow-automation/SKILL.md`).

Why do my unassociated tasks not appear in the CRM UI?

Unassociated tasks are invisible in HubSpot. Always call `hubspot associations create` to link each task to its contact after creation.

How do I understand why a customer churned?

Use `hubspot objects history --type contacts --properties lifecyclestage [--id <recordId>]` to see which workflow, integration, import, or UI edit last changed their lifecycle stage.

Full instructions (SKILL.md)

Source of truth, from hubspot/agent-cli-skills.


name: customer-retention description: Identify inactive/at-risk customers via CRM filters and create follow-up tasks at scale. Builds on bulk-operations; defers activity-creation specifics to sales-execution. triggers:

  • "customer retention"
  • "churn risk"
  • "inactive customers"
  • "customer follow-up"
  • "at-risk accounts"
  • "customers not contacted"
  • "renewal"
  • "account health"

Resources

FileWhen to use
resources/customer-health-signals.mdFilter cookbook of churn signals — --filter expressions for notes_last_contacted, hs_last_sales_activity_date, hs_email_optout, stale tickets, subscription status.

Prereqs

Read bulk-operations/SKILL.md first — every read/write below uses its JSONL pipe, pagination, and dry-run/digest patterns. Activity-property tables and association rules live in sales-execution/SKILL.md.

Schema is portal-specific. Verify each property before filtering — e.g. hubspot properties get --type contacts notes_last_contacted, ... hs_last_sales_activity_date, ... --type subscriptions hs_subscription_status. If subscriptions returns 403, your token lacks subscriptions-read — use a private-app token with that scope.

1 — Find inactive customers

CUTOFF=$(date -v-60d +%Y-%m-%d 2>/dev/null || date -d '60 days ago' +%Y-%m-%d)

# No outreach in 60d (calls/notes/meetings update notes_last_contacted)
hubspot objects search --type contacts \
  --filter "lifecyclestage=customer AND notes_last_contacted<$CUTOFF" \
  --properties email,firstname,notes_last_contacted,hubspot_owner_id

# No sales activity in 60d (broader — also catches emails/tasks)
hubspot objects search --type contacts \
  --filter "lifecyclestage=customer AND hs_last_sales_activity_date<$CUTOFF" \
  --properties email,firstname,hs_last_sales_activity_date

# Never contacted
hubspot objects search --type contacts \
  --filter "lifecyclestage=customer AND !notes_last_contacted" \
  --properties email,firstname

For more signals (email opt-out, stale tickets, no open deals) see resources/customer-health-signals.md. For >100 hits, use the pagination loop from bulk-operations.

To audit why a customer churned or moved stage — which workflow, integration, import, or UI edit last changed lifecyclestage — use hubspot objects history --type contacts --properties lifecyclestage [--id <recordId>] [--include-ui]. It flattens each property's source history into one row per change.

2 — Flag at-risk subscriptions

subscriptions is a standard object (hubspot objects types confirms). Enum values for hs_subscription_status are portal-specific — verify before filtering, then plug the exact value in:

hubspot properties get --type subscriptions hs_subscription_status   # lists allowed values

# Past-due — revenue at immediate risk (substitute your verified value)
hubspot objects search --type subscriptions \
  --filter "hs_subscription_status=past_due" \
  --properties hs_recurring_billing_total,hs_subscription_status

# Map an at-risk subscription to its contact for outreach
hubspot associations list --from subscriptions:<sub_id> --to contacts --format jsonl

3 — Create a follow-up task or check-in note

Activity creation lives in sales-execution (full property tables, note + meeting flows). One anchor example — unassociated tasks are invisible in the CRM UI, so always associate:

task_id=$(hubspot objects create --type tasks \
  --property hs_task_subject="Q1 retention check-in" \
  --property hs_task_priority=HIGH --property hs_task_status=NOT_STARTED \
  --property hs_task_type=CALL --property hs_timestamp=$(date +%s)000 \
  --format json | jq -r '.id')
hubspot associations create --from tasks:$task_id --to contacts:<contact_id>

4 — Bulk task creation for a cohort

Pipe a search through jq into one objects create call, then associate. Preview with --dry-run first (bulk-operations covers digest/confirm for >100 rows).

DUE_MS=$(( ($(date +%s) + 2*86400) * 1000 ))   # due in 2 days

# 1. Capture the cohort (same file feeds both create + associate)
hubspot objects search --type contacts \
  --filter "lifecyclestage=customer AND notes_last_contacted<$CUTOFF" \
  --properties firstname > /tmp/inactive.jsonl

# 2. Build task payloads — one per contact
jq -c --arg due "$DUE_MS" '{
  contact_id: .id,
  properties: {
    hs_task_subject: ("Re-engage: " + (.properties.firstname // "customer")),
    hs_task_priority: "HIGH", hs_task_status: "NOT_STARTED",
    hs_task_type: "CALL", hs_timestamp: $due
  }
}' /tmp/inactive.jsonl > /tmp/task_payloads.jsonl

# 3. Dry-run, then create (drop contact_id before piping)
jq -c '{properties}' /tmp/task_payloads.jsonl | hubspot objects create --type tasks --dry-run | head
jq -c '{properties}' /tmp/task_payloads.jsonl | hubspot objects create --type tasks > /tmp/created.jsonl

# 4. Associate each new task to its contact (paste preserves order)
paste <(jq -r '.id' /tmp/created.jsonl) <(jq -r '.contact_id' /tmp/task_payloads.jsonl) \
  | while read task_id contact_id; do
      hubspot associations create --from tasks:$task_id --to contacts:$contact_id
    done

One CLI call for the search, one for the create, then N for associations — no xargs -I{} per record. The output-order guarantee of objects create (one result per stdin line, in order — see bulk-operations "Output shape") is what makes the paste correct.

Known gaps

  • No native churn-score / health-score property — track via a custom property.
  • hubspot segments provides CRM lists for re-engagement cohorts — segments members-list pulls a list's members, and segments create / update-filters save an at-risk audience as a reusable list. Re-engagement enrollment can be built as a workflow via hubspot workflows create / update (see workflow-automation/SKILL.md).
  • hubspot sequences reads Sales Hub sequences (read-only): use sequences enrollments <contact_id> to see whether an at-risk customer was ever engaged through a sales sequence. Sequences are a product API (Sales Hub Professional+, automation.sequences.read scope), not a CRM object type — the CLI cannot create sequences or enroll contacts. This list grows over time; recheck hubspot --help / CHANGELOG.md rather than assuming an API is absent.
  • hubspot associations create does not batch — one CLI call per pair.