Back to Appointment / Booking Management

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

Appointment / Booking Management

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

This document is the primary technical reference for the FullSpec team building the Appointment and Booking Management automation. It covers the current-state process map, all three agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything the FullSpec team needs to begin implementation without further clarification is contained here. Where an item requires confirmation before build can proceed, it is called out explicitly in the relevant agent block.

01Process snapshot

Step
Name
Description
1
Receive Booking Request
Client submits a request via email, web form, or phone. Staff check the relevant inbox or voicemail to identify new requests. Time cost: 5 min/booking.
2
Check Calendar Availability
Staff open Google Calendar and manually check whether the requested slot is free, accounting for existing bookings and buffer times. BOTTLENECK. Time cost: 8 min/booking.
3
Reply to Client With Confirmation or Alternatives
Staff compose and send a confirmation email, or propose alternative times if the slot is unavailable. Often involves back-and-forth exchanges. BOTTLENECK. Time cost: 10 min/booking.
4
Add Booking to Calendar
Staff manually create a calendar event with client name, service type, contact details, and relevant notes. Time cost: 6 min/booking.
5
Log Booking in CRM
Staff open HubSpot and create or update the contact record, logging appointment date, service, and status. Time cost: 7 min/booking.
6
Collect Payment or Deposit
If a deposit is required, staff send a Stripe payment link manually and monitor for payment confirmation before treating the booking as confirmed. Time cost: 5 min/booking.
7
Send Reminder Message
Staff set a reminder in their own calendar or task list to manually send a reminder email or SMS to the client 24 to 48 hours before the appointment. Time cost: 4 min/booking.
8
Handle Cancellations and Reschedules
When a client cancels or reschedules, staff update the calendar, notify any affected colleagues, and begin the confirmation loop again. BOTTLENECK. Time cost: 10 min/booking.
9
Follow Up After No-Show
If a client does not attend, staff send a follow-up email manually and update the CRM record to reflect the missed appointment. Time cost: 6 min/booking.
Time cost summary: Total manual time per booking cycle is 61 minutes. At approximately 120 bookings per month (30 per week), total manual overhead is approximately 7 hours per week and 28 hours per 30 days. The automation replaces steps 1 through 9 in the routine flow. Step 8 (complex reschedule disputes and refund decisions) remains human-handled by exception only. Steps 2, 3, and 8 are the three confirmed bottlenecks and account for 28 of the 61 minutes per cycle.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Booking Intake Agent

Handles all inbound booking requests arriving via the Calendly booking page or an embedded web form. The agent checks real-time availability through Calendly, routes the request based on slot availability, creates the confirmed Google Calendar event with appropriate buffer periods, and sends a branded confirmation email via Gmail. This agent eliminates the manual availability check and client reply loop that currently account for 18 minutes per booking. It is the entry point for the entire automation and must produce a clean, structured booking record before passing control to the CRM and Payment Agent.

Trigger
A booking request is submitted via the Calendly booking link or an embedded web form on the business website.
Tools
Calendly, Google Calendar, Gmail
Replaces steps
Steps 1, 2, 3, and 4 (Receive Request, Check Availability, Reply to Client, Add to Calendar)
Estimated build
12 hours / Moderate complexity
// Input
calendly.event.created webhook payload {
  event_uuid: string,
  event_type_name: string,
  start_time: ISO8601,
  end_time: ISO8601,
  invitee.name: string,
  invitee.email: string,
  invitee.timezone: string,
  questions_and_answers: [ { question: string, answer: string } ]
}

// Output
google_calendar.event.created {
  calendar_id: 'primary',
  summary: '{invitee.name} - {event_type_name}',
  start: { dateTime: start_time, timeZone: invitee.timezone },
  end: { dateTime: end_time_plus_buffer, timeZone: invitee.timezone },
  description: 'Auto-created by FullSpec Booking Automation',
  attendees: [ { email: invitee.email } ]
}

gmail.message.sent {
  to: invitee.email,
  subject: 'Your booking is confirmed - {event_type_name}',
  body: confirmation_template rendered with booking_details
}

