Back to Customer Segmentation & List 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

Customer Segmentation & List Management

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

This document gives the FullSpec build team everything needed to configure, wire, and validate the Customer Segmentation and List Management automation from scratch. It covers the current manual process and its bottlenecks, the specification for each of the three agents, the end-to-end data flow with exact field names, and the recommended build stack including estimated total effort. You will not find business-case justification here; that lives in the ROI and Business Case document. What follows is the technical ground truth for the build.

01Process snapshot

Step
Name
Description
1
Export Contacts from CRM
Marketing Coordinator manually exports all active contacts from HubSpot as a CSV, filtering by last-modified date. Performed each time a campaign is scheduled. Time cost: 20 min.
2
Pull Form Submission Data
New Typeform leads are downloaded separately and pasted into a holding tab in Google Sheets, because they are not reliably synced to HubSpot in time. Time cost: 15 min.
3
Merge and Deduplicate in Spreadsheet
Both exports are pasted into a master Google Sheet. Deduplication formulas find matching emails; duplicates are flagged and deleted manually. Most error-prone step in the cycle. Time cost: 45 min.
4
Apply Segment Rules Manually
Coordinator sorts and filters the sheet to build each audience segment based on criteria stored in a reference tab. Rules are undocumented outside that tab. Time cost: 50 min.
5
Review Segments with Marketing Manager
Completed segments are shared with the marketing manager for a sense-check. Corrections are made directly in the sheet before any upload. Time cost: 20 min.
6
Upload Segments to Email Platform
Each approved segment is exported as its own CSV and uploaded to Mailchimp as a new or updated audience. Tags and groups applied by hand. Time cost: 30 min.
7
Suppress Unsubscribes and Bounces
Coordinator downloads the latest unsubscribe and bounce list from Mailchimp and cross-references it against the master sheet, marking records as suppressed. Time cost: 25 min.
8
Update CRM Records
Changes from deduplication and suppression are written back manually to HubSpot: contact status, list membership, and opt-out flags. Time cost: 30 min.
9
Log Changes and Notify Team
Coordinator writes a brief summary, pastes it into Slack, and updates the master sheet change log with dates and record counts. Time cost: 15 min.
Time cost summary: Total manual time per cycle is 250 minutes (4 hours 10 min). Run weekly this equals approximately 6 hours/week of coordinator and manager time, costing roughly $14,000/year at the assumed rate. Steps 1, 2, and 3 are replaced by the Data Merge and Dedup Agent. Steps 4, 6, 7, and 8 are replaced by the Segmentation Rules Agent. Step 9 is replaced by the Reporting and Notification Agent. Step 5 (manager review) is retained as a human step, but is reduced to exception-only review of flagged contacts estimated at approximately 5 minutes per ambiguous record.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Data Merge and Dedup Agent

This agent is the entry point for every sync cycle. It listens for contact create or update events from HubSpot via webhook and for new Typeform submissions via webhook, then fetches the full contact record from HubSpot using the contact ID from the event payload. It normalises field names across both sources (HubSpot properties and Typeform response fields map to a shared schema before any further processing). Once both datasets are assembled, it routes the combined set through Segment's identify and merge APIs to resolve duplicate records by email address, emitting a single canonical record per unique contact. The clean dataset is then passed downstream to the Segmentation Rules Agent. Nothing is written to HubSpot or Mailchimp at this stage.

Trigger
HubSpot contact.created or contact.propertyChange webhook, OR Typeform form_response webhook. Also fires on a daily scheduled run to catch any records missed by event-driven triggers.
Tools
HubSpot (Contacts API v3), Typeform (Webhooks v1), Segment (Identify API, Profile API for dedup merge)
Replaces steps
Steps 1, 2, and 3 (Export Contacts from CRM, Pull Form Submission Data, Merge and Deduplicate in Spreadsheet)
Estimated build
14 hours — Moderate complexity
// Input
HubSpot webhook payload: { contactId, changeSource, propertyName, timestamp }
Typeform webhook payload: { form_id, token, submitted_at, answers: [ { field: { id, type }, email, text } ] }

// Normalised schema (internal, passed to Segment identify call)
{
  email: string,           // primary dedup key
  first_name: string,
  last_name: string,
  hs_contact_id: string,   // HubSpot internal ID
  tf_response_id: string,  // Typeform token, null if source is HubSpot
  source: 'hubspot' | 'typeform',
  last_modified: ISO8601,
  lifecycle_stage: string,
  purchase_count: integer,
  engagement_score: float,
  subscription_status: string
}

