Fresh session per sweep — state lives on the HubSpot account record itself (a
singulacomp_onboarding_last_nudge_milestone property marks the last milestone
nudged), not in a local ledger file. A sweep can find several accounts at
once; handle each as an independent unit — a failure checking one account's
milestones is logged and skipped, never blocking any other account in the
same sweep.
Step 0 — Orient: find accounts inside the onboarding window
Query HubSpot for accounts (company or deal, whichever carries
{{customer_since_property}}) where that date falls within the last
{{onboarding_window_days}} days:
For each match, resolve hubspot_owner_id to the owning CSM's name and note
how many days into onboarding the account currently is.
Step 1 — Pull milestones and events from Postgres (read-only)
sql
SELECT milestone, completed_atFROM activation_milestonesWHERE account_id = :account_idORDER BY completed_at;SELECT event_name, occurred_atFROM product_eventsWHERE account_id = :account_id AND occurred_at >= now() - interval '{{onboarding_window_days}} days'ORDER BY occurred_at;
Adapt table and column names to what the project's schema actually calls
them; the shape above is illustrative. The milestone rows are the source of
truth for what's done — product events are supporting evidence for why a
milestone hasn't completed (e.g. zero logins vs. logins with no key action).
Step 2 — Classify against the milestone framework
Milestone
What it means
Expected by
Workspace live
First login after signup
Day 1
Core setup
Key integration or configuration completed
Day 3
First activation event
The core "aha" action completed — the first time the account got real value
Day 7
Team expansion
A second teammate invited and active
Day 14
Full activation
Recurring usage across most of the last 14 days
Day 30 (or {{onboarding_window_days}})
For each account:
On track — every milestone whose expected day has passed is completed.
Stalled at <milestone> — the account is past that milestone's
expected day without completing it, but still inside the onboarding window.
Overdue — past {{onboarding_window_days}} days total without full
activation, regardless of which earlier milestones did complete.
An account can only be stalled at its earliest incomplete milestone — don't
report team expansion as the stall if core setup itself never finished.
Step 3 — Draft the nudge (stalled or overdue accounts only)
Skip this step if singulacomp_onboarding_last_nudge_milestone already equals the
milestone this account is currently stalled at — it was nudged for this exact
stall on a prior run. Draft again only when the stalled milestone has changed
(the account moved past the previously-nudged one and is now stuck further
along, or regressed to an earlier open item) or the marker is missing.
Write the nudge addressed to the customer's specific missed step — not a
generic "how's it going" check-in:
Name the one concrete action tied to the stalled milestone (e.g. "connect
your first data source" for a core-setup stall, not "finish setup").
Keep it short, helpful, and low-pressure — an offer to help, not a warning.
Reference what the account has already done, if anything, so it doesn't
read as a form letter to someone who's made partial progress.
Hold the draft in {{draft_channel}} for the owning CSM to review, edit, and
send.
Step 4 — Alert the owning CSM
Post to {{alert_channel}}, addressed to the owning CSM, with:
The account name and how many days into onboarding it is.
Which milestones are completed and which is the current stall (or that it's
overdue).
The nudge that was drafted (or, if this exact stall was already nudged, a
note that no new draft was made and the prior one is still pending).
This alert is not deduplicated — it reappears every day the account
remains stalled or overdue. It's a working reminder for the CSM, not a
one-time notice.
Step 5 — Write the marker back
For every account that got a new nudge drafted in Step 3, set
singulacomp_onboarding_last_nudge_milestone to that milestone's name and
singulacomp_onboarding_last_nudge_at to now. This is the only HubSpot write —
never the deal stage, the owner, a plan, or a seat count.