recoup-internal-sales

v2026.09.24

INTERNAL Recoup staff sales workflow; invoke only for requests explicitly naming recoup-internal. Run the daily sales sweep across Stripe, Privy, valuations, both Attio funnels, credits, tasks, chats, and email history. Research and prioritize customers and cold prospects, maintain CRM follow-ups, and prepare outreach and meeting materials. Every email requires the user's explicit approval of the exact reviewed draft before sending. Use for sales sweeps, pipeline follow-up, cold outreach and podcast guest invites and nudges, new-lead research, and qualified-call prep. Requires Recoup API, Attio, Stripe, Privy and Supabase access; Exa for cold research. Never use for customer-facing or artist requests.

GitHub
安装命令
npx skhub add recoupable/recoup-internal-sales
Markdown
SKILL.md

Recoup Sales Sweep

No email leaves this workflow before the user reviews and explicitly approves that exact draft for sending. A request to act as CSO, run the sweep, contact a quota, or "send outreach today" authorizes preparation, not unreviewed sends. This applies to customers, cold prospects, replies, follow-ups, and test emails, through every sender and tool. An available credential is not approval.

Read references/outreach-approval.md before preparing outreach. It defines the quality review, the approval record, and the final check before a send. References, subagents, scheduled jobs, and send helpers must follow the same boundary. Missing or ambiguous approval means awaiting approval, never an inferred yes.

The repeatable prospecting + follow-up motion for Recoup staff. Eight data pulls plus one synthesis: read every place a sales signal shows up, collapse it into one ranked list of people to follow up with, then act — enrich/advance the Attio pipeline and draft the outreach. First drafted 2026-07-23; refine the steps below against each real run.

Define the window up front as UTC [FROM, TO) — usually since the last sweep (default last 24h). Run the eight pulls in parallel (subagents work well; each is self-contained), then synthesize.

Run the acquisition circuit-breaker check before anything else. If no new paid customer has landed in 14 days, the default priority order will send you into retention work again and the sweep will not produce a sale.

What this is (and what it is not)

This skill is forward-looking: it produces actions (who to contact, why, how). It deliberately reuses the data plumbing of two sibling skills instead of re-implementing it — load them for the ready-made queries/scripts:

  • recoup-internal-weekly-usage-review — the retrospective numbers (Stripe, Privy logins, the credits/chats/tasks/valuations SQL pack). This sweep turns those same pulls into follow-ups; that skill reports the totals.
  • recoup-internal-funnel-valuation-pipeline — the deep motion for one valuation lead: Attio funnel recipes, qualification rubric, the valuation PDF, and the outreach template. Step 3 hands qualified new valuations to it.

Also useful: recoup-internal-account-health-report (deep dive on one account you're about to contact), recoup-internal-task-email-audit (task-run health for step 6), recoup-platform-api-access (raw Recoup API).

Prerequisites

  • RECOUP_API_KEY / RECOUP_ACCESS_TOKEN — the primary source. A recoup_sk_ key, or an admin Privy JWT for cross-account work (see Source order). Load recoup-platform-api-access for the endpoint map.
  • ATTIO_API_KEY — read + write (this sweep writes follow-ups back).
  • Supabase read access to production (MCP execute_sql or psql) — augmentation only, for the fleet-wide aggregates the API has no endpoint for.
  • Stripe: authenticated CLI or a live restricted key (pass --api-key — the keychain is often unreadable in sandboxed shells).
  • PRIVY_APP_ID + PRIVY_PROJECT_SECRET — Privy Management API.
  • EXA_API_KEY — cold outreach only (references/cold-outreach.md). It is the api project's Vercel variable (one value across dev/preview/prod), so read it from api/.env.local; the same key backs POST /api/research/people.

Research and drafting do not authorize sending. Attio follow-ups and confirmed-dead task changes follow Guardrails; an email is a separate action requiring the exact-draft approval in references/outreach-approval.md. Prepare the complete review packet before asking. Do not call an endpoint that creates an account or triggers an email as a research shortcut; check side effects before running a report, valuation, or task.

Source order — dogfood the API first

1. Recoup API → 2. Supabase. Reach for api.recoupable.dev first and drop to SQL only for what it cannot answer. This is not a style preference: the sweep is run by the people who own the product, and every pull is a free QA pass. The 2026-07-26 run found two product bugs purely by looking at API responses that no SQL query would have surfaced.

Use the API for anything scoped to one account, artist, or task:

NeedCallWhy not SQL
A customer's tasksGET /api/tasks?account_id=Returns owner_email, model, next_run, upcoming, recent_runs, trigger_schedule_id — none of which exist as columns on scheduled_actions. A schedule that will never fire is only visible here.
An artist's socialsGET /api/artists?account_id=Socials are embedded as account_socials; the hand-rolled accounts → account_socials → socials join is where mistakes happen.
Remove an artistDELETE /api/artists/{id}Encodes the last-owner check and the fail-closed song-dependency guard. Raw SQL skips both.
Email → existing accountRead-only account_emails lookupPOST /api/accounts can create the account and email them; never use it for sales discovery.
Refresh a profilePOST /api/socials/{id}/scrapeReal scrape; SQL only shows you stale rows.

Admin cross-account access. An admin Privy JWT plus an account_id override lets you read/write in a customer's context without their key — account_id as a query param on GETs, in the JSON body on PATCH/DELETE. Authorized by validateAuthContext / checkIsAdmin. Confirm with GET /api/admins → {"isAdmin":true}. These JWTs expire in ~1 hour — re-request rather than debugging a sudden 401.

Drop to SQL only for the fleet-wide sweep — the cross-account aggregates that have no endpoint: per-account credit burn, negative balances, went-silent accounts, zombie tasks, valuation runs in a window, chat-title themes. That is most of the eight pulls, and SQL is genuinely the right tool there. The rule is about account-scoped reads, not about banning SQL.

When the two disagree, the API is the customer's truth — it is what the product actually serves them. A DB row the API doesn't surface is not something the customer can see.

Check this first — the acquisition circuit-breaker

Before running the sweep, ask one question: how many new paid customers have we added in the last 14 days? If the answer is zero, the sweep's normal priority order is actively wrong and you must switch modes.

Why this exists: seven of the eight pulls below read our own database. Every one of them requires the person to already be a user. That makes this an excellent farming motion with almost no hunting, and the default ranking (failed payment > new customer > warm valuation lead) puts retention ahead of acquisition every single time. On a small base with noisy retention signals, you never reach pull 3.

The one exception is the Agency Leads funnel (pull 4), which is the only surface in this sweep where a live lead need never have touched the product. Agency revenue is a separate line from subscriptions and it converts on a different clock: a build or advisory engagement can close from a warm personal contact in a week, with no signup, no credits and no activation. When the product funnel is cold, the agency board is often the fastest path to revenue in the whole sweep — read it first, not last.

That failure mode is not hypothetical. Measured 2026-08-04: two months with zero new sales, while 93 distinct accounts ran a catalog valuation in 60 days (241 runs in June, 198 in July) and 56 of them ran exactly once and never returned. The demand was there the whole time. Nobody worked it, because the sweep kept routing the day into retention on existing accounts.

In acquisition mode, do this instead — in this order:

  1. Work the valuation backlog before anything else. Pull 3, but over the whole backlog, not just the sweep window. Every account that ran a valuation and has no Attio entry is an unworked, hands-raised lead. Prioritize one-and-done runs: they got their number and left, which is the sharpest unanswered-question signal we have.
  2. Work the Agency Leads board. Pull 4. Anything at In Conversation with no time on the calendar is a warm lead one message away from an advancement, and it does not require the person to be a user at all.
  3. Look outside the database. No other pull does this, so it will not happen unless you make it happen: named-account research, inbound-adjacent communities, and the enterprise/custom-dev angle for any label or catalog-holder domain already in Attio. Hunting is not a data pull; it is a decision to spend the day differently. New agency leads land on agency_leads at New. The step-by-step hunting motion is references/cold-outreach.md: Exa Agent run → verify emails → research the business → position across all services → earn one deliverable (valuation, metadata run, YouTube gap audit, website proposal, superfan list) → short one-link email. First run 2026-09-06 cost $1.27 and sent one email the same afternoon.
  4. Cap retention work. Timebox existing-account work so it cannot consume the day. A zombie task or a stale follow-up is real, but it will never produce a new sale.

Return to the normal ordering once a new paid customer lands.

The eight pulls

Each pull is: what to read → the sales signal → the action. Always drop test rows (sweetmantech*, sidney@, @example.com, [TEST], preview-auth-probe).

These eight are the fleet-wide layer, so most of them are legitimately SQL (or Stripe/Privy/Attio). The API-first rule bites at the next step: the moment the ranked list names a person, every drill-down on that account — their tasks, artists, socials, scrape freshness — goes through api.recoupable.dev, not another SELECT. Ranking is a SQL job; qualifying a named lead is an API job.

1. Stripe — new money to protect

  • Read: subscriptions created / trials converted / first invoices in the window; failed payments + disputes; credits_topup sessions (weekly-usage-review §3 has the exact calls).
  • Signal: a new paid customer or trial-converter → welcome + make-sure-they- activate follow-up (the highest-value moment to reach out). A failed payment / dispute → save-the-account follow-up, today. A top-up loop (many identical small sessions) → a "let's right-size your plan" conversation.
  • Action: log the person in Attio; draft the welcome / recovery note.

⚠️ A raw checkout-session count measures bots, not demand. autoRechargeOrFail mints a Checkout session server-side whenever a scheduled run is short of credits, so the session list is dominated by cron artefacts with no human anywhere near them. Measured 2026-08-04: 100 live sessions over two days, 0 paid, 0 with a customer_email, 49% created inside four distinct minutes of the hour, across 22 accounts. Reading that as "people are trying to sign up" is backwards — and reading its absence as "nobody wants us" is equally wrong.

Always filter before you quote a number: keep only sessions with a non-null customer_email, then drop any minute-cluster that lines up with an account's cron schedule. What survives is human intent. If nothing survives, say "we cannot see human purchase intent" — not "there is none."

The same artefact appears at the customer level: a Stripe customer with email: null, no payment methods, and a created timestamp inside the account's own cron window was minted by the same code path. Never write that up as an "abandoned checkout" — check created against the account's task schedule first.

2. Privy — who is signing in

  • Read: the Privy-logins script from recoup-internal-weekly-usage-review — it paginates the Privy Management API into new signups vs returning for the window.
  • Signal: a new signup with no chats yet → onboarding-stall follow-up. A returning power user → expansion. An enterprise/label domain email → high- value human outreach. Cross-check session-riders (long sessions never bump latest_verified_at) against the interactive-chats query in step 7.
  • Action: route new signups through the activation-funnel lens; flag warm enterprise leads for a personal note.

3. Valuations — new catalog leads

  • Read: playcount_snapshots created in the window (weekly-usage-review SQL query 13). A row with a NULL catalog is a measured-but-unclaimed run — a brand-new warm lead nobody is working yet.
  • Signal: every new valuation is a lead; unclaimed ones are the freshest.
  • Action: hand each qualified lead to recoup-internal-funnel-valuation-pipeline (research → qualify → enrich Attio → valuation PDF → outreach). Don't re-implement that motion here.

4. Attio — the follow-up backlog (the heart of this sweep)

The one pull that is only about follow-through. Read BOTH people funnels every sweep — they are separate revenue lines and a sweep that reads one is blind to half the pipeline:

Listapi_slugWhat lives there
Valuation Leadsvaluation_leadsProduct/subscription funnel. Someone ran a catalog valuation and could become a Pro subscriber.
Agency Leadsagency_leadsServices funnel. Custom build, advisory, or retainer work. Usually arrives as a person emailing us, often from the personal network, and frequently has nothing to do with music.

Load recoup-internal-funnel-valuation-pipeline for the Valuation Leads funnel recipes (list entries, stages, field slugs, enrich/advance calls). The same Attio API shapes work for agency_leads.

Never file a lead on the wrong list. An agency lead parked in Valuation Leads inflates the valuation funnel with someone who never ran a valuation, exactly the way a wrong lead_source does. If the person has no catalog interest, they are an agency lead. A person can legitimately appear on both, but only once each side is real.

Valuation Leads — stages and SLA

New → Report Delivered → Qualified → Pro Offer Sent → Pro Active (Won) → Lost

  • Owed a nudge: entries at Report Delivered or Pro Offer Sent whose last activity is older than the follow-up SLA (default 3 business days) with no reply/advance — someone we reached out to who went quiet.

Agency Leads — stages and SLA

New → In Conversation → Call Booked → Scoping → Proposal Sent → Won → Nurture → Lost

Fields: stage, buyer_or_referrer (Buyer / Referrer / Unknown), project_type (Build / Advisory / Retainer / Unknown), lead_source (Podcast for guests), owner, est_project_value, lost_reason.

  • The stage that matters most is In Conversation. It means they replied but no time is on the calendar. Call Booked is deliberately separate, because "agreed to a call" and "a time exists" are different states and the gap between them is where this pipeline has historically died — two threads went cold for four years and three and a half years respectively, both immediately after the lead said yes to a call. Both lapses were ours. Flag any entry sitting at In Conversation for more than 3 days as the most urgent item on the agency side, ahead of anything further down the board.
  • Nurture is where good contacts go to be forgotten unless policed. Any Nurture entry with no dated follow-up task is a bug: create the task or move it to Lost.
  • Resolve buyer_or_referrer before scoping. Personal-network leads are frequently introductions, not buyers, and treating a referrer as a buyer wastes a cycle.
  • Leave est_project_value empty until the work is scoped. A guessed number makes an unqualified lead look qualified.
  • The podcast is the top of this funnel. Cold outreach is a podcast invite, the episode is the discovery call, and the day-7 follow-up is three builds we could do. Guests are lead_source = Podcast on this list, mapped onto the existing stages; the loop with its consent gate, the invite and day-7 templates, the episode-copy rules, the cover spec and the Spotify for Creators edit path are in references/podcast-guest-pipeline.md. Run its five-point invite checklist before any review copy (subject Interview request: <topic>, no body sign-off, no stock close, full unslop including the self-audit across the batch, every referenced source hyperlinked); each item was a real defect on the 2026-09-22 run.

Both lists

  • Flagged for follow-up: any entry whose note / next-step says to circle back.
  • Signal → action:
    • Non-responder, < 3 touches → draft the next nudge (new angle, not a resend).
    • Non-responder, ≥ 3 touches over ~2 weeks → move to Lost with lost_reason = No response so the board stays honest.
    • Replied / booked → advance the stage and set the next step.
  • Always set the owner so every live lead is accountable to a person.
  • A personal contact is not a sequence. Much of the agency list comes from the founder's own network. One light nudge from a personal inbox with a new angle, never an automated cadence.

5. Credits — engagement and billing risk

  • Read: per-account burn, current balances, and prior-week-active-now-silent (weekly-usage-review SQL queries 2, 4, 8).
  • Signal: heavy burners → healthy; candidate for expansion / a testimonial. Negative balance still burning → billing risk and a top-up conversation (nothing stops them at zero). Went silent (active last week, zero this week) → churn-save follow-up while it's still warm.
  • ⚠️ Burn reads wrong in BOTH directions. High burn is not engagement — a daily scheduled task produces usage events and looks like an active account. And near-zero burn is not disengagement: it is just as often an account we never funded. Confirm against step 7 before calling anyone active or dead.
  • Read the balance and the plan together (see Credits mechanics below). An account that has been at the same balance for months is not frugal, it is unrefilled.
  • Action: log the follow-up; for a billing-risk account, pair the outreach with the plan-fit conversation from step 1.

Before quoting anyone's balance, read references/credits-mechanics.md. The refill is lazy and reading a balance mutates it; it SETS rather than adds, so on a free-tier account above 333 a read silently reduces it; and isPro is derived from Stripe by account id, so a customer whose Stripe record is missing metadata.accountId reads as free tier while paying.

6. Tasks — zombie schedules on inactive accounts

Scheduled tasks keep firing (and burning AI tokens) long after an account goes cold. Find enabled schedules whose owner has no recent usage and no recent login — reach out to confirm the task still adds value, else turn it off to save spend. scheduled_actions.last_run is dead (not written by the Workflows runner), so judge activity by usage_events, not that column.

-- Zombie tasks: enabled schedules whose account had NO usage in the last 30 days.
-- (Cross-check task-run health with recoup-internal-task-email-audit.)
SELECT sa.account_id, ae.email, sa.title, sa.schedule, a2.name AS artist,
       (SELECT MAX(created_at) FROM usage_events ue
          WHERE ue.account_id = sa.account_id) AS last_usage
FROM scheduled_actions sa
LEFT JOIN LATERAL (SELECT email FROM account_emails
  WHERE account_id = sa.account_id LIMIT 1) ae ON true
LEFT JOIN accounts a2 ON a2.id = sa.artist_account_id
WHERE sa.enabled = true
  AND NOT EXISTS (SELECT 1 FROM usage_events ue
    WHERE ue.account_id = sa.account_id
      AND ue.created_at >= now() - interval '30 days')
ORDER BY ae.email NULLS LAST, sa.title;
  • Signal: a still-firing task for a dormant / non-paying account = wasted tokens and a re-engagement opening ("this report still runs — still useful?").
  • Action: draft the check-in. Disable the schedule only after no-response or an explicit "turn it off" — see Guardrails; disabling is a mutation.

7. Chats — what customers actually use Recoup for

  • Read: chats created in the window with titles (weekly-usage-review SQL queries 5a/5b). Titles are admin-readable; bodies are owner-scoped (see account-health caveat).
  • ⚠️ Do not split on the 'Scheduled generation' topic — it no longer identifies autopilot. Scheduled rooms now commonly carry a NULL topic, and titled rooms include cron output (a real one: "Weekly Performance Dashboard 2026-07-20 to 2026-07-26"). Two tests that do work:
    • Clock alignment. A cron fires at the same minute every day; a human is off-clock. Group an account's rooms by extract(minute from updated_at) — one dominant minute is the schedule, everything else is a person.
    • User messages per room (the sharper one). A scheduled run writes exactly one user-role message per room, at the cron minute. A human session is multi-turn and off-clock. count(user_msgs) == count(rooms) for a month means nobody typed anything that month.
  • Signal: theme the titles (strategy / analytics / content / release) to see what the product is used for — that shapes both the follow-up hook and product feedback. A new interactive user is engaged and worth a check-in; a recurring pain theme is a feature request plus a helpful-outreach opening.
  • Action: note the usage themes; follow up with high-intent interactive users; feed recurring friction back to product.

8. Outbound email — what we have already said to them

  • Read: the Resend sent log (paginates well past a 14-day window) plus email_send_log, with per-message delivery status.
  • Signal: a lead with zero human outreach ever (only product email) → an untouched first-contact opportunity. Bounces on a task email → a customer's own report silently failing (fix before anyone sells them anything). Duplicate sends of the same report → a runner bug hitting a real inbox — an owned-and-fixed apology is the warmest opener there is.
  • Action: never-contacted feeds the ranked list; bounces and duplicates become fix-then-tell hooks (see The send loop).

Synthesis — one ranked follow-up list

  1. Collapse to people. Key every signal by account/email and merge across the eight pulls — one person, all their signals, deduped.
  2. Rank by opportunity × urgency. Roughly: failed payment / churn-in-progress (save today) > new paid customer (welcome now) > agency lead stalled at In Conversation (a booked call is the cheapest advancement available) > warm valuation or enterprise lead (work this week) > expansion / testimonial > zombie-task cleanup. This ordering assumes a funnel that is converting. If no new paid customer has landed in 14 days, invert it — agency leads, warm valuation leads and enterprise leads come first. See the acquisition circuit-breaker above.
  3. Correlate across sources — the cross-checks are where the real finds are:
    • paid in Stripe but no Privy login → onboarding never landed; reach out now.
    • valuation run with no Attio entry → an unworked lead; create it (step 3).
    • negative credits and an enterprise domain → expansion priced as risk.
    • zombie task on a went-silent account → one message does double duty (re-engage + stop the token drain).
    • agency lead at In Conversation with no dated follow-up task → the exact shape of every thread this pipeline has lost. Create the task before moving on.
    • the same person on both funnels → decide which is real now and work that one; pitching a subscription to someone who is talking to us about a build reads as a sales sequence and costs the warmer thread.
  4. Act: write the follow-ups into Attio (create/advance entries, set owner + next step) and draft each outreach. Present the ranked list + drafts to the operator for explicit draft approval. Do not count drafts as contacts or send to fill a quota. Before drafting for any selected contact, build their four-source dossier (next section) and review the actual correspondence.

The contact dossier — before any outreach is drafted

The moment the operator selects a specific contact (from the ranked list or on their own), research that person into one concise four-row table and present it alongside the draft. It is the pre-flight that catches "already paying", "payment-blocked, not disinterested", "this IS the artist", and "we emailed them yesterday". Build it fresh even if a sweep pull already touched the account — the fleet aggregates hide the per-person timeline.

SourceWhat goes in the rowThe lookup that works
Privyfirst + last login eventsPOST https://auth.privy.io/api/v1/users/email/address with {"address": "<email>"} (basic auth PRIVY_APP_ID:PRIVY_PROJECT_SECRET + privy-app-id header) → created_at and each linked account's first_verified_at / latest_verified_at. One call — no need to re-run the pagination script for one person.
Stripecustomer / card / subscription statusFind the customer by metadata.accountId (email search misses — see Tooling gotchas), then GET /v1/customers/{id}/payment_methods and GET /v1/subscriptions?customer=…&status=all. A customer with 0 payment methods and 0 subscriptions = they reached checkout and never paid — blocked, not disinterested.
Supabasecredits_usage, sessions, chatsAll by account_id — except chats, which has no account_id column: join sessions.account_id → chats.session_id, then count chat_messages rows with role='user' per chat. Report typed chats separately from empty "New chat" rows — an untyped open is still a signal (they visited and bounced). Balance via SQL only, never the credits GET (see Credits mechanics).
Resendoutbound emails we've sent thememail_send_log by account_id (there are no to_email/subject columns; the subject is inside raw_body) → cross-ref each resend_id against the Resend API for subject + delivery status. Zero rows = we have never emailed this person from the product.

Read the table before writing a word of outreach: the first/last-login pair sets the "returning vs. new" frame, the Stripe row sets the ask (rescue vs. upgrade vs. retention), the chats row tells you what they actually tried to do in their own words (titles are admin-readable), and the Resend row is what "we" have already said to them.

Product activity is not the conversation. Read the actual latest human inbound and outbound emails, promises, meeting outcomes, and relationship owner before choosing the ask. CRM summaries and delivery receipts cannot establish what the customer wants now. If the thread is unavailable, flag that in the review packet; do not silently assume no reply, an unanswered question, or permission to restart an old proposal. Respect owner and no-contact restrictions when selecting contacts.

Then extend the dossier with four product-side tables — what the contact has actually done with Recoup, in their own timeline:

TableWhat goes in itThe lookup that works
Lifetime activityKey dated events from signup to now, weighted to recent: first login, each typed chat (title verbatim), tasks created, valuation runs, checkout attempts, emails delivered or missed, last event.Merge the four source rows chronologically. A 10–15 row timeline reads faster than any aggregate and is where the story ("built the report Thursday, hit the paywall Monday") becomes visible.
TasksEvery task: title, artist, cadence, model, enabled, last run + status, next runs, does it email — and does the sent email match the intention the customer typed when they created it?GET /api/tasks?account_id= only (model / upcoming / recent_runs / owner_email don't exist on scheduled_actions). For intention-vs-output: compare the email_send_log.raw_body headline against the chat/task title that spawned it. A run that says COMPLETED with no matching email_send_log row delivered nothing — the customer thinks the task ran; it silently didn't reach them.
ArtistsRoster: each artist + every connected social (platform, handle/URL, followers, scrape freshness).GET /api/artists?account_id= — socials embedded as account_socials; the social's updated_at is the scrape date. A one-platform roster (e.g. Spotify only) is itself an outreach hook: nothing else is connected.
Valuation / catalogRun count + dates, claimed vs unclaimed, the value on file, what's in the catalog (songs / albums / total streams), and the crown jewels (top songs by plays).playcount_snapshots by account (the column is account, not account_id) → song_measurements.snapshot = ps.id, join songs ON songs.isrc = song_measurements.song; value is the play count. The dollar value is not in the DB (never persisted) — read it off the Attio auto-note or the valuation email. Repeat runs of the same catalog in one sitting = they're trying to answer a question; find out which one.

The lead workspace — where every email lives

Each lead gets one folder in the operator's private workspace (workspace/sales/<lead>/, its own git repo, never a product submodule). Two things in it are load-bearing for the send loop; everything else (notes, PDFs, call transcripts, task prompts) sits beside them.

workspace/sales/<lead>/
  EMAILS.md            chronological index: every send + receipt, the read, the reply playbook
  emails/
    README.md          the four lines below, so a cold reader knows the rule
    sent/              verbatim final text of each send, one file, with its reply playbook
    received/          verbatim inbound emails, one file each
    threads/           verbatim multi-message Gmail threads (secrets redacted)
    drafts/            in-flight drafts only; emptied once the email is sent

Files are YYYY-MM-DD-slug.md. Rules that keep the record trustworthy:

  • EMAILS.md is the index, emails/ is the evidence. The index entry links to the file; the file holds the words. Never let a loose DRAFT-*.md sit at the folder root.
  • Keep approval evidence beside the versioned draft. Record the user's exact approval, its message reference or time, and the draft version it covers. Follow references/outreach-approval.md; never manufacture an approval record from a quota.
  • The sent file is the verified sent copy, not the draft. The send loop ends by retrieving and comparing what actually went out before writing emails/sent/; saving a draft, scheduling a task, or making an Attio note does not make it sent.
  • One file per send, after the send. The sent file carries the verbatim body, the approval evidence (exact quote, time, draft version), the provider id, the CRM ids and the reply playbook, so the draft and any HTML/text builds are deleted once it exists (decided 2026-09-21 after a ten-invite batch produced five files per lead). A lead with a single cold send needs no EMAILS.md yet; the index appears with the first reply. A draft revision that carried a decision gets one line in EMAILS.md saying what changed and why; the file itself is not kept.
  • Every inbound reply gets a received/ file the day it arrives, even a one-liner. The 2026-08-25 reorg of two live accounts found one sent email that existed only in Attio and two replies that existed only in Gmail; the reorg is what surfaced them.
  • Secrets never enter the files. API tokens that ride inside task prompts get redacted before the prompt or thread is copied in.
  • Attio mirrors, it does not replace. Each send/receipt still becomes an Attio note (the CRM's email sync misses bodies), and the note names the local file path.

The send loop — dossier to closed-out record

Run this sequence for every message: research → quality review → present exact draft → await user approval → send only the approved version → verify and log. Each message needs its own approval or inclusion in an explicitly approved batch of unchanged versions. Approval of an unrelated previous send does not carry over.

  1. Fix, then tell. The dossier almost always surfaces account defects — duplicate artists or tasks, unconnected socials, blocked balances, schedules that won't deliver. Repair only within the authorized scope via the API before drafting; never trigger an unapproved email as part of a fix. Open with the specific repair and its result, not a generic announcement that you reviewed the account. The specific observed detail is what makes outreach read personal; a fix already delivered is what makes it worth answering.

  2. Draft in the house voice. Open with the concrete observation, never meta-framing ("a quick note from a human" reads as automation). Offers are things we will do, not commands to the reader ("I'll send you the link next week, or now if you ask" beats "reply GO"). One plain close: "Let me know how I can help." Valuation numbers always carry their caveats. Every email carries at least one in-body link to a recoupable.dev subdomain — deep-linked to the exact thing you are asking for, never just the signature's home-page link. A reply that never becomes a site visit cannot convert, and with no in-body link every action routes back through a human by default, which is how a warm lead stalls. Match the link to the ask:

    The askLink
    Connect / fix streaming + social platformshttps://chat.recoupable.dev/setup/socials
    Re-run or check a catalog valuationhttps://chat.recoupable.dev/catalogs
    Set up or edit a scheduled reporthttps://chat.recoupable.dev/tasks
    Anything else / general return visithttps://chat.recoupable.dev

    Then run the draft through the unslop skill before presenting it. Every customer-facing draft gets an unslop pass — it strips the AI tells (em dashes, puffery, "not just X but Y", AI vocabulary) that make outreach read machine-written, and its no-em-dash rule enforces this skill's own standing rule for customer email. If unslop is not installed, get it from skills.sh/cursor/plugins/unslop (npx skills add — it installs at user level and applies to all writing). The pass edits the draft in place; meaning and commitments must survive it unchanged. Then apply the quality review in references/outreach-approval.md. Style cleanup, a factual detail, and a product link alone do not make a useful email.

  3. Present the complete draft and wait for approval. Show the recipient list, sender/reply-to, subject, exact body including footer, links and attachments, plus the reason to contact and any missing context or new commitments. Record explicit user approval of that version. Do not send while the user is reviewing.

  4. Send only the approved version; verify what went out. Immediately before invoking any send tool, compare its complete payload with the approved draft. Follow references/outreach-approval.md for changes, subsets, retries, and missing approval. The operator can instead send personally; retrieve their actual sent copy from the CRM/email provider or their paste and record any changed commitments. Write verified text to emails/sent/ before closing out.

  5. Close out every send the same way: complete the open task the send fulfilled; write a sent-log note stating what was promised and the reply playbook (what to do for each likely reply); do the keep-a-lead-warm trio — set owner, log the note, create a dated follow-up task (a Qualified enterprise lead once sat 21 days untouched because none of the three existed), writing that task as a runbook — exact ids, API calls, decision rules — executable by a cold reader; set the stage to what the email actually did (a Pro pitch → Pro Offer Sent; delivering the number → Report Delivered; a retention touch → no stage change).

  6. On reply, prepare the playbook same-day. A reply does not approve our next email. Research, do authorized work, and present the next draft for approval. Log the reply; if the sender is not who the record says, correct the identity immediately (rename, split the people, fix future greetings); pay for substantive product feedback on the spot (grant, verify the balance landed, say so in the reply); reproduce any reported bug in a live browser the same day and file it with evidence; and replace any task the reply mooted — a no-touch rule dies the moment they engage, but the draft-approval requirement remains.

Meeting prep — the qualified-customer call

The send loop ends when a lead books a call. What happens next is a different motion — not "who do we contact" but "what do we put in front of someone who has already agreed to spend thirty minutes with us." Run it for any booked call with a qualified customer. It produces exactly three artifacts: a pitch, a meeting plan, and the PDFs to deliver.

Full workflow in references/meeting-prep.md — mining what the customer already told us (read chat message bodies, never the auto-generated titles), finding the gap between what they promise publicly and what their published process delivers, the four document shapes, and the house format. Every PDF uses the one-page house style: references/pdf-house-style.md (template templates/pdf-house-style/page.html, build and checks scripts/build_pdf.py).

Two things that are easy to get wrong and expensive to undo:

  • Never bring an account-health / account-status PDF to a prospect. That format is internal tooling; run it on a quiet account and you hand someone an audit of their colleague's inactivity.
  • Data documents survive the call; argued documents often need re-aiming. Invest in the measured ones, and argue only from what the customer already did, never from an assumed goal.

Guardrails

  • Keep preparation separate from delivery. Enriching/advancing Attio and disabling a confirmed-dead task follow the existing limits; still confirm before moving a lead to Lost or disabling a task. Sending an email additionally requires explicit approval of that exact draft, through any tool or sender.
  • Never mutate customer data with raw SQL — go through the API. An UPDATE on scheduled_actions writes the row but skips everything the endpoint does around it: updateTask re-syncs the Trigger.dev schedule, and deleteArtist runs a last-owner check plus a fail-closed song-dependency guard before hard-deleting. A direct write leaves the row and the scheduler disagreeing, and the drift is invisible in the table you just edited. Use PATCH/DELETE with an account_id override. This applies to every customer-facing table, not just tasks.
  • Draft first; no unapproved sends. Follow references/outreach-approval.md for every email. No quota, deadline, prior send, CRM stage, subagent recommendation, or automatic goal continuation substitutes for the user's approval. Do not send an apology or correction for an accidental send without review either.
  • PII stays in the CRM. Names, emails, account IDs live in Attio/Supabase — never paste them into shared docs or external services.
  • Exclude test rows everywhere (sweetmantech*, sidney@, @example.com, [TEST], preview-auth-probe).
  • Trust Stripe over the DB for paying status — the Supabase subscriptions mirror can be empty/stale.

Lessons from the first live run (2026-07-23)

Four leads worked end to end: an inbound valuation lead, a distributor/broker, a major-label exec, and a dormant power user. What actually mattered.

Qualification

  • Credit burn is not engagement. Two accounts looked "active today" and were pure autopilot — the usage was a scheduled task firing. One had been dark 4 months while its daily task quietly burned the balance negative. Check the last interactive chat date, always (step 7 has the two tests that survive a NULL topic).
  • And a quiet account is not a disengaged one. A subscriber of 19 months read as dormant — 1 active day, 0 chats — because he had 456 credits against a 9,999 entitlement the whole time and nobody had ever read his balance to trigger the refill. He had paid $405.46 for roughly 2% of one month's allowance. Fleet-wide, 1,499 of 1,646 accounts were carrying an unapplied refill and 133 were blocked at or below zero. Before writing anyone off, check whether we ever funded them.
  • Qualify the catalog before spending human time. A lead who ran a valuation and asked "what can I sell for" held a 10-track public-domain classical catalog worth ~$700 — no publishing to sell, since Bach and Chopin are public domain. Capture works fine; qualification is the gap. Check repertoire type and streams before drafting anything.

Valuation you can put in front of a buyer

  • Say "asset value, not a bid." A catalog buyer asked "is this for the buy?" The number models a 10-16x multiple on sustainable annual net label share. It is not an offer price and must never anchor an LOI.
  • It mis-prices older catalogs in BOTH directions. Against four independent market comps (independent catalog buyers and distributors) it was accurate on a 2-year catalog, undervalued an 8-year/90-release catalog ~2x, and overvalued 8-10 year catalogs. Cause: annual run rate is estimated as lifetime streams ÷ catalog age, which averages an old catalog flat — understating one weighted to recent output, overstating a decaying one. The market prices trailing-12-month performance. Older than ~3 years: get statements before quoting.
  • It is Spotify only. One artist's self-reported ~700k/month Amazon track was worth roughly 10-20x his entire Spotify catalog per year at Amazon's public ~$0.00402/stream rate. Ask about other DSPs before calling a catalog small.
  • Pull real release dates. Catalog age is floored at 1 year and falls back to 5 when unknown, so identical streams can value ~5x apart.

Delivering

  • The sequence that converted: verify the roster → measure → render the PDF → deliver by email and the channel they replied on → advise honestly on the number → ask for statements. Being the one who says "you may be overpaying" is what turned a valuation into a partnership conversation.
  • Keep outreach short and lead with the concrete thing you did for them. Attach a report only when their job makes it relevant — a label exec has no use for a catalog valuation of an artist their employer already owns.

Reference files

  • references/outreach-approval.md — mandatory quality and exact-draft approval gate for every email.
  • fixtures/outreach-approval-scenarios.md — synthetic cases for checking send decisions without real email.
  • references/cold-outreach.md — hunting when the funnel is cold: Exa Agent API → email verification → research → all-services positioning → one real deliverable → one-link email.
  • references/meeting-prep.md — prep for a booked call: the pitch, the meeting plan, the PDFs to deliver.
  • references/credits-mechanics.md — how checkAndResetCredits behaves, before you quote a balance.
  • references/tooling-gotchas.md — Privy / Stripe / Supabase / Attio quirks and dead columns.
  • references/podcast-guest-pipeline.md — the podcast-invite top of funnel: loop + Attio mapping with the consent gate, the invite template and its pre-review checklist, threaded nudges, running the day (status first, research budget, verification ladder), day-7 template, what to measure, premium episode copy, cover spec, guest photo sourcing, Spotify for Creators edits.
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

NOASSERTION

源路径

skills/recoup-internal-sales

默认分支

main

最新提交

943ef55

Tree SHA

85628a9