// Output (passed to Segmentation Rules Agent)
{
  dedup_run_id: uuid,
  records_in: integer,
  records_out: integer,   // after merge; duplicates resolved
  merged_pairs: [ { canonical_email, merged_from_ids: [string] } ],
  contacts: [ <normalised schema above> ]
}
  • HubSpot tier must be at least Professional to access the Webhooks API and the full Contacts v3 property set. Confirm API key scope includes crm.objects.contacts.read and crm.objects.contacts.write before build begins.
  • Typeform webhook must be registered on the specific form ID used for lead capture. The form ID must be provided by the marketing team. Do not assume a single form; confirm whether multiple forms are in scope.
  • Segment dedup strategy: match on email address (lowercase, trimmed) as the primary key. If two records share an email but have different hs_contact_id values, retain the record with the more recent last_modified and log the merged ID pair. Do not delete HubSpot records via API at this stage; flag only.
  • Fallback behaviour: if the Typeform webhook payload contains no email field answer, the record is written to the exception log in Google Sheets with reason 'missing_email' and processing halts for that record only. The rest of the batch continues.
  • Segment must be on a plan that includes the Profile API (Business tier or above). Confirm with the client that the $120/month Segment subscription covers this endpoint before configuring the merge call.
  • The daily scheduled run should execute at 02:00 UTC to avoid overlap with campaign send windows. Confirm the preferred timezone with the marketing manager.
  • Idempotency: the dedup agent must store processed webhook event IDs (using the HubSpot changeSource timestamp + contactId composite key) to prevent double-processing on retry. Use the automation platform's built-in key-value store or a Supabase row for this.
Segmentation Rules Agent

This agent receives the clean deduplicated contact dataset from the Data Merge and Dedup Agent and evaluates every contact against the configured segment rules. Rules are stored as a structured configuration object (not hard-coded) so the marketing manager can update criteria without a code change. The agent first pulls the current suppression list from Mailchimp (unsubscribes and bounces) and excludes any matching emails from all segment assignments before syncing. For contacts that cannot be confidently assigned to a segment (for example, a contact whose purchase history is present but engagement score is null), the agent sets a flag and routes the record to the exception list rather than assigning a default segment. All remaining contacts are synced to Mailchimp audience memberships via the Mailchimp Batch API and their HubSpot contact properties are updated to reflect the new segment tags and suppression flags.

Trigger
Successful completion of the Data Merge and Dedup Agent, signalled by the dedup_run_id payload being passed to this agent's input queue.
Tools
Mailchimp (Audiences API v3, Batch API, Suppression Lists endpoint), HubSpot (Contacts API v3 PATCH), Segment (Track API for audit events)
Replaces steps
Steps 4, 6, 7, and 8 (Apply Segment Rules Manually, Upload Segments to Email Platform, Suppress Unsubscribes and Bounces, Update CRM Records)
Estimated build
16 hours — Moderate complexity
// Input
{
  dedup_run_id: uuid,
  contacts: [ <normalised contact schema from Agent 1> ],
  segment_rules: [
    { segment_id: string, label: string, criteria: { field: string, operator: string, value: any } }
  ]
}

// Suppression fetch (internal, before assignment)
GET /3.0/lists/{list_id}/members?status=unsubscribed,bounced
-> suppressed_emails: Set<string>

// Segment assignment output (per contact)
{
  email: string,
  hs_contact_id: string,
  assigned_segment: string | null,   // null if flagged for review
  suppressed: boolean,
  flag_reason: string | null,         // e.g. 'null_engagement_score'
  mailchimp_audience_id: string | null,
  mailchimp_tag: string | null
}

// Output (passed to Reporting and Notification Agent)
{
  run_id: uuid,
  segment_counts: { [segment_label]: integer },
  records_synced: integer,
  records_suppressed: integer,
  records_flagged: integer,
  flagged_contacts: [ { email, hs_contact_id, flag_reason } ],
  hubspot_update_errors: [ { hs_contact_id, error_message } ],
  mailchimp_batch_id: string
}