booking_record passed to CRM and Payment Agent {
  invitee_name: string,
  invitee_email: string,
  invitee_phone: string | null,
  event_type_name: string,
  start_time: ISO8601,
  end_time: ISO8601,
  calendar_event_id: string,
  deposit_required: boolean
}
  • Calendly plan must support webhook event notifications (Teams plan or above at $16/month confirmed in tooling). Verify that the 'invitee.created' webhook event is enabled on the correct event type before build.
  • Google Calendar must be the single authoritative calendar for availability. If multiple staff calendars are in use, confirm which calendar_id is the source of truth before build. The automation writes to 'primary' by default.
  • Buffer time (before and after each appointment) must be configured in Calendly directly. The automation reads the end_time from the Calendly payload, which already includes the buffer if set in Calendly event settings.
  • The Gmail confirmation template must be provided by the business owner as branded HTML before the send node is built. A plain-text fallback must also be prepared.
  • Invitee phone number is captured via a Calendly custom question field. Confirm the exact question label in the Calendly form so the parser maps it to invitee_phone correctly.
  • Deposit required flag is determined by a lookup against the event_type_name. A mapping table (event type to deposit true/false) must be confirmed with the business owner before build and stored as a configuration variable in the orchestration layer.
  • Deduplication: if a Calendly webhook fires twice for the same event_uuid (retry scenario), the agent checks whether a Google Calendar event with that uuid in its description already exists before creating a new one. If found, it skips creation and logs a duplicate warning.
CRM and Payment Agent

Receives the structured booking record from the Booking Intake Agent and handles all CRM logging and conditional payment collection. The agent performs a HubSpot contact lookup by email, creates the contact if not found, and creates or updates a deal record linked to the contact with the booking date, service type, and status. If deposit_required is true, it generates a Stripe payment link and sends it to the client via Gmail, then listens for the Stripe payment_intent.succeeded webhook to mark the booking as payment-confirmed in HubSpot. If deposit_required is false, the booking is marked confirmed immediately and control passes to the Reminder and Follow-Up Agent.

Trigger
A confirmed booking record is passed from the Booking Intake Agent via the orchestration layer.
Tools
HubSpot, Stripe, Gmail
Replaces steps
Steps 5 and 6 (Log Booking in CRM, Collect Payment or Deposit)
Estimated build
14 hours / Moderate complexity
// Input
booking_record {
  invitee_name: string,
  invitee_email: string,
  invitee_phone: string | null,
  event_type_name: string,
  start_time: ISO8601,
  calendar_event_id: string,
  deposit_required: boolean
}

// On approval (deposit path)
stripe.payment_intent.succeeded webhook {
  payment_intent_id: string,
  amount_received: integer (cents),
  customer_email: string,
  metadata.booking_calendar_event_id: string
}

// Output
hubspot.contact.upserted {
  contact_id: string,
  email: invitee_email,
  firstname: invitee_name (first token),
  lastname: invitee_name (remaining tokens),
  phone: invitee_phone | null
}

hubspot.deal.created {
  deal_id: string,
  dealname: '{event_type_name} - {invitee_name} - {start_time date}',
  dealstage: 'appointment_confirmed' | 'awaiting_deposit',
  appointment_date: start_time,
  service_type: event_type_name,
  payment_status: 'confirmed' | 'pending' | 'not_required',
  calendar_event_id: string
}

stripe.payment_link.sent (deposit path) {
  payment_link_url: string,
  amount: deposit_amount_cents,
  metadata.booking_calendar_event_id: calendar_event_id
}

gmail.message.sent (deposit path) {
  to: invitee_email,
  subject: 'Complete your booking deposit - {event_type_name}',
  body: deposit_template rendered with payment_link_url
}

