Back to Demo & Discovery Scheduling

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Demo and Discovery Scheduling

[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team everything needed to construct, wire, and validate the Demo and Discovery Scheduling automation. It covers the current-state step map with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. The process owner's role is limited to approving the pre-call brief template and confirming routing thresholds before build begins. FullSpec handles all orchestration, integration, and testing end to end.

01Process snapshot

Step
Name
Description
1
Identify Interested Prospect
Rep monitors HubSpot inbound form submissions, email replies, and CRM activity notifications to spot prospects who have expressed demo interest. Time cost: 10 min/cycle.
2
Qualify the Lead
Rep reviews company size, role, and lead source in HubSpot to decide between a discovery call or full demo route. Time cost: 8 min/cycle.
3
Send Availability Options
Rep manually composes an email with two or three time slots or pastes a Calendly link and sends it to the prospect. Time cost: 7 min/cycle. BOTTLENECK.
4
Chase Non-Responses
If no reply within 48 hours the rep sends a manual follow-up nudge, frequently forgotten or deprioritised under competing workload. Time cost: 6 min/cycle. BOTTLENECK.
5
Confirm the Meeting
Once a time is agreed, the rep creates a Google Calendar invite and sends a confirmation to the prospect. Time cost: 5 min/cycle.
6
Create Zoom Meeting Link
Rep manually creates a Zoom meeting, copies the link, and appends it to the invite and confirmation email. Time cost: 4 min/cycle.
7
Update CRM Deal Stage
Rep updates the HubSpot deal record to the demo-booked stage and logs the meeting date. Time cost: 5 min/cycle.
8
Prepare and Send Pre-Call Brief
Rep manually writes or adapts a prep email covering the agenda, what to prepare, and a short company overview, then sends it. Time cost: 12 min/cycle. BOTTLENECK.
9
Send Internal Slack Notification
Rep posts a message in the sales Slack channel to give the team visibility of the upcoming demo. Time cost: 3 min/cycle.
10
Send Pre-Call Reminder to Prospect
Rep manually sends a one-hour-before reminder with the Zoom link, frequently forgotten when back-to-back meetings stack up. Time cost: 4 min/cycle.
Time cost summary: Total manual time per cycle is 64 minutes. At ~40 demo requests per month (approximately 10 per week) this equals roughly 640 minutes (just over 10 hours) per month and 6 hours per week of rep time. Steps 1 and 2 (qualification and routing) are replaced by the Lead Qualification Agent. Steps 3, 4, 5, and 6 (booking link dispatch, follow-up nudge, calendar invite, Zoom link) are replaced by the Scheduling and Confirmation Agent. Steps 7, 8, 9, and 10 (CRM update, pre-call brief, Slack notification, prospect reminder) are replaced by the Pre-Call Comms Agent. The one remaining manual touchpoint is the rep review of ambiguous low-confidence leads flagged by the decision branch.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Lead Qualification Agent

Reads the incoming HubSpot contact record the moment a demo-interest signal fires, scores the lead against role, company size, and traffic source, and outputs either a discovery-call Calendly URL or a full-demo Calendly URL. When the computed confidence score falls below the configured threshold the agent writes a REVIEW_REQUIRED flag to the contact record and halts, routing the lead to a rep for manual assessment before any outbound email is sent.

Trigger
New HubSpot contact or deal enters the demo-interest workflow, either via lifecycle stage change to 'Demo Requested' or via a form submission mapped to that stage.
Tools
HubSpot (Contacts API, Deals API)
Replaces steps
Steps 1 and 2 (Identify Interested Prospect, Qualify the Lead)
Estimated build
5 hours, Moderate complexity
// Input
contact.id            : string   // HubSpot contact record ID
contact.jobtitle      : string   // e.g. 'Head of Sales', 'Founder'
contact.company       : string   // Company name
contact.num_employees : integer  // Company size band
contact.hs_lead_source: string   // e.g. 'Organic Search', 'Outbound Sequence'
deal.id               : string   // Associated deal record ID (if exists)

// Scoring logic (internal)
score += 30 if jobtitle matches [VP|Director|Head|Founder|Owner]
score += 25 if num_employees >= 10
score += 20 if hs_lead_source in ['Referral','Outbound Sequence']
score += 15 if deal.amount > 0
// Threshold: score >= 50 -> auto-route; score < 50 -> REVIEW_REQUIRED

// Output (auto-route path)
routing.decision      : 'DISCOVERY' | 'FULL_DEMO'
routing.calendly_url  : string   // Segment-specific Calendly booking URL
routing.contact_id    : string   // Passed to Scheduling and Confirmation Agent
routing.deal_id       : string   // Passed downstream

// Output (manual review path)
routing.decision      : 'REVIEW_REQUIRED'
hubspot.contact.review_flag : true  // Property written back to HubSpot
  • HubSpot tier must be at least Sales Hub Starter to access the Workflows API and custom contact properties. Confirm the active subscription tier before build begins.
  • The two Calendly booking URLs (discovery-call and full-demo) must be created and tested in Calendly before they are hardcoded into the routing output. Name them consistently: 'discovery-call' and 'full-demo' in the Calendly event slug.
  • The REVIEW_REQUIRED HubSpot contact property must be created as a boolean field in HubSpot before the agent can write to it. Property internal name: hs_review_required.
  • Scoring weights are defaults. The sales lead must confirm or adjust role, company-size, and source weights before the agent goes live.
  • Dedupe behaviour: if a contact already has an active open deal in stage 'Demo Booked' the agent must detect this and exit without creating a duplicate workflow run. Check deal stage before scoring.
  • Fallback: if HubSpot returns a 429 rate-limit error, the agent must wait 10 seconds and retry up to three times before raising a logged error and halting.
Scheduling and Confirmation Agent

Fires immediately after the Lead Qualification Agent emits a valid routing decision. Sends the appropriate Calendly booking link to the prospect via a personalised Gmail message. If no Calendly booking event is detected within 48 hours, a single follow-up nudge is sent. When Calendly posts a booking-confirmed webhook, the agent creates a Zoom meeting under the assigned rep's account, generates the unique join link, and adds it to a Google Calendar invite shared with both the prospect and the rep.

Trigger
Lead Qualification Agent output: routing.decision is 'DISCOVERY' or 'FULL_DEMO', plus a 48-hour timer branch for the non-response nudge, plus the Calendly booking_confirmed webhook.
Tools
Gmail (Gmail API, send-as scope), Calendly (webhook subscription), Zoom (Meetings API, user-level OAuth per rep), Google Calendar (Calendar API, event insert)
Replaces steps
Steps 3, 4, 5, and 6 (Send Availability Options, Chase Non-Responses, Confirm the Meeting, Create Zoom Meeting Link)
Estimated build
7 hours, Moderate complexity
// Input (from Lead Qualification Agent)
routing.decision      : 'DISCOVERY' | 'FULL_DEMO'
routing.calendly_url  : string
routing.contact_id    : string
routing.deal_id       : string
contact.email         : string
contact.firstname     : string
rep.email             : string   // Assigned rep Gmail address
rep.zoom_user_id      : string   // Zoom user ID for meeting creation

// Output (booking link sent)
gmail.sent_message_id       : string   // Gmail message ID of booking email
workflow.nudge_timer_started: true      // 48-hour countdown initiated

// Input (Calendly webhook payload on booking confirmed)
event.calendly_event_uri    : string
event.start_time            : ISO8601 datetime
event.end_time              : ISO8601 datetime
invitee.name                : string
invitee.email               : string

// Output (after booking confirmed)
zoom.meeting_id             : string
zoom.join_url               : string
zoom.start_url              : string
gcal.event_id               : string   // Google Calendar event ID
gcal.invite_link            : string
booking.confirmed_at        : ISO8601 datetime
booking.meeting_start       : ISO8601 datetime  // Passed to Pre-Call Comms Agent
  • Gmail API scope required: https://www.googleapis.com/auth/gmail.send. Do not request broader scopes. Authenticate using the rep's Google Workspace service account or individual OAuth, confirmed per rep before build.
  • Calendly webhook must be registered at the organisation level with the event type 'invitee.created' subscribed. Confirm Calendly plan tier: webhook subscriptions require Calendly Standard or above.
  • Zoom Meetings API requires user-level OAuth if meetings must appear under the rep's Zoom account (not the admin account). Each rep must individually authorise via Zoom OAuth during setup. If multi-rep routing is not in scope for this build, a single admin OAuth token may be used as a short-term substitute, but flag this clearly in documentation.
  • The 48-hour nudge timer must be implemented as a delayed branch in the orchestration layer, not a polling loop. If Calendly confirms a booking before the 48 hours expire, the nudge branch must be cancelled immediately to avoid sending a follow-up after the prospect has already booked.
  • Google Calendar invite must include the Zoom join URL in both the description field and the location field for maximum compatibility with calendar clients.
  • Fallback: if Zoom API returns an error during meeting creation, log the failure to the error table, alert via Slack to the build monitoring channel, and send the calendar invite without a Zoom link, inserting a placeholder 'Zoom link to follow' in the description.
Pre-Call Comms Agent

Fires when the Scheduling and Confirmation Agent passes a confirmed booking payload. Updates the HubSpot deal record to the demo-booked stage and logs the meeting date. At 24 hours before the meeting start time, dispatches a personalised pre-call brief to the prospect via Gmail, pulling the prospect's name, company, and meeting topic from HubSpot properties. Simultaneously posts a formatted notification to the designated sales Slack channel. At 60 minutes before the meeting, sends a short reminder email to the prospect containing the Zoom join link.

Trigger
Calendly booking_confirmed event received and processed by the Scheduling and Confirmation Agent, passing confirmed booking payload including meeting start time.
Tools
HubSpot (Deals API, deal stage update, contact properties), Gmail (Gmail API, send-as scope), Slack (Incoming Webhooks or Web API chat.postMessage)
Replaces steps
Steps 7, 8, 9, and 10 (Update CRM Deal Stage, Prepare and Send Pre-Call Brief, Send Internal Slack Notification, Send Pre-Call Reminder to Prospect)
Estimated build
6 hours, Moderate complexity
// Input (from Scheduling and Confirmation Agent)
booking.confirmed_at        : ISO8601 datetime
booking.meeting_start       : ISO8601 datetime
zoom.join_url               : string
gcal.event_id               : string
routing.deal_id             : string
routing.contact_id          : string
contact.firstname           : string
contact.email               : string
contact.company             : string
rep.email                   : string
rep.slack_user_id           : string   // For @mention in Slack message

// On CRM update (immediate, step 1 of agent)
hubspot.deal.dealstage      : 'Demo Booked'   // Internal stage ID must be confirmed
hubspot.deal.demo_date      : ISO8601 date    // Custom property: demo_date
hubspot.deal.zoom_link      : string          // Custom property: zoom_join_url

// On pre-call brief send (T-24h)
gmail.brief_subject         : string   // e.g. 'Getting ready for your demo, {{firstname}}'
gmail.brief_body            : string   // Templated HTML, tokens: firstname, company, meeting_start, zoom_join_url
gmail.sent_message_id_brief : string

// On Slack notification (immediate)
slack.channel               : string   // e.g. '#sales-demos'
slack.message_blocks        : JSON     // Prospect name, company, time, rep @mention, Zoom link

// On reminder send (T-60min)
gmail.reminder_subject      : string   // e.g. 'Your demo starts in 60 minutes'
gmail.reminder_body         : string   // Zoom join link + one-line agenda
gmail.sent_message_id_reminder: string

// On approval (ambiguous lead flagged by Qualification Agent)
// Rep reviews contact in HubSpot, clears hs_review_required flag
// Workflow resumes from Scheduling and Confirmation Agent with rep-confirmed routing decision
  • The HubSpot deal stage ID for 'Demo Booked' must be confirmed from the client's HubSpot pipeline configuration before this agent can write the stage update. Stage IDs are pipeline-specific and cannot be assumed. Retrieve via GET /crm/v3/pipelines/deals.
  • Two custom HubSpot deal properties must be created before build: demo_date (date field) and zoom_join_url (single-line text). Confirm these do not already exist under different internal names to avoid duplicates.
  • The pre-call brief HTML template must be signed off by the sales lead before the agent is built. Personalisation tokens are: {{firstname}}, {{company}}, {{meeting_start}} (formatted as readable datetime), and {{zoom_join_url}}. If any HubSpot property is blank, the template must degrade gracefully, substituting 'your company' for a missing company value rather than rendering a broken token.
  • Slack integration method: use the Slack Incoming Webhook URL for simplicity at this build scale. If the client's Slack workspace restricts webhook creation, fall back to the Web API with a bot token and the chat:write scope.
  • The T-24h and T-60min timers must both be calculated from the confirmed booking.meeting_start value, not from the time the agent runs. Use scheduled execution with an exact datetime target, not relative delays, to avoid drift.
  • If the meeting is booked within 24 hours of the start time, the pre-call brief must be sent immediately rather than waiting for T-24h. Add a conditional check: if (meeting_start - now) < 24h then send brief immediately.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow with field names at every agent handoff
// ============================================================
// DEMO AND DISCOVERY SCHEDULING: END-TO-END DATA FLOW
// ============================================================

// TRIGGER
// HubSpot contact lifecycle stage changes to 'Demo Requested'
// OR HubSpot form submission mapped to demo-interest workflow
// Fields available at trigger:
//   contact.id, contact.email, contact.firstname, contact.lastname
//   contact.jobtitle, contact.company, contact.num_employees
//   contact.hs_lead_source, deal.id, deal.amount

// ============================================================
// AGENT 1: Lead Qualification Agent
// ============================================================

INPUT  <- HubSpot Contacts API GET /crm/v3/objects/contacts/{contact.id}
INPUT  <- HubSpot Deals API GET /crm/v3/objects/deals/{deal.id}

SCORE  = compute(contact.jobtitle, contact.num_employees,
                 contact.hs_lead_source, deal.amount)

IF score >= 50:
  routing.decision     = 'DISCOVERY' | 'FULL_DEMO'
  routing.calendly_url = select_url(routing.decision)
  // e.g. 'https://calendly.com/[YourCompany.com]/discovery-call'
  // or   'https://calendly.com/[YourCompany.com]/full-demo'
  PASS -> Agent 2 with: routing.decision, routing.calendly_url,
                        contact.id, contact.email, contact.firstname,
                        deal.id, rep.email, rep.zoom_user_id

IF score < 50:
  hubspot.contact.hs_review_required = true
  HubSpot Contacts API PATCH /crm/v3/objects/contacts/{contact.id}
  HALT  // Rep reviews; on manual clearance workflow resumes to Agent 2

// ============================================================
// AGENT 2: Scheduling and Confirmation Agent
// ============================================================

// STEP 2a: Send booking link
INPUT  <- routing.calendly_url, contact.email, contact.firstname, rep.email
OUTPUT -> Gmail API POST /gmail/v1/users/me/messages/send
          to: contact.email
          from: rep.email
          subject: 'Book your {routing.decision} with [YourCompany.com]'
          body: personalised HTML with routing.calendly_url embedded
          gmail.sent_message_id = response.id

// STEP 2b: 48-hour non-response branch
TIMER  = now() + 48h
WAIT   // Monitor for Calendly booking_confirmed webhook

IF no booking_confirmed received within 48h:
  OUTPUT -> Gmail API POST /gmail/v1/users/me/messages/send
            to: contact.email
            subject: 'Still happy to connect?'
            body: nudge email with routing.calendly_url
  // Single nudge only; no further automated follow-up

// STEP 2c: Calendly booking confirmed
INPUT  <- Calendly webhook POST {platform_webhook_endpoint}
          event.calendly_event_uri, event.start_time, event.end_time
          invitee.name, invitee.email

// STEP 2d: Create Zoom meeting
OUTPUT -> Zoom Meetings API POST /v2/users/{rep.zoom_user_id}/meetings
          body: { topic, start_time: event.start_time,
                  duration: (event.end_time - event.start_time) in minutes,
                  type: 2 }
          zoom.meeting_id  = response.id
          zoom.join_url    = response.join_url
          zoom.start_url   = response.start_url

// STEP 2e: Create Google Calendar invite
OUTPUT -> Google Calendar API POST /calendar/v3/calendars/primary/events
          body: { summary, start: event.start_time, end: event.end_time,
                  attendees: [contact.email, rep.email],
                  location: zoom.join_url,
                  description: 'Zoom link: ' + zoom.join_url }
          gcal.event_id    = response.id
          gcal.invite_link = response.htmlLink

// PASS confirmed booking payload to Agent 3
PASS -> Agent 3 with: booking.meeting_start = event.start_time,
                      zoom.join_url, gcal.event_id,
                      routing.deal_id, routing.contact_id,
                      contact.firstname, contact.email, contact.company,
                      rep.email, rep.slack_user_id

// ============================================================
// AGENT 3: Pre-Call Comms Agent
// ============================================================

// STEP 3a: Update HubSpot deal (immediate)
INPUT  <- routing.deal_id, booking.meeting_start, zoom.join_url
OUTPUT -> HubSpot Deals API PATCH /crm/v3/objects/deals/{deal.id}
          properties: { dealstage: '{demo_booked_stage_id}',
                        demo_date: booking.meeting_start,
                        zoom_join_url: zoom.join_url }

// STEP 3b: Post Slack notification (immediate)
OUTPUT -> Slack Incoming Webhook POST {slack_webhook_url}
          payload: { blocks: [
            { type: 'section', text: '*New demo booked*' },
            { type: 'section', text:
              'Prospect: ' + contact.firstname + ' ' + contact.lastname +
              ' | Company: ' + contact.company +
              ' | Time: ' + booking.meeting_start +
              ' | Rep: <@' + rep.slack_user_id + '>' +
              ' | Zoom: ' + zoom.join_url } ] }

// STEP 3c: Send pre-call brief (at booking.meeting_start - 24h)
TIMER  = booking.meeting_start - 24h
// If (meeting_start - now) < 24h: send immediately
OUTPUT -> Gmail API POST /gmail/v1/users/me/messages/send
          to: contact.email
          subject: 'Getting ready for your demo, {{firstname}}'
          body: HTML template with tokens:
                {{firstname}} <- contact.firstname
                {{company}}   <- contact.company
                {{meeting_start}} <- formatted datetime
                {{zoom_join_url}} <- zoom.join_url
          gmail.sent_message_id_brief = response.id

// STEP 3d: Send pre-call reminder (at booking.meeting_start - 60min)
TIMER  = booking.meeting_start - 60min
OUTPUT -> Gmail API POST /gmail/v1/users/me/messages/send
          to: contact.email
          subject: 'Your demo starts in 60 minutes'
          body: zoom.join_url + one-line agenda
          gmail.sent_message_id_reminder = response.id

// ============================================================
// END OF FLOW
// All steps completed without rep involvement (except manual
// review of REVIEW_REQUIRED leads before Agent 2 fires).
// ============================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform TBD). Structure as three discrete workflows, one per agent, connected by shared payload objects. Use a shared credential store so HubSpot, Gmail, Zoom, Calendly, and Slack credentials are defined once and referenced by all three workflows. Do not embed credentials inline in any workflow step.
Webhook configuration
Calendly booking_confirmed events post to the platform's inbound webhook endpoint. Register a single Calendly webhook at the organisation level (not per event type) with 'invitee.created' subscribed. The platform endpoint URL must be HTTPS. Validate the Calendly-Webhook-Signature header on every inbound request using the webhook signing secret to prevent spoofed payloads. HubSpot workflow triggers use the HubSpot Workflows API rather than a raw webhook to reduce surface area.
Templating approach
Pre-call brief and reminder emails are built as HTML templates stored in the orchestration layer's template store (or as a version-controlled plain-text file if the platform lacks a template store). Personalisation tokens use mustache-style syntax: {{firstname}}, {{company}}, {{meeting_start}}, {{zoom_join_url}}. Templates must degrade gracefully if a token resolves to null: use fallback strings ('there' for a missing firstname, 'your company' for a missing company). Template changes must be made in the template store only, not hardcoded into workflow steps, so the sales team can update copy without FullSpec involvement after launch.
Error logging
All agent errors (API failures, missing required fields, token resolution failures, timer miscalculations) write a row to a Supabase table named automation_errors with columns: error_id (uuid), workflow_name (text), step_name (text), contact_id (text), deal_id (text), error_message (text), occurred_at (timestamptz), resolved (boolean, default false). On any unhandled error the orchestration layer also posts an alert to a dedicated Slack channel (e.g. '#automation-alerts') via the same Slack webhook, including the workflow name and the contact ID for fast triage. Critical errors (Zoom meeting creation failure, HubSpot deal update failure) are labelled CRITICAL in the Slack alert body.
Testing approach
All three agents are built and tested against sandbox or test instances first: HubSpot sandbox account, Calendly test event types, Zoom sandbox API credentials, and Gmail test accounts. No live prospect data or production HubSpot deals are used during build. End-to-end test runs use a controlled test contact record with known property values to verify scoring, routing, booking, Zoom creation, CRM update, brief send, Slack post, and reminder send in sequence. All test runs are logged before moving to production credentials.
Estimated total build time
Lead Qualification Agent: 5 hours. Scheduling and Confirmation Agent: 7 hours. Pre-Call Comms Agent: 6 hours. Sub-total agent build: 18 hours. End-to-end integration testing and QA: 4 hours. Total estimated build time: 22 hours across the 4-week delivery schedule.
Before build begins, the FullSpec team requires the following to be confirmed and handed over: (1) HubSpot pipeline stage ID for 'Demo Booked' and confirmation that the two custom deal properties (demo_date and zoom_join_url) have been created; (2) the two Calendly event slugs for discovery-call and full-demo URLs; (3) Zoom OAuth tokens per rep or confirmation that a single admin token is acceptable for this phase; (4) the Slack channel name and webhook URL for both the sales notification and the automation-alerts monitoring channel; (5) the signed-off pre-call brief HTML template with agreed personalisation tokens; and (6) confirmation of the lead scoring thresholds with the sales lead. Contact support@gofullspec.com if any of these items are outstanding.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Demo & Discovery Scheduling.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View