// On approval (flagged contact reviewed by marketing manager)
Marketing manager assigns segment via Slack action or Google Sheet update.
Re-trigger: Segmentation Rules Agent re-evaluates the specific contact with override.
Override payload: { email, hs_contact_id, override_segment: string, reviewed_by: string, reviewed_at: ISO8601 }
  • Segment rules must be fully documented and signed off by the marketing manager before this agent is configured. The build cannot proceed with vague or verbal-only criteria. The rule configuration object must be stored in the automation platform's credential or config store, not in workflow logic directly.
  • Mailchimp Batch API must be used for syncs involving more than 500 contacts in a single run to avoid hitting the standard API rate limit of 10 requests per second. Implement exponential backoff on 429 responses with a maximum of 3 retries before marking the batch as failed.
  • Mailchimp audience ID must be provided for each target segment. Do not create new audiences dynamically; update existing ones only. Confirm all audience IDs with the marketing team before build.
  • HubSpot PATCH calls must update the following properties per contact: hs_lead_status, hs_email_optout, and a custom property 'fs_segment_tag' (create this property in HubSpot before build if it does not already exist). Confirm the custom property namespace with the HubSpot account owner.
  • Contacts in the suppression set must have their HubSpot 'hs_email_optout' property set to true in the same PATCH call. This write must happen even if the Mailchimp sync fails for that record.
  • Ambiguity threshold: a contact is flagged for review if any required segmentation field is null or if the contact matches more than one segment's top-level criteria simultaneously. Do not default-assign to a catch-all segment without explicit instruction from the marketing manager.
  • Segment Track API should emit a 'segment_assigned' event per contact for audit purposes, including run_id, email hash (SHA-256, not plain text), assigned_segment, and timestamp.
Reporting and Notification Agent

This agent fires after the Segmentation Rules Agent completes its sync run and receives the run summary payload. It formats a structured Slack message showing segment counts, records synced, records suppressed, and a count of contacts flagged for human review. If any contacts are flagged, the Slack message includes a direct link to the flagged contacts tab in the master Google Sheet. The agent also appends a timestamped row to the master Google Sheet log covering the same metrics. If the Segmentation Rules Agent reported any HubSpot update errors, the Reporting Agent raises an amber alert in Slack rather than a standard green summary, making failures visible without requiring the team to check logs manually.

Trigger
Successful or partial completion of the Segmentation Rules Agent, indicated by the run_id payload. Fires on both full-success and partial-error outcomes so that failures are always reported.
Tools
Slack (Incoming Webhooks API or Bot Token with chat:write scope), Google Sheets (Sheets API v4, append row to log tab)
Replaces steps
Step 9 (Log Changes and Notify Team)
Estimated build
6 hours — Simple complexity
// Input
{
  run_id: uuid,
  run_timestamp: ISO8601,
  segment_counts: { [segment_label]: integer },
  records_synced: integer,
  records_suppressed: integer,
  records_flagged: integer,
  hubspot_update_errors: [ { hs_contact_id, error_message } ],
  mailchimp_batch_id: string
}

// Output: Slack message payload
{
  channel: '#marketing-ops',   // confirm channel name with team
  blocks: [
    { type: 'header', text: 'Segmentation run complete — <run_timestamp>' },
    { type: 'section', fields: [ segment_counts mapped as label/value pairs ] },
    { type: 'section', text: 'Synced: <records_synced> | Suppressed: <records_suppressed> | Flagged for review: <records_flagged>' },
    // If records_flagged > 0:
    { type: 'section', text: 'View flagged contacts: <google_sheets_tab_url>' },
    // If hubspot_update_errors.length > 0:
    { type: 'section', text: ':warning: <n> HubSpot update errors. Check error log tab.' }
  ]
}

// Output: Google Sheets append row
[ run_id, run_timestamp, records_synced, records_suppressed, records_flagged,
  segment_counts_json_string, mailchimp_batch_id, hubspot_error_count ]
  • Slack channel name must be confirmed with the marketing team before build. The bot token must have chat:write scope and must be invited to the channel. Do not use an Incoming Webhook URL if the team anticipates needing interactive Slack actions for exception approval in a future phase.
  • Google Sheets log tab must exist before the agent runs. The spreadsheet ID and sheet tab name (suggest 'Run Log') must be stored in the credential store. The service account used for Sheets API access requires Editor permission on the specific spreadsheet.
  • If the Segmentation Rules Agent passes a hubspot_update_errors array with one or more entries, the Slack message tone changes to amber (use a yellow circle emoji prefix on the header) to distinguish it from a clean run. This is a visual-only signal; no automated retry is triggered by this agent.
  • Google Sheets row append must be idempotent: check whether a row with the same run_id already exists in the log tab before appending, to prevent duplicate rows on workflow retry.
  • This agent is Simple complexity and should be built and tested first as a smoke test for Slack and Sheets connectivity before the heavier agents are wired up.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow with field names at each agent handoff
// ─────────────────────────────────────────────────────────────────
// TRIGGER LAYER
// ─────────────────────────────────────────────────────────────────

