Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Lead Nurture Sequence
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the single technical reference for the FullSpec team building the Lead Nurture Sequence automation. It covers the current-state process map with identified bottlenecks, full specifications for each of the three agents (Lead Qualification, Sequence Messaging, and Sales Alert), the end-to-end data flow with exact field names, and the recommended build stack. The FullSpec team owns everything described here. The client-side responsibility is limited to confirming HubSpot field hygiene, approving email templates before go-live, and reviewing flagged edge-case leads during the pilot period.
01Process snapshot
02Agent specifications
Reviews incoming HubSpot contact data and form submission fields to score the lead against the ideal customer profile and apply the correct qualification tag. The agent operates without human input for standard lead types and flags edge cases for rep review. Scoring uses a weighted model pulled from a Google Sheets reference table containing ICP criteria such as company size, lead source, enquiry type, and budget indicators. The output tag is written directly to a custom HubSpot contact property and determines whether the Sequence Messaging Agent fires or the record is held for manual review.
// Input hubspot.contact.id : string hubspot.contact.email : string hubspot.contact.firstname : string hubspot.contact.lastname : string hubspot.contact.hs_lead_source : string hubspot.contact.enquiry_topic : string // custom property hubspot.contact.company : string hubspot.contact.jobtitle : string google_sheets.icp_criteria : object // scoring weights per field // Output hubspot.contact.qualification_score : integer // 0-100 hubspot.contact.qualification_status : enum // 'qualified' | 'edge_case' | 'disqualified' hubspot.contact.lifecycle_stage : string // updated to 'marketingqualifiedlead' if qualified hubspot.contact.nurture_flag : boolean // true if disqualified but retainable // On edge case internal.flag_for_rep_review : true internal.flag_reason : string // reason code from scoring model
- HubSpot tier must be Sales Hub Starter or above. The private app token (not legacy API key) must be provisioned with contacts read/write and CRM object permissions before build begins.
- The Google Sheets scoring table must be named 'ICP_Criteria' on a tab called 'Scoring' and must contain columns: field_name, weight, pass_value, fail_value. The FullSpec team will supply a starter template; the client confirms actual ICP values before the agent is wired.
- Qualification score threshold for 'qualified' status is set at 60 by default. This must be confirmed with the process owner before go-live and is expected to be recalibrated after the first 30 days of live data.
- If any of the core personalisation fields (hs_lead_source, enquiry_topic, firstname) are blank, the agent must flag the record as 'edge_case' rather than attempt to score on partial data.
- Dedupe check: before writing the qualification tag, the agent queries HubSpot for existing contacts with the same email address. If a duplicate is found, it merges the new record into the existing one and does not trigger the Sequence Messaging Agent again.
- The custom HubSpot property 'qualification_score' (type: number) and 'qualification_status' (type: enumeration) must be created in HubSpot before deployment. Confirm creation during the stack audit in week 1.
Generates and sends personalised follow-up emails at timed intervals using the lead's contact data, source channel, and enquiry details pulled from HubSpot. The agent sends three emails: a welcome email immediately on qualification, a second follow-up on day 2, and a third on day 4. It polls for Calendly booking events and inbound Gmail replies after each send. If a booking or positive reply is detected, the sequence stops and the Sales Alert Agent is triggered. If three touchpoints complete with no response, the contact is tagged as long-term nurture and removed from the active sequence.
// Input (per send cycle) hubspot.contact.id : string hubspot.contact.firstname : string hubspot.contact.email : string hubspot.contact.hs_lead_source : string hubspot.contact.enquiry_topic : string hubspot.contact.sequence_step : integer // custom property: 0=not started, 1=sent, 2=sent, 3=sent hubspot.contact.last_touch_date : datetime // custom property calendly.booking_events : array // filtered by contact email gmail.thread_replies : array // inbound replies on sequence thread // Output (after each send) gmail.sent_message_id : string hubspot.contact.sequence_step : integer // incremented hubspot.contact.last_touch_date : datetime // updated to now() hubspot.contact.lifecycle_stage : string // updated to 'opportunity' on booking detected hubspot.contact.nurture_flag : boolean // set true after step 3 with no response hubspot.engagement.email_log : object // written via HubSpot Engagements API // On booking or reply detected internal.sequence_halt : true internal.halt_reason : enum // 'booking_detected' | 'reply_detected' internal.trigger_sales_alert : true
- Gmail must be connected via OAuth 2.0 using the sending rep's Google Workspace account. Required scopes: gmail.send, gmail.readonly (for reply detection on the thread). A service account with domain-wide delegation is preferred for multi-rep teams.
- Three email templates (Touch 1, Touch 2, Touch 3) must be written, reviewed, and approved by the process owner before any send goes live. Templates use Handlebars-style merge tags: {{firstname}}, {{enquiry_topic}}, {{hs_lead_source}}, {{calendly_link}}.
- The Calendly connection uses the Calendly v2 API (Bearer token). The agent polls the /scheduled_events endpoint filtered by invitee email. Webhook-based detection is preferred if the Calendly plan supports outgoing webhooks (Professional tier or above).
- Sequence timing: Touch 1 fires immediately on trigger. Touch 2 fires after 2 business days (48 hours excluding weekends). Touch 3 fires after 4 business days from Touch 1. Business day logic must account for the client's local timezone, confirmed during stack audit.
- The custom HubSpot property 'sequence_step' (type: number) and 'last_touch_date' (type: date) must be created before deployment. Email logging must use the HubSpot Engagements API v1 (type: EMAIL) to ensure activity appears in the contact timeline.
- Reply detection uses Gmail thread ID stored after Touch 1 send. The agent checks for replies from the contact's email address on that thread ID. If a reply exists, the sequence halts regardless of content. Human rep reads and responds to the actual content.
- Fallback: if Gmail send fails (rate limit or auth error), the error is logged to the Supabase error table with contact_id, step, timestamp, and error_code. The orchestration layer retries once after 15 minutes before alerting via Slack.
Watches for positive signals passed from the Sequence Messaging Agent, specifically a Calendly booking confirmation or a reply detection flag, and posts a formatted notification to the Slack sales channel. The message includes the lead's name, company, enquiry topic, meeting time (if a booking), and a direct link to the HubSpot contact record. This agent replaces the informal, inconsistent manual Slack message that reps currently send. It fires once per trigger event and does not repeat unless a new booking or reply is detected.
// Input internal.halt_reason : enum // 'booking_detected' | 'reply_detected' hubspot.contact.id : string hubspot.contact.firstname : string hubspot.contact.lastname : string hubspot.contact.company : string hubspot.contact.enquiry_topic : string hubspot.contact.hubspot_owner_id : string calendly.event.start_time : datetime // null if halt_reason = reply_detected calendly.event.event_type_name : string // null if halt_reason = reply_detected hubspot.contact.record_url : string // constructed from portal_id + contact_id // Output slack.message.channel : string // e.g. #sales-alerts, confirmed pre-build slack.message.text : string // formatted Block Kit message slack.message.blocks : array // lead name, company, meeting time or reply flag, CRM link slack.message.ts : string // message timestamp returned by Slack API
- Slack connection requires a Slack App installed to the client's workspace with the chat:write OAuth scope and membership in the target channel. The bot must be invited to the channel manually before deployment.
- The target Slack channel name must be confirmed before build. Default assumption is #sales-alerts. The channel ID (not name) must be stored in the credential store as SLACK_CHANNEL_ID to avoid breakage if the channel is renamed.
- Message format uses Slack Block Kit with two sections: (1) a header block identifying the trigger type ('New Booking' or 'Reply Detected') and (2) a fields block containing lead name, company, enquiry topic, meeting time (if applicable), and a button linking to the HubSpot record.
- If the Slack send fails, the error is logged to the Supabase error table and a fallback plain-text email is sent to the rep's Gmail address as a safety net.
- HubSpot portal ID must be stored as HUBSPOT_PORTAL_ID in the credential store to construct the record URL correctly: https://app.hubspot.com/contacts/{portal_id}/contact/{contact_id}.
03End-to-end data flow
// ====================================================
// LEAD NURTURE SEQUENCE: END-TO-END DATA FLOW
// FullSpec Build Reference | Lead Nurture Sequence
// ====================================================
// TRIGGER
// HubSpot webhook fires on contact.creation where lifecycle_stage = 'lead'
EVENT: hubspot.contact.created
-> contact.id : string // e.g. '12345678'
-> contact.email : string
-> contact.firstname : string
-> contact.lastname : string
-> contact.company : string
-> contact.jobtitle : string
-> contact.hs_lead_source : string // e.g. 'Organic Search', 'Paid Social'
-> contact.enquiry_topic : string // custom property, must be non-null
-> contact.lifecycle_stage : 'lead'
// ====================================================
// AGENT 1: Lead Qualification Agent
// ====================================================
INPUT: hubspot.contact.* + google_sheets.icp_criteria (tab: 'Scoring')
STEP 1.1: fetch ICP scoring table from Google Sheets
sheets.spreadsheet_id : SHEETS_ICP_SPREADSHEET_ID // from credential store
sheets.range : 'Scoring!A:E'
returns : icp_criteria[] // field_name, weight, pass_value, fail_value
STEP 1.2: score contact against ICP criteria
score fields : hs_lead_source, enquiry_topic, jobtitle, company
output : qualification_score // integer 0-100
threshold : >= 60 -> 'qualified' | < 60 -> 'disqualified' | missing fields -> 'edge_case'
STEP 1.3: dedupe check
GET /crm/v3/objects/contacts?email={contact.email}
if duplicate found -> merge, halt sequence for this contact
STEP 1.4: write qualification result to HubSpot
PATCH /crm/v3/objects/contacts/{contact.id}
body:
contact.qualification_score : integer
contact.qualification_status : 'qualified' | 'edge_case' | 'disqualified'
contact.nurture_flag : boolean
contact.lifecycle_stage : 'marketingqualifiedlead' // if qualified
// AGENT 1 -> AGENT 2 HANDOFF
// If qualification_status = 'qualified', pass contact.id to Sequence Messaging Agent
// If qualification_status = 'edge_case', flag for rep review and halt
// If qualification_status = 'disqualified', set nurture_flag = true and halt active sequence
// ====================================================
// AGENT 2: Sequence Messaging Agent
// ====================================================
INPUT: hubspot.contact.id, hubspot.contact.* (refetched), sequence state
STEP 2.1: send Touch 1 (immediate)
gmail.send:
to : contact.email
from : GMAIL_SENDING_ADDRESS // from credential store
subject : rendered from template_touch_1.subject
body : rendered from template_touch_1.body
merge_tags : {{firstname}}, {{enquiry_topic}}, {{hs_lead_source}}, {{calendly_link}}
calendly_link : CALENDLY_BOOKING_URL // from credential store
returns:
gmail.message_id : string // stored as contact.sequence_thread_id
gmail.thread_id : string // stored for reply detection
STEP 2.2: log Touch 1 engagement in HubSpot
POST /engagements/v1/engagements
body:
engagement.type : 'EMAIL'
engagement.timestamp : now()
metadata.subject : template_touch_1.subject
associations.contactIds : [contact.id]
PATCH /crm/v3/objects/contacts/{contact.id}
contact.sequence_step : 1
contact.last_touch_date : now()
contact.lifecycle_stage : 'contacted' // HubSpot stage
STEP 2.3: scheduled poll (every 24h for contacts with sequence_step in [1,2])
CHECK a: Calendly booking
GET /scheduled_events?invitee_email={contact.email}
if event.start_time exists and status = 'active':
set halt_reason = 'booking_detected'
set calendly.event.start_time, calendly.event.event_type_name
-> trigger Sales Alert Agent
CHECK b: Gmail reply on thread
GET /gmail/v1/users/me/threads/{thread_id}
if thread.messages count > sent_message_count:
set halt_reason = 'reply_detected'
-> trigger Sales Alert Agent
STEP 2.4: send Touch 2 (day 2, if no halt)
same gmail.send flow as 2.1 using template_touch_2
log engagement, update contact.sequence_step = 2, contact.last_touch_date
STEP 2.5: send Touch 3 (day 4, if no halt)
same gmail.send flow as 2.1 using template_touch_3
log engagement, update contact.sequence_step = 3, contact.last_touch_date
STEP 2.6: post-Touch 3 no-response action
PATCH /crm/v3/objects/contacts/{contact.id}
contact.nurture_flag : true
contact.qualification_status : 'disqualified' // or 'nurture_only'
contact.lifecycle_stage : 'lead' // reset to long-term nurture pool
// AGENT 2 -> AGENT 3 HANDOFF
// internal.trigger_sales_alert = true
// payload: contact.id, contact.firstname, contact.lastname, contact.company,
// contact.enquiry_topic, halt_reason, calendly.event.start_time (if booking),
// hubspot.contact.record_url
// ====================================================
// AGENT 3: Sales Alert Agent
// ====================================================
INPUT: handoff payload from Agent 2
STEP 3.1: construct HubSpot record URL
url = 'https://app.hubspot.com/contacts/' + HUBSPOT_PORTAL_ID + '/contact/' + contact.id
STEP 3.2: build Slack Block Kit message
header block : halt_reason = 'booking_detected' -> 'New Booking Confirmed'
halt_reason = 'reply_detected' -> 'Lead Replied to Sequence'
fields block :
'Lead' : contact.firstname + ' ' + contact.lastname
'Company' : contact.company
'Enquiry' : contact.enquiry_topic
'Meeting' : calendly.event.start_time (formatted) | 'N/A'
button block : 'View in HubSpot' -> hubspot.contact.record_url
STEP 3.3: post to Slack
POST /api/chat.postMessage
body:
channel : SLACK_CHANNEL_ID // from credential store
blocks : constructed Block Kit payload
returns:
slack.message.ts : string
// ====================================================
// ERROR HANDLING (all agents)
// ====================================================
// Any unhandled exception:
// -> INSERT INTO supabase.errors (contact_id, agent_name, step, error_code, timestamp)
// -> retry once after 15 minutes
// -> if second failure: POST to Slack #automation-errors channel
// ====================================================
// END OF FLOW04Recommended build stack
More documents for this process
Every document generated for Lead Nurture Sequence.