confirmed_booking_record passed to Reminder and Follow-Up Agent {
  contact_id: string,
  deal_id: string,
  invitee_email: string,
  invitee_phone: string | null,
  start_time: ISO8601,
  calendar_event_id: string
}
  • HubSpot tier must support Deals API and custom deal properties (Starter CRM or above at $50/month confirmed in tooling). Verify that custom properties 'appointment_date', 'service_type', 'payment_status', and 'calendar_event_id' exist or create them during the discovery stage.
  • HubSpot contact lookup uses email as the unique key. If a contact exists, update it. If not, create it. Never create duplicate contacts. Use the HubSpot search API (POST /crm/v3/objects/contacts/search) with filter on 'email' property before any create call.
  • Stripe account must be verified and in live mode before deposit links can be sent to real clients. Test mode is used during QA. Confirm deposit amount per event type with the business owner. Store deposit amounts in the same event-type configuration table as the deposit_required flag.
  • Stripe payment link metadata must include calendar_event_id so the payment_intent.succeeded webhook can match the payment back to the correct booking record. This is the primary correlation key.
  • If a deposit payment is not received within a configurable window (default 24 hours), the agent sends a single payment reminder email and logs a 'deposit_overdue' status on the HubSpot deal. It does not cancel the booking automatically. Escalation to the operations team is logged via error logging (see Section 04).
  • Gmail deposit template must be provided by the business owner before this agent is built. The template must include the payment_link_url variable token.
  • HubSpot deal pipeline and stage IDs must be confirmed during discovery. The automation writes to the pipeline ID, not the stage name string, to avoid breakage if the business renames stages.
Reminder and Follow-Up Agent

Manages all time-based communication for confirmed bookings. The agent schedules two reminder touchpoints per booking: one 48 hours before the appointment via both Twilio SMS and Gmail, and one 2 hours before the appointment via the same two channels. After the appointment window closes, the agent checks the HubSpot deal for a completion status. If the deal has not been updated to 'appointment_completed' within 30 minutes of the scheduled end time, the agent triggers the no-show branch: it sends a follow-up email via Gmail and updates the HubSpot deal status to 'no_show'. Complex reschedule disputes and refund decisions are flagged to the operations team and not handled by the automation.

Trigger
Confirmed booking record received from the CRM and Payment Agent. Reminder jobs are scheduled at booking confirmation time relative to start_time. No-show detection runs on a poll or scheduled check after start_time plus appointment duration plus 30 minutes.
Tools
Twilio, Gmail, HubSpot
Replaces steps
Steps 7 and 9 (Send Reminder Message, Follow Up After No-Show)
Estimated build
10 hours / Moderate complexity
// Input
confirmed_booking_record {
  contact_id: string,
  deal_id: string,
  invitee_email: string,
  invitee_phone: string | null,
  start_time: ISO8601,
  calendar_event_id: string
}

// Scheduled reminder jobs created at input time
reminder_job_48h: { execute_at: start_time minus 48 hours }
reminder_job_2h:  { execute_at: start_time minus 2 hours }
noshow_check_job: { execute_at: start_time plus appointment_duration_minutes plus 30 min }

// Output (reminder path - executed at each scheduled time)
twilio.sms.sent {
  to: invitee_phone (E.164 format),
  from: twilio_registered_number,
  body: reminder_sms_template rendered with { appointment_time, service_type }
}

gmail.message.sent {
  to: invitee_email,
  subject: 'Reminder: your appointment is coming up',
  body: reminder_email_template rendered with booking_details
}

// Output (no-show path - executed by noshow_check_job if deal not updated)
hubspot.deal.updated {
  deal_id: string,
  dealstage: 'no_show',
  no_show_date: ISO8601
}