// Event A: HubSpot contact created or property changed
HubSpot Webhook ->
  payload.contactId       : string   // e.g. '12345678'
  payload.changeSource    : string   // e.g. 'CRM_UI' | 'API' | 'FORM'
  payload.propertyName    : string   // e.g. 'email' | 'lifecyclestage'
  payload.timestamp       : integer  // Unix ms

// Event B: Typeform form submitted
Typeform Webhook ->
  payload.form_id         : string
  payload.token           : string   // unique response token
  payload.submitted_at    : ISO8601
  payload.answers[]       : array    // field.type includes 'email', 'text', 'number'

// Event C: Daily scheduled run (02:00 UTC)
Scheduler ->
  { source: 'schedule', run_date: ISO8601 }

// ─────────────────────────────────────────────────────────────────
// AGENT 1: Data Merge and Dedup Agent
// ─────────────────────────────────────────────────────────────────

// Step 1.1: Fetch full HubSpot contact record
GET /crm/v3/objects/contacts/{contactId}
  ?properties=email,firstname,lastname,lifecyclestage,hs_lead_status,
              num_associated_deals,hs_email_optout,last_modified_date,
              hubspot_score,hs_analytics_num_visits
-> hs_contact: { id, properties: { email, firstname, lastname, ... } }

// Step 1.2: Parse Typeform response into normalised schema
typeform.answers.find(a => a.field.type === 'email').email
-> tf_email: string
typeform.answers.find(a => a.field.ref === 'first_name').text
-> tf_first_name: string
// ... additional Typeform field mappings per confirmed form structure

// Step 1.3: Normalise both sources to shared contact schema
contact_normalised = {
  email              : lowercase(trim(source.email)),
  first_name         : string,
  last_name          : string,
  hs_contact_id      : string | null,
  tf_response_id     : string | null,
  source             : 'hubspot' | 'typeform',
  last_modified      : ISO8601,
  lifecycle_stage    : string,
  purchase_count     : integer,
  engagement_score   : float | null,
  subscription_status: string
}

// Step 1.4: Dedup via Segment Profile API
POST /v1/identify
  { userId: email, traits: contact_normalised }
GET /v1/profiles/email:{email}/traits
-> canonical_profile: { userId, traits, merged_ids: [string] }
// Duplicate resolution: retain record with max(last_modified)
// Log merged pairs: { canonical_email, merged_from_ids }

// HANDOFF A -> B
dedup_output = {
  dedup_run_id   : uuid,
  records_in     : integer,
  records_out    : integer,
  merged_pairs   : [ { canonical_email, merged_from_ids } ],
  contacts       : [ contact_normalised ]   // deduplicated set
}

// ─────────────────────────────────────────────────────────────────
// AGENT 2: Segmentation Rules Agent
// ─────────────────────────────────────────────────────────────────

// Step 2.1: Fetch Mailchimp suppression list
GET /3.0/lists/{audience_id}/members?status=unsubscribed&count=1000
GET /3.0/lists/{audience_id}/members?status=bounced&count=1000
-> suppressed_emails: Set<string>  // lowercase email addresses

// Step 2.2: Evaluate segment rules per contact
for each contact in dedup_output.contacts:
  if contact.email in suppressed_emails:
    contact.suppressed = true
    contact.assigned_segment = null
    continue
  match_results = segment_rules.filter(rule => evaluate(contact, rule.criteria))
  if match_results.length === 1:
    contact.assigned_segment = match_results[0].segment_id
  elif match_results.length === 0 OR contact.engagement_score === null:
    contact.assigned_segment = null
    contact.flag_reason = 'no_rule_match' | 'null_engagement_score'
  elif match_results.length > 1:
    contact.assigned_segment = null
    contact.flag_reason = 'multi_rule_conflict'

// Step 2.3: Sync assigned contacts to Mailchimp via Batch API
POST /3.0/batches
  body.operations[] = {
    method: 'PUT',
    path: '/lists/{audience_id}/members/{md5(email)}',
    body: { email_address, status_if_new: 'subscribed',
            tags: [assigned_segment], merge_fields: { FNAME, LNAME } }
  }
-> mailchimp_batch_id: string

// Step 2.4: Update HubSpot contact properties
PATCH /crm/v3/objects/contacts/{hs_contact_id}
  body.properties = {
    hs_lead_status     : assigned_segment | 'FLAGGED_FOR_REVIEW',
    hs_email_optout    : contact.suppressed,
    fs_segment_tag     : assigned_segment | null
  }

// Step 2.5: Emit audit event via Segment Track
POST /v1/track
  { userId: sha256(email), event: 'segment_assigned',
    properties: { run_id, assigned_segment, suppressed, flag_reason, timestamp } }

