Back to Job / Project 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

Job / Project Scheduling Automation

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

This document gives the FullSpec build team everything needed to implement the Job / Project Scheduling automation end to end. It covers the current manual process, all agent specifications with IO contracts, the full data flow from trigger to final output, and the recommended build stack. The process owner's responsibility is to confirm tool access and calendar hygiene before build begins. Everything else, including agent construction, integration wiring, testing, and handoff, is handled by the FullSpec team.

01Process snapshot

Step
Name
Description
1
Receive and Log Job Request
Scheduler receives a confirmed job by phone, email, or form and manually enters details into Google Sheets or Jobber. (8 min)
2
Check Resource Availability
Scheduler reviews team Google Calendars one by one to identify free crew members during the required window. BOTTLENECK. (15 min)
3
Confirm Equipment and Vehicle Availability
Scheduler cross-checks a Google Sheets log to verify equipment or vehicles are not already allocated to another same-day job. (10 min)
4
Assign Crew and Create Calendar Event
Scheduler manually creates a Google Calendar event, adds assigned crew, and enters job address, contact, and scope notes. (12 min)
5
Update Job Management System
Scheduler opens Jobber and updates the job record with confirmed date, time, and assigned crew to keep both systems in sync. (8 min)
6
Notify Assigned Crew
Scheduler sends individual or group Slack messages to assigned crew with job details, address, and start time. (7 min)
7
Send Confirmation to Customer
Scheduler emails the customer via Gmail to confirm booking date, time, and crew lead name. (6 min)
8
Update CRM Record
Scheduler opens HubSpot and logs the scheduled date against the deal or contact record. (5 min)
9
Handle Rescheduling or Changes
When a job changes, the scheduler repeats steps 2 through 8 manually for every affected booking. BOTTLENECK. (20 min)
Time cost summary: Total manual time per scheduling cycle is 91 minutes (single new job, no reschedule). With rescheduling factored into a typical week the process consumes approximately 6 hours per week across roughly 40 to 80 jobs per month. Steps 1 through 8 are replaced by the two agents below. Step 9 (rescheduling) is re-triggered automatically from Jobber on each change event and re-runs the full agent chain; genuine conflicts that cannot be resolved automatically are flagged to the scheduler as the sole remaining human step.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Availability and Assignment Agent

This agent is the core scheduling engine. It fires the moment a job status transitions to confirmed in Jobber, reads all job metadata from the webhook payload, and queries Google Calendar for each eligible crew member within the required date and time window. It ranks available crew by availability first, then by proximity rules if a location field is populated on the Jobber job record. Once the best match is identified it creates a Google Calendar event with the job address, customer contact details, scope notes, and all assigned crew as attendees, then writes the confirmed assignment back to the Jobber job record. If no crew member is free, it routes an exception flag to the scheduler and halts without creating a partial event. Estimated build time: 16 hours. Complexity: Moderate.

Trigger
Jobber webhook: job.status_changed to 'confirmed' (also fires on job.updated for reschedule events)
Tools
Jobber (REST API v2), Google Calendar (Calendar API v3)
Replaces steps
Steps 1, 2, 3, 4, 5
Estimated build
16 hours | Moderate
// Input (from Jobber webhook payload)
job_id          : string   // Jobber unique job identifier
job_status      : string   // must equal 'confirmed' to proceed
job_title       : string   // human-readable job name
required_date   : date     // ISO 8601 preferred start date
required_window : object   // { start: datetime, end: datetime }
site_address    : string   // full service address
customer_name   : string   // contact name for calendar event
customer_phone  : string   // on-the-day contact number
scope_notes     : string   // free-text job description / special instructions
crew_required   : integer  // number of crew members needed

// Output (passed to Notifications and CRM Sync Agent)
assignment_id        : string   // internal run UUID for traceability
assigned_crew        : array    // [{ crew_member_id, name, email, calendar_id }]
calendar_event_id    : string   // Google Calendar event ID (created by this agent)
calendar_event_link  : string   // HTML link to the created event
confirmed_start      : datetime // ISO 8601 confirmed job start
confirmed_end        : datetime // ISO 8601 confirmed job end
jobber_job_id        : string   // echoed through for downstream sync
site_address         : string   // echoed through for notifications
customer_name        : string   // echoed through for customer email
customer_email       : string   // looked up from Jobber contact record
customer_phone       : string   // echoed through
scope_notes          : string   // echoed through

