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).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.
testing
Translate one English blog post into multiple target languages via parallel sub-agents, preserving frontmatter conventions, hero image, and brand voice. Use when the user shares a published English post URL or markdown path and says 'translate it', 'add other languages', 'publish in DE/ES/RU/UK', 'translate to 5 languages', or asks for localized versions of a specific post.
development
Build a complete press kit for an event, product launch, or campaign — in multiple languages — and publish it as a shareable Google Drive folder ready to send to journalists, partners, or a delegate. Produces press releases (typically DE/EN/ES, or configurable), uploads press photos and flyers, creates an Overview document for at-a-glance briefing, and creates a Handover document with pending tasks, contacts, risks, and decisions so press distribution can be delegated. Use when the user says 'I need a press release', 'create a press kit', 'press release in X languages', 'set up a Drive folder for press', 'handover doc for someone else to run press', or has an upcoming announcement that needs to be sent to media. Trigger generously: even partial requests (just a press release, just a flyer folder) typically evolve into the full kit.
development
Track ticket sales for a live event (concert, festival, conference, workshop) with daily snapshots, generate a burndown chart comparing actual sales to ideal-linear targets and tier-cumulative milestones, and report whether the event is on pace. Use when the user asks how sales are going, wants to know if their event will sell out, asks for a daily sales report, wants to set up sales tracking for an upcoming event, or asks about ticket pace / velocity / projection. Trigger generously: phrases like 'how is concert sales going', 'burndown for my event', 'are we going to sell out', 'sales velocity', 'daily ticket chart', 'how many tickets do we need to sell', or any case where the user has a ticketed event with a fixed sales window and wants visibility on pacing.