gmail.message.sent {
  to: invitee_email,
  subject: 'We missed you today - let us rebook',
  body: noshow_followup_template rendered with { rebooking_link, invitee_name }
}
  • Twilio account must have a registered sending number before build. For US-based SMS at volume, A2P 10DLC registration is required. This registration can take 2 to 4 weeks and must be initiated immediately after discovery is complete. Do not block build on this but confirm the timeline with the business owner.
  • If invitee_phone is null (not captured at booking), the SMS step is skipped gracefully and only the Gmail reminder is sent. Log a warning to the error log but do not fail the run.
  • All reminder and no-show jobs must be stored as scheduled tasks in the orchestration layer's job queue with deal_id as the correlation key. If a booking is cancelled in Calendly before the reminder fires, a Calendly invitee.cancelled webhook must trigger cancellation of the corresponding pending jobs.
  • No-show detection reads the HubSpot deal stage via the HubSpot Deals API (GET /crm/v3/objects/deals/{deal_id}). If dealstage equals 'appointment_completed', no action is taken. If dealstage is any other value after the noshow_check_job fires, the no-show branch executes.
  • SMS and email reminder templates must be provided by the business owner before this agent is built. The Twilio template body must be under 160 characters for a single SMS segment.
  • Appointment duration in minutes must be stored as a configuration variable per event type to calculate the noshow_check_job execution time correctly.
  • Twilio rate limit is 1 message per second per long-code number. For volumes above 120 bookings per month the current rate is well within limits, but a short-code or toll-free number should be evaluated if volume grows significantly.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow across all three agents. Field names are exact as used in each API call.
// ============================================================
// END-TO-END DATA FLOW: Appointment / Booking Management
// FullSpec Automation Platform
// ============================================================

// TRIGGER
// Client submits booking via Calendly link or embedded web form
INBOUND: calendly.webhook [event: invitee.created]
  -> event_uuid: string
  -> event_type_name: string
  -> start_time: ISO8601
  -> end_time: ISO8601
  -> invitee.name: string
  -> invitee.email: string
  -> invitee.timezone: string
  -> questions_and_answers[phone]: string | null

// ============================================================
// AGENT 1: Booking Intake Agent
// ============================================================

STEP 1.1: Parse Calendly payload
  -> invitee_name      = invitee.name
  -> invitee_email     = invitee.email
  -> invitee_phone     = questions_and_answers[label='Phone number'].answer | null
  -> invitee_timezone  = invitee.timezone
  -> start_time        = event.start_time
  -> end_time          = event.end_time  // includes Calendly buffer if configured
  -> event_type_name   = event.event_type.name
  -> event_uuid        = event.uuid

STEP 1.2: Deduplication check
  QUERY google_calendar.events (description contains event_uuid)
  IF found -> SKIP creation, LOG warning 'duplicate_event_uuid', EXIT agent
  IF not found -> CONTINUE

STEP 1.3: Create Google Calendar event
  POST google_calendar.events
  -> calendar_id      = config.primary_calendar_id
  -> summary          = '{invitee_name} - {event_type_name}'
  -> start.dateTime   = start_time
  -> end.dateTime     = end_time
  -> start.timeZone   = invitee_timezone
  -> description      = 'FullSpec auto-created | uuid:{event_uuid}'
  -> attendees        = [ invitee_email ]
  RETURN calendar_event_id

STEP 1.4: Send confirmation email via Gmail
  POST gmail.messages.send
  -> to      = invitee_email
  -> subject = 'Your booking is confirmed - {event_type_name}'
  -> body    = render(confirmation_email_template, { invitee_name, start_time,
                      event_type_name, invitee_timezone })

STEP 1.5: Lookup deposit_required flag
  QUERY config.event_type_deposit_map[event_type_name]
  -> deposit_required: boolean
  -> deposit_amount_cents: integer | null

// AGENT HANDOFF 1 -> 2
EMIT booking_record {
  invitee_name, invitee_email, invitee_phone,
  event_type_name, start_time, end_time,
  calendar_event_id, deposit_required, deposit_amount_cents
}

// ============================================================
// AGENT 2: CRM and Payment Agent
// ============================================================

STEP 2.1: HubSpot contact upsert
  POST /crm/v3/objects/contacts/search
  -> filterGroups[0].filters[0].propertyName = 'email'
  -> filterGroups[0].filters[0].value        = invitee_email
  IF contact found -> contact_id = results[0].id
  IF not found ->
    POST /crm/v3/objects/contacts
    -> email     = invitee_email
    -> firstname = invitee_name.split(' ')[0]
    -> lastname  = invitee_name.split(' ').slice(1).join(' ')
    -> phone     = invitee_phone | null
    RETURN contact_id