// On exception (no crew available)
exception_flag       : boolean  // true when no assignment could be made
exception_reason     : string   // human-readable explanation for scheduler
scheduler_alert_sent : boolean  // true after Slack DM dispatched to scheduler
  • Jobber webhook version: use Jobber REST API v2. Register the webhook at POST /v2/webhooks targeting the automation platform's inbound URL. The webhook secret must be stored in the shared credential store and validated on every inbound request using HMAC-SHA256 signature verification.
  • Google Calendar API tier: requires Google Workspace (Business Starter or above) with Calendar API v3 enabled in the connected Google Cloud project. The service account or OAuth client must have read access to all crew member calendars via domain-wide delegation or per-user consent before build begins.
  • Crew calendar list: the list of crew Google Calendar IDs must be stored as a structured variable in the automation platform (not hardcoded). It must be updatable by the process owner without a code change.
  • Availability query: call Events.list on each crew calendar with timeMin=required_window.start and timeMax=required_window.end, checking for zero overlapping events with status 'confirmed' or 'tentative'. A 'free/busy' query (Calendar.freebusy) is preferred for efficiency when checking more than three crew members simultaneously.
  • Calendar event creation: the event must be created with status 'confirmed', visibility 'private', and attendees set to all assigned crew email addresses. The event description field must include job_id, site_address, scope_notes, and customer_phone.
  • Jobber write-back: PATCH /v2/jobs/{job_id} to update assigned_to (crew member IDs) and scheduled_start / scheduled_end. Confirm the Jobber subscription tier supports job update via API before build begins; this requires Jobber Grow or Connect plan.
  • Dedupe / idempotency: store assignment_id keyed on job_id in the error log table. If the same job_id is received twice (webhook retry), check the table first and skip creation if a calendar event already exists for that job_id.
  • Exception routing: if no crew is available, send a Slack DM to the designated scheduler user ID (stored in credential store) with the job details and required window. Do not create a partial calendar event. Set exception_flag = true in the output payload.
  • Proximity rules: if the Jobber job record includes a GPS-derived postcode or zone field, sort available crew by zone match before selecting. If no zone data is present, fall back to first-available ordering. Confirm whether zone data exists in Jobber before implementing proximity logic.
Notifications and CRM Sync Agent

This agent takes the confirmed assignment output from the Availability and Assignment Agent and executes three parallel downstream actions: a Slack notification to the assigned crew, a Gmail confirmation email to the customer, and a HubSpot deal record update. It runs only when exception_flag is false. If any of the three actions fail, the others are not rolled back; instead, the failure is logged to the error table and an alert is sent to the scheduler so they can re-send manually if needed. This agent has no external trigger of its own; it is invoked directly by the preceding agent's output. Estimated build time: 10 hours. Complexity: Simple.

Trigger
Internal: output payload from Availability and Assignment Agent where exception_flag = false
Tools
Slack (Web API), Gmail (Gmail API v1 via OAuth), HubSpot (CRM API v3)
Replaces steps
Steps 6, 7, 8
Estimated build
10 hours | Simple
// Input (from Availability and Assignment Agent output)
assignment_id       : string   // used for log correlation
assigned_crew       : array    // [{ name, email, slack_user_id }]
confirmed_start     : datetime // job start for Slack and email copy
confirmed_end       : datetime // job end for Slack and email copy
site_address        : string   // included in all three notifications
customer_name       : string   // included in Slack and Gmail
customer_email      : string   // Gmail recipient address
customer_phone      : string   // included in Gmail confirmation
scope_notes         : string   // included in Slack message for crew
calendar_event_link : string   // included in Slack message for crew
jobber_job_id       : string   // used for HubSpot deal lookup
exception_flag      : boolean  // agent must halt if true

// Output (logged on completion)
slack_message_ts    : string   // Slack message timestamp for audit
gmail_message_id    : string   // Gmail sent message ID
hubspot_deal_id     : string   // HubSpot deal ID updated
hubspot_property    : string   // property name written: 'scheduled_job_date'
sync_status         : object   // { slack: 'sent'|'failed', gmail: 'sent'|'failed', hubspot: 'updated'|'failed' }
completed_at        : datetime // ISO 8601 timestamp of agent completion
  • Slack API: use the Slack Web API chat.postMessage method. The bot token must have the chat:write and users:read OAuth scopes. crew_slack_user_id values must be stored in the crew calendar config variable alongside calendar IDs. If a crew member has no Slack user ID on record, fall back to posting in the designated crew channel (channel ID stored in credential store) and tagging by name.
  • Gmail API: use Gmail API v1 via OAuth 2.0 (not SMTP). The OAuth client must have the gmail.send scope only. The sending address must be a verified Gmail or Google Workspace address owned by [YourCompany.com]. Confirm the sending address before build. The email template must be stored as an editable HTML template in the automation platform's template store.
  • Gmail template fields: the confirmation email must interpolate customer_name, confirmed_start (formatted as human-readable date and time), confirmed_end, site_address, and customer_phone. The crew lead name (first entry in assigned_crew array) must also appear in the email body.
  • HubSpot CRM API: use HubSpot CRM API v3. Look up the deal by jobber_job_id stored in a custom HubSpot deal property named 'jobber_job_id' (must be created in HubSpot before build). Update the deal property 'scheduled_job_date' (date type) with confirmed_start. Confirm the HubSpot subscription tier includes custom properties and API access (Professional plan or above required).
  • Parallel execution: Slack, Gmail, and HubSpot actions may run in parallel branches rather than sequentially to reduce total agent runtime. Each branch must have independent error handling so a Gmail failure does not prevent the Slack message from sending.
  • Error logging: on any action failure, write a row to the Supabase error log table (see section 04) with assignment_id, failed_action, error_code, error_message, and timestamp. Send a Slack DM alert to the scheduler user ID immediately after logging.
  • Dedupe: check assignment_id against the Supabase log before executing. If this assignment_id has already been processed successfully (all three sync_status values 'sent' or 'updated'), skip re-execution to prevent duplicate notifications on webhook retries.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow: Job / Project Scheduling Automation
// ─────────────────────────────────────────────────────────────────
// TRIGGER: Jobber webhook fires on job.status_changed = 'confirmed'
// ─────────────────────────────────────────────────────────────────
INBOUND webhook_payload {
  job_id          : 'JB-00421'
  job_status      : 'confirmed'
  job_title       : 'Roof Inspection - 14 Maple St'
  required_date   : '2025-07-08'
  required_window : { start: '2025-07-08T08:00:00Z', end: '2025-07-08T12:00:00Z' }
  site_address    : '14 Maple Street, Springfield, ST 00100'
  customer_name   : 'Jordan Riley'
  customer_phone  : '+1 555 201 4400'
  scope_notes     : 'Full ridge and valley inspection, 2-storey access required'
  crew_required   : 2
}

// ─────────────────────────────────────────────────────────────────
// VALIDATION: HMAC-SHA256 signature check on X-Jobber-Hmac-SHA256 header
// ─────────────────────────────────────────────────────────────────
validate_signature(webhook_payload, secret) -> pass | reject_with_401

// ─────────────────────────────────────────────────────────────────
// DEDUPE CHECK: query Supabase table 'scheduling_runs'
// ────────────────────────────────────────��────────────────────────
SELECT assignment_id FROM scheduling_runs WHERE job_id = 'JB-00421'
  -> no row found: proceed
  -> row found with status = 'complete': halt (duplicate webhook)

// ─────────────────────────────────────────────────────────────────
// AGENT 1: Availability and Assignment Agent
// ─────────────────────────────────────────────────────────────────

// Step 1: Query Google Calendar freebusy for all crew
Google_Calendar.freebusy.query({
  timeMin  : '2025-07-08T08:00:00Z'
  timeMax  : '2025-07-08T12:00:00Z'
  items    : [ { id: 'crew1@yc.com' }, { id: 'crew2@yc.com' }, { id: 'crew3@yc.com' } ]
}) -> busy_map { crew1: [], crew2: [{ start, end }], crew3: [] }

// Step 2: Filter available crew
available_crew = [ crew1@yc.com, crew3@yc.com ]  // crew2 has conflict

// Step 3: Apply zone / proximity sort (if zone field populated on job)
ranked_crew = sort_by_zone(available_crew, job_zone='North')
  -> assigned_crew = [ { id: 'CREW-01', name: 'Alex Dolan', email: 'crew1@yc.com', slack_user_id: 'U04XXALEX' },
                        { id: 'CREW-03', name: 'Sam Park', email: 'crew3@yc.com', slack_user_id: 'U04XXSAM' } ]

// Step 4: Create Google Calendar event
Google_Calendar.events.insert({
  calendarId  : 'primary'
  summary     : 'Roof Inspection - 14 Maple St [JB-00421]'
  start       : { dateTime: '2025-07-08T08:00:00Z' }
  end         : { dateTime: '2025-07-08T12:00:00Z' }
  location    : '14 Maple Street, Springfield, ST 00100'
  description : 'Job ID: JB-00421 | Customer: Jordan Riley | Ph: +1 555 201 4400 | Notes: Full ridge and valley inspection, 2-storey access required'
  attendees   : [ { email: 'crew1@yc.com' }, { email: 'crew3@yc.com' } ]
  status      : 'confirmed'
  visibility  : 'private'
}) -> calendar_event_id: 'GCAL-EVENT-88821'
   -> calendar_event_link: 'https://calendar.google.com/event?eid=...'

// Step 5: Write assignment back to Jobber
Jobber.PATCH /v2/jobs/JB-00421 {
  assigned_to      : [ 'CREW-01', 'CREW-03' ]
  scheduled_start  : '2025-07-08T08:00:00Z'
  scheduled_end    : '2025-07-08T12:00:00Z'
} -> jobber_write_status: 200 OK

// Step 6: Generate assignment_id and insert run record
assignment_id = uuid()  // e.g. 'a3f9-11ee-...'
Supabase.INSERT scheduling_runs { job_id, assignment_id, status: 'assigned', created_at }

// ─────────────────────────────────────────────────────────────────
// AGENT HANDOFF: Availability and Assignment Agent -> Notifications and CRM Sync Agent
// Payload: assignment_id, assigned_crew[], confirmed_start, confirmed_end,
//          site_address, customer_name, customer_email, customer_phone,
//          scope_notes, calendar_event_link, jobber_job_id, exception_flag=false
// ─────────────────────────────────────────────────────────────────

// ─────────────────────────────────────────────────────────────────
// AGENT 2: Notifications and CRM Sync Agent (3 parallel branches)
// ─────────────────────────────────────────────────────────────────

// Branch A: Slack crew notification
Slack.chat.postMessage({
  channel : 'U04XXALEX'   // DM to Alex Dolan
  text    : ':wrench: New job assigned: Roof Inspection - 14 Maple St | Tue 8 Jul, 8:00am-12:00pm | 14 Maple Street, Springfield | Customer: Jordan Riley | Notes: Full ridge and valley inspection, 2-storey access required | Calendar: https://calendar.google.com/event?eid=...'
}) -> slack_message_ts: '1720387200.000100'
Slack.chat.postMessage({ channel: 'U04XXSAM', text: '<same template>' })

// Branch B: Gmail customer confirmation
Gmail.users.messages.send({
  to      : 'jordan.riley@example.com'
  subject : 'Your booking is confirmed - 8 July 2025'
  body    : 'Hi Jordan, your booking is confirmed for Tuesday 8 July, 8:00am to 12:00pm at 14 Maple Street, Springfield. Your crew lead is Alex Dolan. On-the-day contact: +1 555 201 4400. Please reply to this email with any questions.'
}) -> gmail_message_id: 'msg_18f3a...'

// Branch C: HubSpot deal update
HubSpot.CRM.deals.search({ filter: { property: 'jobber_job_id', value: 'JB-00421' } })
  -> hubspot_deal_id: '88214399'
HubSpot.CRM.deals.update('88214399', {
  properties: { scheduled_job_date: '2025-07-08' }
}) -> hubspot_write_status: 200 OK

// ─────────────────────────────────────────────────────────────────
// COMPLETION: Update Supabase run record
// ─────────────────────────────────────────────────────────────────
Supabase.UPDATE scheduling_runs SET {
  status           : 'complete'
  slack_message_ts : '1720387200.000100'
  gmail_message_id : 'msg_18f3a...'
  hubspot_deal_id  : '88214399'
  completed_at     : '2025-07-07T14:31:05Z'
}

