--- name: drop-off-rescue description: Find contacts stalled at funnel stages (abandoned cart, signed-up-but-no-trial, trial-but-no-convert), draft stage-appropriate re-engagement emails, and schedule sends via your ESP — with dedupe so weekly reruns never double-send version: "1.0.0" author: Cogny AI platforms: [klaviyo, mailchimp, rule, get-a-newsletter] user-invocable: true argument-hint: "[weekly|audit|free]" allowed-tools: # Cogny Cloud (aggregated) namespace - mcp__cogny__klaviyo__* - mcp__cogny__mailchimp__* - mcp__cogny__rule__* - mcp__cogny__get_a_newsletter__* - mcp__cogny__create_finding - mcp__cogny__write_context_node - mcp__cogny__read_context_node # Cogny Solo / Lite (per-ESP direct) namespace - mcp__klaviyo__* - mcp__mailchimp__* - mcp__rule__* - mcp__get_a_newsletter__* - WebFetch - Bash - Read - Write --- # Drop-off Rescue Push stalled funnel contacts to the next step. This skill finds people who signed up but didn't convert, drafts stage-appropriate re-engagement emails, and (with an ESP MCP connected) schedules the send after explicit confirmation. Re-runnable weekly without double-sending — the skill tracks what was already sent per stage and skips contacts still inside their cooldown window. **Works in three modes:** - `cloud` — Cogny MCP + ESP MCP: full automation, cross-ESP dedupe, findings - `solo` — ESP MCP only: tag-based dedupe, per-ESP automation - `free` — no MCP: pasted CSV of stalled contacts, draft-only output (you send manually) ## Usage ``` /drop-off-rescue # weekly rerun — default /drop-off-rescue weekly # same /drop-off-rescue audit # read-only: report stalls + what would be sent, write nothing /drop-off-rescue free # no MCP — paste CSV of stalled contacts ``` Designed to be scheduled weekly via `/loop` or `/schedule`. On reruns, already-nudged contacts are skipped; newly-stalled contacts are picked up; contacts that progressed get celebrated in the output. ## Step 1 — Mode detection Probe both MCP namespaces: 1. If `mcp__cogny____*` tools are present for at least one ESP → **cloud** mode (and record that ESP as connected). 2. Else if `mcp____*` tools are present for at least one ESP → **solo** mode. 3. Else → **free** mode. If the argument is `free`, force free mode regardless of what's connected. If multiple ESPs are connected, ask the user which one to run against (or offer to run all and cross-dedupe in cloud mode). ## Step 2 — Canonical funnel stages The skill works against a canonical stage taxonomy. The user can extend it, but these slugs drive tag names, campaign naming, and dedupe state — keep them stable across runs. **E-commerce:** | Slug | Meaning | Typical stall | |------|---------|---------------| | `browse-abandoned` | Viewed product, never added to cart | 1–3 days | | `cart-abandoned` | Added to cart, never checked out | 1–2 days | | `checkout-abandoned` | Started checkout, didn't submit | hours–1 day | | `post-first-order-silent` | Made first order, no engagement since | 14–30 days | **SaaS / trial funnel:** | Slug | Meaning | Typical stall | |------|---------|---------------| | `signup` | Created account, did nothing | 1–3 days | | `no-channel` | Signed up, no data source / integration connected | 3–7 days | | `no-trial` | Connected a channel, hasn't started the free trial | 7 days | | `no-client` | Trial active, hasn't connected the tool/client that uses it | 3 days | | `no-subscription` | Trial ended, didn't convert to paid | 0–3 days after trial | | `trial-started-inactive` | Started trial, no meaningful usage | 3–5 days | | `trial-ended-no-convert` | Trial ended, didn't subscribe | 7 days after end | Cogny's own Solo funnel (signup → `no-channel` → `no-trial` → `no-client` → `no-subscription`) is a good reference implementation of this taxonomy. If the user describes a funnel with stages that don't map to these slugs, add new kebab-case slugs — just keep them stable across reruns (the slug is the dedupe key). ## Step 3 — Resolve current stage per contact **Primary signal: ESP tags / segments.** Read contacts tagged `stage:` using the per-ESP tool: | ESP | Read stage tag | Apply stage tag | Schedule/send | |-----|----------------|-----------------|---------------| | Klaviyo | `list_profiles` filtered by segment/property `stage` | `update_profile` (properties) | campaign create + schedule | | Mailchimp | `tool_list_members` filtered by tag | `tool_update_member` (tags) | campaign schedule | | Rule | `tool_list_subscribers` filtered by tag | `tool_add_tag_to_subscriber` | `tool_schedule_campaign` | | Get a Newsletter | `tool_list_contacts` filtered by tag | `tool_update_contact` (tag) | `tool_send_draft` with `time_to_send` | **Klaviyo event-derived fallback (only when `stage:cart-abandoned` tag is absent):** query `list_events` with `metric="Added to Cart"` in the last 7 days, exclude profiles who have a subsequent `Placed Order`. Write the `stage:cart-abandoned` tag back via `update_profile` so the next run is consistent. **Conversational fallback (any ESP, when no stage tags exist at all):** 1. Ask the user: "I don't see any `stage:*` tags on your contacts. Which of these funnel stages apply to your business?" — list the canonical slugs. 2. For each picked stage, ask: "Who is on this stage? Describe the condition — e.g. `joined list > 3 days ago AND no purchase`, or `clicked email X but never visited /signup`." 3. Translate each heuristic into the ESP's available filters (list membership, tags, date ranges, clicked links) and preview the matching count. 4. Offer (under the confirmation gate in Step 7) to apply `stage:` tags now, so future runs are fully automated. ## Step 4 — Already-sent detection (the core mechanism) Weekly reruns must not re-send the same stage email to the same contact. The skill uses a **three-layer hybrid**: ### Layer 1 — contact tag (primary, all modes with an ESP connected) On every successful send, write three tags to the recipient: - `rescue-sent::-W` — append-only; one per week nudged - `rescue-last-stage:` — overwritten; always reflects the latest stage nudged (enables stage-advancement detection in one read) - `rescue-last-sent:` — overwritten; used for cooldown math and the global 7-day anti-fatigue rule Why tag-based primary: works in solo mode without a context tree, survives cross-machine usage, all four ESPs expose a tag-write tool, debuggable by a human in the ESP UI, bulk-clearable if something goes wrong. ### Layer 2 — ESP campaign-name convention (fallback) Every rescue campaign is named exactly: ``` drop-off-rescue / / -W / ``` On rerun, if a contact's tags were stripped (manual edit, sync tool), `list_campaigns` / `tool_list_campaigns` / `tool_list_sent` in the last 35 days is scanned for matching names. Recipients are cross-referenced via the per-ESP report tool (`list_events`, `tool_get_report`, `tool_get_campaign_statistics`, `tool_get_report`). **Deliberately no markers in subject or preheader** — the user-visible inbox copy stays clean. ### Layer 3 — Cogny context-tree log (cloud mode only) Write on every send: ``` insights/drop-off-rescue///last-sent → { "week": "2026-W17", "variant": "A", "esp": "klaviyo", "ts": "2026-04-24T10:00Z" } ``` Read on the next run. This layer catches cross-ESP duplication — the same contact living in both Klaviyo and Mailchimp under the same org won't get two nudges in the same week. ### Dedupe decision A contact is classed **"already nudged at current stage within cooldown"** if **any** of the three layers reports a hit inside the cooldown window. Cooldown defaults (printed in output, user-overridable): | Stage | Cooldown | |-------|----------| | `cart-abandoned`, `checkout-abandoned` | 7d | | `browse-abandoned`, `signup`, `no-channel` | 14d | | `no-trial`, `no-client`, `no-subscription`, `trial-started-inactive` | 21d | | `post-first-order-silent`, `trial-ended-no-convert` | 30d | ## Step 5 — Classify every contact (the weekly loop) For each stage, read the full `stage:` cohort, then classify each contact: | Class | Condition | Action this run | |-------|-----------|-----------------| | **New stall** | On `stage:` now, no `rescue-sent::*` ever | Eligible — variant A | | **Cooldown skip** | Has `rescue-sent::` inside the stage's cooldown window | Skip, count as "cooldown-skipped" | | **Re-stall** | Past cooldown, still on `stage:`, ≤2 prior rescue sends at this stage | Eligible — variant B (tone shift) | | **Progressed** | `rescue-last-stage` is an earlier stage than the current stage tag | Celebrate in output; nudge at new stage | | **Regressed** | `rescue-last-stage` is a later stage than the current stage tag | Flag as anomaly, don't auto-nudge (data issue) | | **Exhausted** | 3 prior rescue sends at this stage without progression | Apply `rescue-exhausted:`, exclude until stage changes | | **Exited** | No stage tag at all, but has `rescue-last-stage` | Clear all `rescue-*` tags in cleanup pass | **Hard caps (enforced regardless of per-stage cooldowns):** - **3 nudges max per contact per stage** before they go to `rescue-exhausted:`. - **Max one rescue email per contact per rolling 7 days across all stages.** If a contact would be hit for two stages in one week, emit only the higher-priority stage (priority order: `cart-abandoned` > `checkout-abandoned` > `browse-abandoned` > `signup` > `no-channel` > `no-trial` > `no-client` > `no-subscription` > activation-stages > `post-first-order-silent` > `trial-ended-no-convert`) and defer the other. ## Step 6 — Draft emails per stage For every stage with ≥1 eligible contact, draft **two variants**: - **Variant A** — direct, short, outcome-led. For first-time nudges. - **Variant B** — value-led, longer, addresses likely objections. For re-stalls (second+ nudge at the same stage). For every variant produce: ``` Stage: Variant: A | B Subject: <≤55 chars, in brand voice> Preheader: <85–100 chars, extends subject, never repeats it> Body: Primary CTA: