skills/form-signup-recovery/SKILL.md
Recover lost registrations after a signup/lead form has been silently broken — diagnose via the Resend email log, classify real users vs tests, re-submit missing contacts to HubSpot via the public Forms API (no auth token needed), and produce a personal follow-up list. Use when the user says "backfill dropped signups", "the registration form was broken", "we lost signups", "check what registrations came through", "recover lost leads", or any variation where a form-to-CRM pipeline has been failing and needs reconciliation. Also use proactively after fixing any registration form bug to check whether real users were lost before the fix.
npx skillsauth add razbakov/skills form-signup-recoveryInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
When a registration form silently breaks, the CRM is the wrong place to look first — if the bug is in the CRM submission path itself, the CRM will be empty precisely because the form was broken. The highest-fidelity audit log is almost always the admin notification email sent by the server handler after a successful submission. Resend is the most common outbound provider for these — its /emails API returns the full list with HTML bodies, which contain the structured name/email/phone/referrer fields.
This skill reconstructs the real signup list from Resend, separates real users from tests, re-submits them to HubSpot via the public Forms API (which requires only portalId + formGuid — no auth token), and produces a follow-up action list.
.env as RESEND_API_KEY or NUXT_RESEND_API_KEY. Read the file to find it..env, usually NUXT_HUBSPOT_PORTAL_ID and NUXT_HUBSPOT_FORM_GUID. These are public values (the frontend ships them to the browser), so no auth is needed to re-submit.Do NOT rely on a HubSpot access token for this flow. Access tokens expire silently; the Forms API submission endpoint is unauthenticated by design.
Read the project's .env file to find Resend key, HubSpot portal ID, and form GUID. If values are named differently, grep for RESEND / HUBSPOT to find them. Never paste secrets into the response — use them only in curl commands.
List all outbound emails (this endpoint returns up to 100 per page, usually enough for recent recovery):
curl -s "https://api.resend.com/emails?limit=100" \
-H "Authorization: Bearer $RESEND_API_KEY"
Filter for admin notifications — they typically have subjects like New signup:, New lead:, New submission:. Extract the IDs.
The list endpoint only returns metadata. To get the HTML body (which contains the structured fields), fetch each email by ID:
for id in $IDS; do
curl -s "https://api.resend.com/emails/$id" \
-H "Authorization: Bearer $RESEND_API_KEY"
printf '\n'
done > /tmp/signups-raw.jsonl
The admin email HTML usually contains a table like:
<tr><td>Name</td><td>John Doe</td></tr>
<tr><td>Email</td><td><a href="mailto:...">[email protected]</a></td></tr>
<tr><td>Phone</td><td><a href="tel:...">+491234567890</a></td></tr>
<tr><td>Referrer</td><td>https://...</td></tr>
Parse with a short Python script. A robust regex that handles both plain text and <a>-wrapped values:
def grab(label, html):
m = re.search(r'>'+label+r'</td><td[^>]*>(?:<a[^>]*>)?([^<]+?)(?:</a>|</td>)', html)
return m.group(1).strip() if m else ''
Extract: date (from created_at), name, email, phone, source, medium, campaign, referrer.
Test signups typically have one or more of:
@razbakov.com, @<project>.com, etc. — read the project CLAUDE.md if unsure which domains are "internal")+4900000000, all same digits)Present BOTH lists to the user before proceeding: "I found N real signups and M tests. Here they are — confirm before I submit to the CRM?" The user may recognize someone you flagged as a test, or vice versa.
Use the public Forms API endpoint — same path the frontend uses:
PORTAL=<portal_id>
FORM=<form_guid>
URL="https://api.hsforms.com/submissions/v3/integration/submit/$PORTAL/$FORM"
curl -s -X POST "$URL" \
-H "Content-Type: application/json" \
-d '{"fields":[
{"name":"email","value":"..."},
{"name":"firstname","value":"..."},
{"name":"phone","value":"..."}
]}'
Success response: {"inlineMessage":""} (empty string). HubSpot dedupes by email, so re-submitting an existing contact is safe (it updates, doesn't duplicate).
Field names are handler-specific. The email/firstname/phone above are what most handlers pass, but the form schema in HubSpot determines which field names are valid. If re-submission fails with a validation error, read the original server handler source to see what field names it uses, or check the HubSpot form definition directly.
Do NOT re-submit through the project's own /api/subscribe handler (or equivalent) — that would trigger N admin-notification emails to the user's inbox and N welcome emails to the recovered contacts, which is usually bad UX (a delayed automatic welcome email looks worse than no email).
The recovered contacts need a personal message, not an automated welcome — especially if real time has passed. Draft a template that:
Save the list + template to a backlog item or session note in the project, so the user can work through them 1-by-1. Flag high-signal referrers (organic hits from specific sites, social traffic sources) for follow-up questions — these people are channel research gold.
Create a session note in the project's ops/sessions directory (YYYY-MM-DD-signup-backfill.md) with:
Link from the related engineering ticket (the "fix registration form" task) so the backfill is discoverable from the bug that caused it.
.env works — test it first with a GET /crm/v3/objects/contacts?limit=1. If expired, fall back to Resend + re-submission rather than rotating the token mid-task.await hubspotCall(); await resendCall(); — if HubSpot throws, Resend never fires, so admin emails are a reliable proxy for HubSpot success. But if the code has try/catch around the HubSpot call, Resend could fire even when HubSpot failed. Read the handler source before assuming.firstname — e.g. "Charles David Heyburn" → firstname=Charles. Don't try to parse last names; HubSpot's Forms API doesn't require lastname and guessing will be wrong.ops/sessions/YYYY-MM-DD-signup-backfill.md).tools
--- name: handoff description: Get an agent past a browser/UI wall it can't (or must not) cross on its own — a login-gated dashboard, a CAPTCHA, a 2FA prompt, an API that keeps rejecting the write, or an irreversible click that policy says a human must make. This skill is an ESCALATION LADDER, not a first move: it tells you to try the automated browser surfaces FIRST (Chrome-in-Claude, computer-use, an autonomous browser sub-agent) and only fall back to the Handoff app — a wrapper browser that h
documentation
Summarize one or more YouTube videos from their links. Use this whenever the user pastes a youtube.com or youtu.be URL (or several) and wants to know what it's about — phrasings like "summarize to telegram", "tldr these videos", "what do these say", "summary of this talk", or just dropping links with no instruction at all. Fetches each video's real transcript via yt-dlp (not the page text, which never contains the transcript), cleans the captions, and writes a per-video summary. Default delivery is Telegram; honor any other surface the user names ("to my notes", "just here in chat", "email it"). Trigger even when the user only pastes bare links — bare YouTube links almost always mean "tell me what's in these".
data-ai
Daily Digest — Chief-of-Staff role consolidates the six top-managers into one Telegram message to the Commander, instead of six. Implements the protocol from agent-proactivity.md.
development
Seed a new or empty Instagram account with a 9-post grid (3×3) so the profile looks established the moment a new visitor lands. Designed for festivals, new businesses, product launches, conferences, communities — any time an empty IG profile would hurt conversion from external traffic (QR scans, flyer drops, cross-promo). Generates assets via /image-from-gemini (per content-publishing rules — never HTML), writes captions with hashtag sets, and outputs a posting order + cadence plan. Trigger generously: phrases like '9 posts for instagram', 'fill my IG', 'starter grid', 'launch grid', 'instagram seed', '9-post grid', 'IG account not to look empty', 'first instagram posts', 'feed bootstrap', '3x3 grid', 'instagram launch content'. Even if the user mentions only one piece (just the images, just the captions, just the order), use this skill — the grid only works as an integrated bundle.