How I implemented an end-to-end multi-agent sales and onboarding workflow
How I implemented a three-agent n8n workflow for sales follow-up, proposal preparation, contract management, and client onboarding with human review.
Jason K Hanani
7 min read


Closing
The difficult part of building this system wasn't any individual integration. Connecting a call recorder to n8n, or n8n to a document signing tool, is documented and not complicated. The harder work was designing the flow between the pieces: what triggers what, where state lives, when a human needs to be in the loop, and how to keep each component small enough to debug when something goes wrong.
That's the part that takes the most thought, and it's what rarely shows up in demos.
If you're working through a system like this for your own business or team, or if you're looking for someone who works at the intersection of operations and systems thinking, I'd be glad to connect.
Connecting an LLM to an application is usually the easy part. The harder questions are operational: what triggers the workflow, where state lives, how agents know what has already happened, when a human must approve an output, and what happens when the input is incomplete or ambiguous.
These are not model problems. They're system design problems, and they're why most AI automation demos look clean in a slide deck and fall apart when something real happens.
This article explains a three-agent workflow I implemented and deployed in n8n to support a services sales pipeline, from lead follow-up and call booking through proposal preparation, contract preparation, and client onboarding. I'll walk through how each agent works, what connects them, and the decisions that turned out to matter, including the ones I'd handle differently.
The problem
A services sales pipeline follows the same sequence every time: find a lead, follow up, book a call, send a proposal, close the deal, get the contract signed, onboard the client. Predictable and repetitive, and at every step there's manual work that doesn't require judgment; it just requires someone to remember to do it.
The goal wasn't to remove humans from the process. Several steps need human eyes on them, like reviewing a proposal before it goes to a client or approving a contract before it gets sent. The goal was to remove the execution overhead around those decisions: the manual emails, the status tracking, the follow-up reminders, the folder creation, the copy-pasting between tools.
System overview
The Multi-Agent Sales and Onboarding Workflow runs across three agents, each with its own trigger and scope:
Sales Agent: outreach, follow-up sequences, and call booking tracking
Proposal Agent: proposal generation from sales call recordings
Onboarding Agent: contract creation, signing detection, and client onboarding
All three agents share a single Google Sheet as their source of truth. Every lead has a CRM row, and every field update (when a sequence started, how many follow-ups went out, whether a call was scheduled, whether a proposal was sent, whether a contract was signed) gets written back to that sheet by whichever agent last touched it. The agents don't communicate with each other directly. They communicate through state.
This has a practical benefit that's easy to underestimate. Because state lives in the spreadsheet, a human can read the full pipeline at a glance, edit a field manually when something goes wrong, and trace what each agent did and when. That visibility matters more than the automation itself when something breaks.
The Sales Agent
The Sales Agent runs on a 24-hour schedule. Every day it reads a Lead List sheet, moves new contacts into the CRM, and evaluates each row to decide what to do.
The logic runs the same check on every lead. Once a lead is added to the CRM and the sequence is initiated, the agent checks whether a call has been scheduled. If yes, it stops sending emails. If no, it checks how many follow-ups have gone out and how many days since the last one, then routes to the first, second, or third follow-up, or does nothing and waits. Three follow-ups is the cap. No lead gets emailed indefinitely.
Call scheduling runs through a separate Calendly trigger. When someone books, the webhook fires and "Call Scheduled?" updates in the CRM, which is how the outreach sequence knows to stop. There's also a no-show flow: a separate scheduled check looks for leads manually marked as no-shows in the CRM and sends a rescheduling email, then updates the record to prevent duplicate follow-ups.
The Sales Agent is effectively stateless on its own. All the logic that determines what it should do is encoded in the CRM. If "Follow Ups" is 2 and "Sequence Last Date" was yesterday, it sends the third follow-up. If "Call Scheduled?" is true, it skips the lead. The agent reads fields and routes. The state lives in the sheet.
The Proposal Agent
The Proposal Agent runs on a different trigger: a webhook from the call recording tool. In this implementation, that's Fathom, though any recorder that pushes a transcript via webhook would work the same way. When a call ends and the tool finishes processing, it sends the transcript and metadata to n8n automatically.
The first thing the agent does is look up the attendee's email in the CRM and check the call type. The routing condition: is "Sales Transcript ID" empty? If yes, this is the first sales call, so generate a proposal. If no, a proposal has already been sent, so just update "Latest/Closed Transcript ID" and stop.
For a sales call, the workflow passes the full formatted transcript and the company name to Claude claude-sonnet-4.5 via OpenRouter, using n8n's Information Extractor node. The extraction schema has 19 required attributes: proposal title, description, client bottlenecks, project aim, project goal, three solution headings and descriptions, four project phases, and the two payment installments with amounts and deadlines.
The schema matters because the output feeds directly into a Google Slides template with named placeholders like {{proposalTitle}}, {{bottlenecks}}, and {{firstsolutionheading}}. A freeform LLM response has nothing to slot into the template. The schema forces the output into exactly the shape the downstream step expects.
Once the Slides deck is built, the workflow emails the generated proposal to a human reviewer and updates the CRM with the proposal status. The proposal is not sent directly to the client by the automation.
This is the right gate to have. A proposal commits you to a price, a scope, and a timeline. Automating that entirely would be a mistake. The agent's job is to produce a structured first draft that reduces the human review from a blank page to a focused quality check.
The Onboarding Agent
The Onboarding Agent handles everything after a deal is marked as closed in the CRM. It runs across three separate flows.
Contract creation. When a CRM row's "Closed?" field updates to true, a Google Sheets trigger fires. The agent generates a PandaDoc contract pre-populated with the lead's details from the CRM. A review email with the PandaDoc link goes to a human. The human reviews, adjusts if needed, and sends it. When the contract is sent, the agent writes the PandaDoc document ID and "Contract Sent?" = true back to the CRM.
Signing detection. PandaDoc doesn't push a webhook when a document is signed in this setup, so the agent polls. An hourly schedule trigger reads the CRM, finds all rows where "Contract Sent?" is true and "Contract Signed?" is false, and calls the PandaDoc API for each one. If the document status comes back as document.completed, the agent updates the CRM and sends a welcome email to the client with a Calendly link to book an onboarding call.
Onboarding setup. When the client books their onboarding call, a Calendly webhook fires. The agent checks the event name (it only activates for events called "Onboarding Call"), looks up the company name from the CRM by email, creates a named client folder in Google Drive, shares the folder with the client, and sends a notification with the folder link and call time.
Three separate triggers, three separate flows. They belong to the same agent because they serve the same stage of the pipeline, but they run independently. That's the pattern throughout: each flow is small, testable, and responsible for one thing.
The design decisions that actually mattered
Using Google Sheets as the state layer wasn't laziness. A spreadsheet is immediately readable, editable by hand when something goes wrong, and provides a clear record of the current state and the fields each agent updates. A database would scale further, but for a system where human oversight matters and volume is manageable, a spreadsheet is often the right call.
The human gates at proposal and contract are also deliberate. The system could, in theory, send a proposal the moment a call ends. It doesn't. A price and scope commitment shouldn't go out without someone reviewing it first, and the same logic applies to the contract. Automation handles the repetitive execution; the decisions with consequences that are hard to undo need a person in the loop.
The hourly polling approach for detecting a signed contract is less elegant than a webhook, but it's what the setup required. The tradeoff: at most a one-hour delay between signing and the welcome email going out, with no extra infrastructure to maintain. For this test workflow, the delay was acceptable, although a webhook would be preferable for a more time-sensitive production implementation.
Defining the 19-field extraction schema before building anything else forced a useful constraint. Every downstream step had to work with structured data, which meant the Google Slides template, the CRM update, and the review email all worked consistently from day one. The alternative would have been asking the LLM for freeform output and parsing it later, which adds fragility at every step.
What I'd do differently
The LLM extraction in the Proposal Agent degrades when calls are unfocused. A conversation that jumps around, or one that never covers pricing explicitly, produces weak extractions, especially for the payment fields. A validation step after extraction, checking whether the required fields are populated and plausible before the Slides deck is built, would catch errors before they reach the human reviewer.
I'd also add clearer error state to the CRM. If an agent fails mid-run, a row can end up in an ambiguous state with some fields updated and some not. A dedicated "Status" or "Error" column that each agent writes at the start and end of its run would make partial failures easier to spot and recover from.