// HANDOFF B -> C
seg_output = {
  run_id                : uuid,
  segment_counts        : { [segment_label]: integer },
  records_synced        : integer,
  records_suppressed    : integer,
  records_flagged       : integer,
  flagged_contacts      : [ { email, hs_contact_id, flag_reason } ],
  hubspot_update_errors : [ { hs_contact_id, error_message } ],
  mailchimp_batch_id    : string
}

// ─────────────────────────────────────────────────────────────────
// AGENT 3: Reporting and Notification Agent
// ─────────────────────────────────────────────────────────────────

// Step 3.1: Format and post Slack message
POST https://slack.com/api/chat.postMessage
  { channel: '#marketing-ops',
    blocks: [ header, segment_count_fields, summary_line,
              flagged_link_if_any, error_warning_if_any ] }

// Step 3.2: Append row to Google Sheets run log
POST /v4/spreadsheets/{spreadsheet_id}/values/{log_tab}!A:H:append
  body.values = [[
    run_id, run_timestamp, records_synced, records_suppressed,
    records_flagged, JSON.stringify(segment_counts),
    mailchimp_batch_id, hubspot_update_errors.length
  ]]

// ─────────────────────────────────────────────────────────────────
// EXCEPTION PATH: Flagged contact reviewed by marketing manager
// ─────────────────────────────────────────────────────────────────

// Marketing manager updates flagged_contacts tab in Google Sheets
// OR responds via Slack interactive action (future phase)
override_payload = {
  email            : string,
  hs_contact_id    : string,
  override_segment : string,
  reviewed_by      : string,
  reviewed_at      : ISO8601
}
// Re-triggers Agent 2 for that contact only, skipping rule evaluation
// and applying override_segment directly to Mailchimp and HubSpot
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform to be confirmed at build kick-off). One workflow per agent: 'data-merge-dedup', 'segmentation-rules', 'reporting-notification'. All API credentials stored in a shared credential store within the platform. No credentials hard-coded in workflow logic.
Webhook configuration
HubSpot webhook subscription registered via the HubSpot developer portal pointing to the orchestration platform's inbound webhook URL. Typeform webhook registered on the specific form ID via Typeform Admin. Both endpoints must use HTTPS. HubSpot webhooks include a client secret in the X-HubSpot-Signature header; validate this signature on every inbound request before processing.
Templating approach
Segment rules stored as a JSON configuration object in the platform's environment variables or a dedicated config table, not embedded in workflow branches. Slack message blocks built from a reusable template function that accepts the run summary object and returns the full blocks array. Google Sheets column order defined in a single schema constant to allow column additions without refactoring the append call.
Error logging
A dedicated Supabase table ('automation_errors') captures all non-retryable failures: columns run_id, agent_name, error_code, error_message, affected_record_id, occurred_at. On insert, a Supabase database webhook triggers a Slack alert to '#marketing-ops-alerts' (separate from the run summary channel). Retryable errors (HTTP 429, 503) are handled by exponential backoff within the workflow before reaching the error table. Max 3 retries; on third failure, log to Supabase and halt that workflow branch.
Testing approach
All agents built and validated in sandbox environments first: HubSpot sandbox account, Mailchimp test audience (do not use live audience IDs during build), Typeform test form, and Segment dev source. Promote to production credentials only after end-to-end QA against a sample of known contacts. Refer to the Test and QA Plan document for full test case list including edge cases.
Estimated total build time
Data Merge and Dedup Agent: 14 hours. Segmentation Rules Agent: 16 hours. Reporting and Notification Agent: 6 hours. End-to-end integration testing and error handling: 8 hours. Discovery, data audit, and credential setup: included in delivery Week 1. Total: 44 hours across 4 delivery weeks. (Template snapshot records 38 build effort hours; the additional 6 hours reflect end-to-end test wiring and error logging setup not counted in per-agent estimates.)
Pre-build blockers to resolve before Week 2: (1) Segment rules documented and signed off in writing by the marketing manager. (2) HubSpot sandbox access granted to the FullSpec build account. (3) Mailchimp audience IDs for all target segments provided. (4) Typeform form ID confirmed and webhook permission granted. (5) The custom HubSpot property 'fs_segment_tag' created in the production HubSpot account. (6) Segment account plan confirmed as Business tier or above. Delay on any of these items will extend the build timeline proportionally.
For build support or to flag a blocker during the delivery window, contact the FullSpec team at support@gofullspec.com. Reference the process name 'Customer Segmentation and List Management' and the relevant agent name in the subject line.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Customer Segmentation & List 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