STEP 2.2: HubSpot deal create
  POST /crm/v3/objects/deals
  -> dealname          = '{event_type_name} - {invitee_name} - {start_time.date}'
  -> pipeline          = config.hubspot_pipeline_id
  -> dealstage         = 'awaiting_deposit' IF deposit_required ELSE 'appointment_confirmed'
  -> appointment_date  = start_time
  -> service_type      = event_type_name
  -> payment_status    = 'pending' IF deposit_required ELSE 'not_required'
  -> calendar_event_id = calendar_event_id
  RETURN deal_id

STEP 2.3: Associate deal to contact
  PUT /crm/v3/objects/deals/{deal_id}/associations/contacts/{contact_id}/deal_to_contact

STEP 2.4: Deposit branch (IF deposit_required = true)
  POST stripe.paymentLinks.create
  -> line_items[0].price     = config.stripe_price_id[event_type_name]
  -> line_items[0].quantity  = 1
  -> metadata.calendar_event_id = calendar_event_id
  -> metadata.deal_id           = deal_id
  -> after_completion.type   = 'redirect'
  RETURN payment_link_url

  POST gmail.messages.send
  -> to      = invitee_email
  -> subject = 'Complete your booking deposit - {event_type_name}'
  -> body    = render(deposit_email_template, { payment_link_url, invitee_name })

  AWAIT stripe.webhook [event: payment_intent.succeeded]
  -> MATCH metadata.calendar_event_id = calendar_event_id
  -> PATCH /crm/v3/objects/deals/{deal_id}
     -> dealstage     = 'appointment_confirmed'
     -> payment_status = 'confirmed'
     -> payment_amount = amount_received_cents / 100

// AGENT HANDOFF 2 -> 3
EMIT confirmed_booking_record {
  contact_id, deal_id, invitee_email, invitee_phone,
  start_time, end_time, calendar_event_id,
  appointment_duration_minutes = config.event_type_duration_map[event_type_name]
}

// ============================================================
// AGENT 3: Reminder and Follow-Up Agent
// ============================================================

STEP 3.1: Schedule reminder jobs
  SCHEDULE reminder_job_48h AT (start_time - 48h)
  SCHEDULE reminder_job_2h  AT (start_time - 2h)
  SCHEDULE noshow_check     AT (start_time + appointment_duration_minutes + 30 min)
  STORE { deal_id, calendar_event_id } as job metadata

STEP 3.2: Register Calendly cancellation listener
  LISTEN calendly.webhook [event: invitee.cancelled, uuid: event_uuid]
  IF fired -> CANCEL all pending jobs for this deal_id
           -> PATCH hubspot.deal { dealstage: 'cancelled' }
           -> LOG 'booking_cancelled'

STEP 3.3: Execute reminder_job_48h
  IF invitee_phone != null:
    POST twilio.messages.create
    -> to   = invitee_phone (E.164)
    -> from = config.twilio_sending_number
    -> body = render(reminder_sms_48h_template, { start_time, event_type_name })
  POST gmail.messages.send
  -> to      = invitee_email
  -> subject = 'Reminder: your appointment is tomorrow'
  -> body    = render(reminder_email_48h_template, { invitee_name, start_time })

STEP 3.4: Execute reminder_job_2h
  IF invitee_phone != null:
    POST twilio.messages.create
    -> body = render(reminder_sms_2h_template, { start_time, event_type_name })
  POST gmail.messages.send
  -> subject = 'Reminder: your appointment is in 2 hours'
  -> body    = render(reminder_email_2h_template, { invitee_name, start_time })

STEP 3.5: Execute noshow_check
  GET /crm/v3/objects/deals/{deal_id}?properties=dealstage
  IF dealstage = 'appointment_completed' -> EXIT (attended)
  IF dealstage != 'appointment_completed':
    PATCH /crm/v3/objects/deals/{deal_id}
    -> dealstage    = 'no_show'
    -> no_show_date = NOW()
    POST gmail.messages.send
    -> to      = invitee_email
    -> subject = 'We missed you today - let us rebook'
    -> body    = render(noshow_followup_template, { invitee_name, rebooking_link })

