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- 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
How to use customer-retention
- 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.Optionally filter subscriptions by status (e.g., past-due) and map them to contacts using `hubspot associations list`
- 3.Build task payloads from the search results using `jq` to transform contact records into task properties
- 4.Run `--dry-run` on the task creation to preview the bulk operation
- 5.Create tasks in bulk and capture the output IDs, then associate each task to its contact using `hubspot associations create`
Use cases
- 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
- 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
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.
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.
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`).
Unassociated tasks are invisible in HubSpot. Always call `hubspot associations create` to link each task to its contact after creation.
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
| File | When to use |
|---|---|
resources/customer-health-signals.md | Filter 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 segmentsprovides CRM lists for re-engagement cohorts —segments members-listpulls a list's members, andsegments create/update-filterssave an at-risk audience as a reusable list. Re-engagement enrollment can be built as a workflow viahubspot workflows create/update(seeworkflow-automation/SKILL.md).hubspot sequencesreads Sales Hub sequences (read-only): usesequences 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.readscope), not a CRM object type — the CLI cannot create sequences or enroll contacts. This list grows over time; recheckhubspot --help/CHANGELOG.mdrather than assuming an API is absent.hubspot associations createdoes not batch — one CLI call per pair.
Related skills
More from hubspot/agent-cli-skills and the wider catalog.

data-enrichment
Match external records to CRM contacts/companies by email or domain, then upsert enriched data in one pass.

deal-management
Run the full deal lifecycle from CLI — discover pipelines, qualify MQLs, advance/reassign in bulk, hunt stalled deals, and close.

quote-to-cash
Build product catalogs, assemble quotes with line items, and track invoices and subscriptions through revenue.

sales-execution
Log calls, notes, meetings, and tasks against contacts and deals in HubSpot CRM.

sales-reporting
Daily briefings, pipeline snapshots, and win/loss analysis from the terminal.

team-ownership
Assign, reassign, and audit CRM record ownership across contacts, companies, deals, and tickets.