Closed-Won to Kickoff in One Workflow
Short answer: trigger on the CRM stage change to won, key everything on the deal id so a retried webhook can't create a second client, generate a handoff brief from the deal notes with one LLM call, create the client record, folder and task checklist, then put the welcome email and kickoff invite in a review queue instead of sending them blind. Track each step in a onboarding_runs table so a failure halfway resumes instead of starting over.
Why this keeps coming up
Automation communities keep asking the same two questions: what should go into the handoff when a deal closes, and should a small business build dashboards or automations first?
The recurring complaints: sales knows things delivery never hears, the welcome email goes out twice when the CRM retries, someone forgets to create the shared folder, and a half-failed run leaves a client in three systems but missing from the fourth.
What goes into the handoff
| Step | Output | Idempotency key |
|---|---|---|
| 1. Intake | Deal id, company, contacts, value, notes | deal_id |
| 2. Handoff brief | Goals, scope, promises sales made, risks, open questions | deal_id:brief |
| 3. Client record | Row in your client table or project tool | deal_id:client |
| 4. Shared folder | Drive/Notion page from a template | deal_id:folder |
| 5. Task checklist | Kickoff, access requests, invoice, first deliverable | deal_id:tasks |
| 6. Welcome email + kickoff invite | Draft for review, not auto-sent | deal_id:welcome |
| 7. Internal ping | One Slack/Teams message with the brief | deal_id:ping |
Schema
CREATE TABLE onboarding_runs (
deal_id TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'started', -- started | done | failed
steps_done TEXT[] NOT NULL DEFAULT '{}',
brief JSONB,
started_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
Claim the deal once:
INSERT INTO onboarding_runs (deal_id) VALUES ($1)
ON CONFLICT (deal_id) DO NOTHING
RETURNING deal_id;
No row back means another run already owns this deal, so the workflow only resumes the steps missing from steps_done.
Mark a step done right after it succeeds:
UPDATE onboarding_runs
SET steps_done = array_append(steps_done, $2), updated_at = NOW()
WHERE deal_id = $1 AND NOT ($2 = ANY(steps_done));
The handoff brief prompt (one call, structured output)
Ask the model for strict JSON with these fields: goals, in_scope, out_of_scope, promises_made, deadlines, stakeholders, risks, open_questions. Tell it to write "unknown" instead of guessing. The promises_made field is the one delivery teams thank you for.
Workflow outline (n8n)
- Webhook or CRM trigger filtered to stage = won.
- Postgres claims
deal_id; an IF node routes new deals to the full path and existing ones to resume. - LLM node writes the brief, stored in
brief. - Each creation step runs behind an IF that checks
steps_done, then appends its step name. - Welcome email and kickoff invite are saved as drafts for a human to approve.
- A daily schedule trigger lists runs stuck in
startedfor more than 2 hours.
Dashboards first or automations first?
Automate the handoff first. It is the step where information is lost, and the onboarding_runs table becomes your first dashboard for free: deals won, onboarding started, stuck runs.
Where to run it
- Self-host n8n plus Postgres on Railway or start on n8n Cloud.
- In a visual builder, the same keys work in Make using a Data Store keyed by
deal_id.
FAQ
What if the CRM sends "won" twice after someone reopens the deal? The deal_id claim blocks a second onboarding. If you want re-onboarding, key on deal_id plus the close date.
Should the welcome email ever auto-send? Only after you trust the template; even then, keep the deal_id:welcome step check in front of the send so retries can't double-send.
Where does the brief live? Store it in the run row and paste it into the client folder, so delivery reads it before kickoff.
If you'd rather build this in a visual builder, you can start free on Make.
Disclosure: Some links are affiliate links — I may earn a commission at no extra cost to you.
Top comments (0)