// ─────────────────────────────────────────────────────────────────
// EXCEPTION PATH (if no crew available at Step 2 above)
// ─────────────────────────────────────────────────────────────────
exception_flag = true
Slack.chat.postMessage({
  channel : 'SCHEDULER_USER_ID'   // stored in credential store
  text    : ':warning: No crew available for JB-00421 on 8 Jul 08:00-12:00. Manual assignment required.'
})
Supabase.INSERT scheduling_runs { job_id, assignment_id, status: 'exception', exception_reason, created_at }
// Agent halts. No calendar event created.
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform (tool decision to be confirmed at build start). One workflow per agent: 'Availability and Assignment Workflow' and 'Notifications and CRM Sync Workflow'. Both workflows share a single credential store with named entries for each connected tool. No credentials are hardcoded in workflow nodes.
Webhook configuration
Jobber webhook registered via POST /v2/webhooks targeting the automation platform's inbound HTTPS endpoint. Events subscribed: job.status_changed and job.updated. Webhook secret stored as an environment variable in the credential store. Every inbound request must be validated with HMAC-SHA256 before the workflow proceeds. The inbound URL must be a stable, publicly accessible HTTPS address (not localhost).
Templating approach
Gmail confirmation email and Slack crew notification built as named templates stored inside the automation platform's template library. Templates reference interpolation variables (customer_name, confirmed_start, site_address, etc.) so process owners can edit copy without touching workflow logic. HTML email template must be tested across Gmail and Outlook rendering before go-live.
Error logging
Supabase table named 'scheduling_runs' (columns: id, job_id, assignment_id, status, exception_flag, exception_reason, slack_message_ts, gmail_message_id, hubspot_deal_id, error_code, error_message, created_at, completed_at). On any agent failure, a row is inserted or updated with the error details and an immediate Slack DM alert is sent to the scheduler user ID stored in the credential store. The Supabase project URL and service role key are stored in the credential store, not in workflow nodes.
Testing approach
All integration testing is performed in sandbox mode first. Jobber sandbox environment used for webhook testing. Google Calendar test calendars (non-production crew accounts) used for availability queries and event creation during build. HubSpot sandbox portal used for deal update testing. Slack test workspace channel used for notification testing before switching to production tokens. Gmail test send address used before switching to the production sending address. Full production smoke test run with one live job before broader rollout.
Credential store entries required
JOBBER_WEBHOOK_SECRET, JOBBER_API_KEY, GOOGLE_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_SECRET, GOOGLE_OAUTH_REFRESH_TOKEN, GOOGLE_CALENDAR_CREW_IDS (JSON array), SLACK_BOT_TOKEN, SLACK_SCHEDULER_USER_ID, SLACK_CREW_CHANNEL_ID, GMAIL_OAUTH_CLIENT_ID, GMAIL_OAUTH_CLIENT_SECRET, GMAIL_OAUTH_REFRESH_TOKEN, GMAIL_SENDING_ADDRESS, HUBSPOT_PRIVATE_APP_TOKEN, HUBSPOT_JOBBER_JOB_ID_PROPERTY (custom property name), SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY
Estimated total build time
Availability and Assignment Agent: 16 hours. Notifications and CRM Sync Agent: 10 hours. End-to-end integration testing, QA, and rescheduling logic: 2 hours. Total: 28 hours across a 4-week delivery schedule.
Before build begins, the FullSpec team requires the following to be confirmed by the process owner: (1) Jobber plan is Grow or Connect tier to support job assignment writes via API, (2) Google Workspace plan is Business Starter or above with Calendar API enabled and domain-wide delegation approved, (3) HubSpot plan is Professional or above with custom properties and private app tokens available, (4) the custom HubSpot deal property 'jobber_job_id' is created and populated for existing deals, (5) all crew members are maintaining their Google Calendars accurately and have completed the calendar hygiene briefing, and (6) Slack user IDs for all crew members and the scheduler have been collected and are ready to load into the credential store. Build cannot begin until items 1 through 3 are verified. Contact the FullSpec team at support@gofullspec.com with any access queries.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Job / Project 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