// END OF FLOW
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool is used to host the three agents as separate workflows sharing a single credential store. One workflow per agent is the enforced pattern: Booking Intake Workflow, CRM and Payment Workflow, and Reminder and Follow-Up Workflow. Inter-agent communication uses a shared internal data store (key-value or lightweight queue) keyed on calendar_event_id. No build platform is specified at this stage; the pattern is platform-agnostic.
Webhook configuration
Calendly webhooks: two subscriptions required, one for 'invitee.created' and one for 'invitee.cancelled'. Both point to the orchestration layer's inbound webhook URL. Stripe webhooks: one subscription for 'payment_intent.succeeded', filtered to payment intents with metadata containing 'booking_calendar_event_id'. All webhook endpoints must be HTTPS with signature verification enabled (Calendly HMAC header 'Calendly-Webhook-Signature', Stripe 'Stripe-Signature'). Rotating secrets are stored in the credential store, not in workflow nodes.
Templating approach
All email bodies and SMS text are stored as named templates in the credential or config store, not hardcoded in workflow nodes. Template variables use double-brace syntax, for example {{invitee_name}}, {{start_time}}, {{payment_link_url}}. This allows the business owner to update message content without a code change. Templates required before build: confirmation_email, deposit_email, reminder_email_48h, reminder_email_2h, reminder_sms_48h, reminder_sms_2h, noshow_followup_email. Gmail sends use HTML with a plain-text fallback. SMS templates must be 160 characters or under per message.
Error logging
All agent errors and warning events are written to a dedicated error log table (Supabase or equivalent lightweight database). Each row contains: timestamp, agent_name, deal_id (if available), calendar_event_id (if available), error_code, error_message, raw_payload. A Slack alert is triggered for any error_code classified as CRITICAL (webhook signature failure, HubSpot write failure, Stripe link creation failure). WARNING-level events (null phone number, duplicate event_uuid) are logged only and reviewed in the weekly ops check. The error log table is readable by the FullSpec team and the operations manager at [YourCompany.com].
Testing approach
All agents are built and validated in sandbox mode first. Calendly test webhooks are replayed using the Calendly webhook testing tool. Stripe is tested in test mode with test card numbers before switching to live mode. HubSpot sandbox account (if available on the client's plan) is used for CRM writes during QA. Twilio test credentials are used for SMS validation. End-to-end regression tests are run against the full flow with live credentials in a controlled test environment before go-live. Refer to the FullSpec Test and QA Plan (FS-DOC-06) for the full test case list.
Configuration store entries
primary_calendar_id (string), hubspot_pipeline_id (string), hubspot_stage_map (object: stage names to IDs), event_type_deposit_map (object: event type name to {deposit_required: boolean, deposit_amount_cents: integer}), event_type_duration_map (object: event type name to minutes), stripe_price_id_map (object: event type name to Stripe Price ID), twilio_sending_number (E.164 string), rebooking_link (URL string), deposit_overdue_hours (integer, default 24). All entries are confirmed with the business owner during the discovery stage before any agent build begins.
Estimated total build time
Booking Intake Agent: 12 hours. CRM and Payment Agent: 14 hours. Reminder and Follow-Up Agent: 10 hours. End-to-end integration testing and QA: 4 hours. Total: 40 hours. This matches the confirmed build_effort_hours in the process snapshot.
Before any build work begins, the FullSpec team requires the following items confirmed by the business owner: (1) the primary Google Calendar ID controlling availability, (2) the event type to deposit amount mapping, (3) the HubSpot pipeline ID and deal stage names, (4) the Twilio sending number and confirmation that A2P 10DLC registration is in progress if sending to US numbers, (5) all email and SMS templates in their final approved form, and (6) Stripe live-mode access with price IDs created for each depositpayable service. Build cannot start on the CRM and Payment Agent or the Reminder and Follow-Up Agent until items 2, 3, 4, and 5 are received. Contact the FullSpec team at support@gofullspec.com with any questions.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Appointment / Booking Management.

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