Back to Sales Rep Activity Tracking

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

Sales Rep Activity Tracking

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

This document gives the FullSpec build team everything needed to implement the Sales Rep Activity Tracking automation from first credential to live deployment. It covers the current-state process map with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. The FullSpec team owns all build, configuration, and testing work described here. Your team's responsibilities are limited to providing tool credentials, confirming HubSpot property names, and approving the pilot output before full go-live.

01Process snapshot

Step
Name
Description
1
Complete Call or Meeting
Rep finishes a sales call or video meeting. No record is created automatically at this point. Time cost: 0 min (trigger only).
2
Open CRM Contact Record
Rep navigates to the relevant contact or deal in HubSpot and searches by name or company if the record is not already open. Time cost: 3 min.
3
Write Manual Call or Meeting Note
Rep types a free-form note covering conversation outcomes and next steps. Quality and length vary widely between reps. Time cost: 8 min. BOTTLENECK.
4
Log Activity Type and Outcome
Rep selects activity type from a dropdown and marks the outcome. Frequently skipped entirely when the rep is back-to-back. Time cost: 2 min. BOTTLENECK.
5
Log Outbound Emails Manually
Emails sent from Gmail are not automatically associated with HubSpot records unless the BCC or sidebar extension is used consistently, which many reps do not do. Time cost: 3 min.
6
Schedule Follow-Up Task
Rep creates a follow-up task or reminder in HubSpot or Google Calendar. A separate manual action that is frequently deferred or forgotten. Time cost: 4 min.
7
Manager Chases Missing Logs
Sales manager reviews the activity feed before a pipeline meeting and sends individual Slack messages to reps whose records are not updated. Time cost: 25 min. BOTTLENECK.
8
Reps Backfill Missing Records
Reps reconstruct activity from memory, email threads, or calendar entries and enter it retrospectively into HubSpot with incomplete detail. Time cost: 15 min.
9
Export Activity Report to Spreadsheet
Sales ops exports CRM activity data to Google Sheets to build a weekly activity summary for the leadership review. Time cost: 20 min.
10
Review and Distribute Activity Summary
Manager formats the spreadsheet, adds annotations, and shares it with leadership via Slack or email ahead of the weekly review. Time cost: 15 min.
Time cost summary: Total manual time per cycle is 95 minutes across all 10 steps. At approximately 120 logged activities per rep per month (roughly 30 per week), cumulative manual effort across logging, chasing, backfilling, and reporting reaches 5 hours per week per rep-and-manager pair. The automation replaces steps 2, 3, 4, 5, 6, 7, 9, and 10 entirely. Steps 1 and 8 remain with humans: step 1 is a real-world trigger event, and step 8 covers out-of-channel activities such as in-person visits without a calendar entry.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Activity Capture Agent

Monitors Gmail, Google Calendar, and Zoom for completed sales interactions. On each trigger event, the agent extracts structured information from the event metadata or Zoom transcript, matches the attendee or recipient to an existing HubSpot contact and deal, and writes a clean, standardised activity note to HubSpot without any rep input. This agent is the entry point for the entire workflow and must handle three distinct trigger paths reliably: calendar event completion, Zoom meeting end, and outbound Gmail send. The agent must also handle the no-match case gracefully, routing unmatched records to a triage list rather than silently failing.

Trigger
Google Calendar event ends (attendee email present) OR Zoom meeting concludes (cloud recording complete webhook) OR outbound Gmail sent to a HubSpot contact email address.
Tools
Gmail, Google Calendar, Zoom, HubSpot
Replaces steps
2, 3, 4, 5
Estimated build
10 hours — Complex
// Input
calendar_event: { event_id, title, start_time, end_time, attendees[email], description }
zoom_meeting:   { meeting_id, host_email, participant_emails[], transcript_text, duration_seconds }
gmail_message:  { message_id, from_email, to_emails[], subject, body_text, sent_at }

// Contact match lookup (HubSpot)
hubspot_contact: { contact_id, associated_deal_ids[], owner_rep_email }

// Output (on successful match)
hubspot_activity: {
  contact_id,
  deal_id,
  activity_type,       // 'Call' | 'Meeting' | 'Email'
  outcome,             // 'Connected' | 'Left Voicemail' | 'No Answer' | 'Sent'
  note_text,           // AI-structured summary: topics, outcomes, next_steps
  timestamp,           // ISO 8601 UTC
  source_signal        // 'calendar' | 'zoom' | 'gmail'
}

// On no-match
triage_record: { signal_type, attendee_email, timestamp, reason: 'contact_not_found' }
  • HubSpot tier must be Starter or above to access the Engagements API (v3) for writing activity logs. Confirm the account tier before build begins.
  • Gmail trigger requires the Gmail API with the 'gmail.readonly' OAuth scope and a push notification subscription via Pub/Sub or polling at no more than 60-second intervals to stay within rate limits.
  • Google Calendar trigger uses the Calendar API 'calendar.events.readonly' scope. Watch subscriptions expire after 7 days and must be auto-renewed by the orchestration layer.
  • Zoom trigger relies on the Zoom Webhook (Event Subscription) for the 'meeting.ended' event and a separate call to the Zoom Recordings API to retrieve the transcript. Zoom cloud recording with auto-transcription must be enabled on the account. If not enabled, the agent falls back to calendar metadata only and sets a flag 'transcript_available: false' on the HubSpot note.
  • Contact matching uses the primary email address only. Where multiple attendees are present, the agent matches against all non-host emails in sequence and associates the activity with the first matched HubSpot contact. Where no match is found, the record is written to the triage table rather than dropped.
  • Activity type mapping: calendar event maps to 'Meeting', Zoom meeting maps to 'Call', Gmail send maps to 'Email'. Outcome defaults to 'Connected' for completed meetings, 'Sent' for email, and must be overridable per deal stage if required.
  • Dedupe: before writing, the agent checks HubSpot for an existing engagement within a 5-minute window on the same contact and source to prevent double-logging if both calendar and Zoom signals fire for the same event.
  • Confirm HubSpot custom property names for 'activity_outcome' and any custom activity types before build. Do not assume default HubSpot field names match the client's configured schema.
Follow-Up and Task Agent

Reads the activity note produced by the Activity Capture Agent immediately after it is written to HubSpot. The agent scans the note text for explicit next-step phrases and date references, extracts or infers the due date, and creates a HubSpot task assigned to the owning rep against the matched deal. Where no specific date is found in the note, the task due date defaults to three business days from the activity timestamp. This agent runs synchronously after the Activity Capture Agent completes and does not require a separate scheduled trigger.

Trigger
Activity Capture Agent completes a new HubSpot activity log entry and passes the engagement_id and note_text downstream.
Tools
HubSpot, Google Calendar
Replaces steps
6
Estimated build
4 hours — Moderate
// Input
engagement_id:    string   // HubSpot engagement ID from Activity Capture Agent
note_text:        string   // Full structured note text
contact_id:       string
deal_id:          string
rep_owner_email:  string
activity_timestamp: string // ISO 8601 UTC

// NLP extraction
next_step_phrase: string | null  // e.g. 'Send proposal by Friday', 'Follow up next Tuesday'
extracted_date:   string | null  // ISO 8601 date parsed from phrase
fallback_date:    string         // activity_timestamp + 3 business days

// Output
hubspot_task: {
  task_type:   'TODO',
  subject:     string,   // derived from next_step_phrase or 'Follow up: [contact name]'
  due_date:    string,   // extracted_date ?? fallback_date
  body:        string,   // first 280 chars of note_text
  owner_id:    string,   // HubSpot owner ID mapped from rep_owner_email
  associations: { contact_id, deal_id }
}
  • Date extraction must handle natural-language formats including 'next Tuesday', 'end of week', 'in two weeks', and explicit dates such as '12 June'. Use a deterministic date parsing library rather than a general LLM call for this step to ensure predictability.
  • If note_text contains no detectable next-step phrase, the agent still creates a task with the fallback due date and subject 'Follow up: [contact first name]'. It must not silently skip task creation.
  • HubSpot task owner_id requires a lookup from rep_owner_email to HubSpot internal owner ID. Maintain a small static map of rep emails to owner IDs and flag any missing mapping to the error log rather than assigning to a default owner.
  • Google Calendar integration is optional at this stage: if the client wants tasks mirrored as calendar events on the rep's calendar, this can be added as a secondary action using the Calendar API 'calendar.events' write scope. Confirm scope of this feature before build.
  • Confirm whether HubSpot tasks should also trigger a HubSpot notification to the rep or whether Slack is the preferred notification channel. Avoid double-notifying.
Reporting and Nudge Agent

Runs on a scheduled daily trigger at 6:00 PM local time. The agent queries HubSpot for all activity logged during the current day across all rep owner IDs, appends a summary row per rep to the Google Sheets activity tracker, and then constructs and posts a formatted Slack digest to the designated sales channel. The digest includes per-rep activity counts for the day and flags any HubSpot contacts whose last-activity date is seven or more days in the past, marking them as stale. The stale-contact threshold is configurable via an environment variable.

Trigger
Scheduled daily trigger at 18:00 local time (configurable). No dependency on upstream agents at execution time; reads directly from HubSpot.
Tools
HubSpot, Google Sheets, Slack
Replaces steps
7, 9, 10
Estimated build
5 hours — Moderate
// Input
hubspot_query: {
  filter: { last_modified_date: today, object_type: 'engagements' },
  group_by: owner_id
}
stale_threshold_days: integer  // env var, default 7
rep_roster: [{ owner_id, rep_name, rep_email }]  // static config or HubSpot owners API

// Intermediate
daily_summary: [
  { rep_name, calls_logged, emails_logged, meetings_logged, tasks_created, total_activities }
]
stale_contacts: [{ contact_id, contact_name, last_activity_date, owner_rep_name }]

// Output: Google Sheets row (appended)
sheets_row: [date, rep_name, calls, emails, meetings, tasks, total, stale_flag_count]

// Output: Slack message (sales channel)
slack_block: {
  header: 'Daily Activity Digest — [date]',
  sections: [ per_rep_activity_counts ],
  stale_section: 'Contacts with no activity in 7+ days: [list]',
  footer: 'Powered by FullSpec | support@gofullspec.com'
}
  • Google Sheets append requires the Sheets API with 'spreadsheets.values.append' scope. The target spreadsheet ID and sheet tab name must be confirmed and stored in the credential store before build.
  • Slack posting uses an incoming webhook URL or a Bot Token with 'chat:write' scope targeting a specific channel ID. Confirm the channel name and whether the Bot Token or webhook approach is preferred for the client's Slack workspace tier.
  • The stale-contact query must exclude contacts in deal stages marked as 'Closed Won' or 'Closed Lost' to avoid flagging completed deals. Confirm the exact HubSpot deal stage names and IDs before implementing the filter.
  • If zero activities are logged for a rep on a given day (for example, during a leave day), the agent still appends a row with zero counts rather than skipping the rep. This ensures the Sheets tracker has a complete daily record.
  • The Slack digest is sent at 6:00 PM rather than 8:00 AM as originally scoped in the automated canvas. Confirm preferred send time with the sales manager before finalising the trigger schedule.
  • HubSpot Engagements API (v3) rate limit is 150 requests per 10 seconds. The daily batch query should use the search endpoint with pagination rather than per-contact polling to stay within limits.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow — Sales Rep Activity Tracking
// ─────────────────────────────────────────────────────────────────────
// TRIGGER LAYER — three parallel entry points
// ─────────────────────────────────────────────────────────────────────

TRIGGER_A: Google Calendar API (push notification)
  event.status == 'confirmed' AND event.end_time <= NOW()
  fields: event_id, title, start_time, end_time, attendees[email], description

TRIGGER_B: Zoom Webhook 'meeting.ended'
  fields: meeting_id, host_email, participant_emails[], duration_seconds
  → follow-up call: Zoom Recordings API GET /v2/meetings/{meetingId}/recordings
  fields appended: transcript_vtt_url, recording_files[file_type='TRANSCRIPT']

TRIGGER_C: Gmail API (Pub/Sub push or polling, max 60s interval)
  filter: from == rep_email AND to IN hubspot_contact_emails
  fields: message_id, from_email, to_emails[], subject, body_text, sent_at

// ─────────────────────────────────────────────────────────────────────
// AGENT 1: Activity Capture Agent
// ─────────────────────────────────────────────────────────────────────

STEP 1.1 — Contact match
  input:  attendee_email | gmail_recipient | zoom_participant_email
  action: HubSpot Contacts API GET /crm/v3/objects/contacts?email={email}
  output: contact_id (string) | null
  on null: write to triage_table { signal_type, email, timestamp, reason:'contact_not_found' }
           → HALT this branch; no further processing

STEP 1.2 — Deal association lookup
  input:  contact_id
  action: HubSpot Associations API GET /crm/v3/objects/contacts/{id}/associations/deals
  output: deal_ids[] (use first open deal by create_date DESC)
          rep_owner_id (from deal.properties.hubspot_owner_id)

STEP 1.3 — Dedupe check
  action: HubSpot Engagements search for existing engagement on contact_id
          within window: [NOW() - 5min, NOW() + 5min], type matching signal source
  on duplicate found: → HALT; log 'duplicate_skipped' to error_log

STEP 1.4 — Note generation
  input (calendar path):  event.title, event.description, event.duration, attendees[]
  input (zoom path):      transcript_text (parsed from VTT), meeting.duration_seconds
  input (gmail path):     email.subject, email.body_text (truncated to 4000 chars)
  output: structured_note {
    activity_type:  'Meeting' | 'Call' | 'Email'
    outcome:        'Connected' | 'Left Voicemail' | 'Sent'
    summary:        string  // 2-4 sentences: topics covered, decisions made
    next_steps:     string  // extracted commitments and follow-up intent
    transcript_available: boolean
  }

STEP 1.5 — Write HubSpot engagement
  action: HubSpot Engagements API POST /crm/v3/objects/engagements
  payload: { type, outcome, timestamp, note_text, associations:{ contact_id, deal_id } }
  output: engagement_id (string) → passed to Agent 2

// ─────────────────────────────────────────────────────────────────────
// AGENT 2: Follow-Up and Task Agent
// ─────────────────────────────────────────────────────────────────────

STEP 2.1 — Next-step extraction
  input:  structured_note.next_steps (string)
  action: date parser (deterministic NLP library, not LLM)
  output: extracted_date (ISO 8601) | null
          fallback_date = activity_timestamp + 3 business days
          task_subject  = next_steps phrase | 'Follow up: {contact.firstname}'

STEP 2.2 — Owner ID resolution
  input:  rep_owner_email
  action: static map lookup { email -> hubspot_owner_id }
  on miss: log 'owner_not_found' to error_log; skip task creation

STEP 2.3 — Create HubSpot task
  action: HubSpot Tasks API POST /crm/v3/objects/tasks
  payload: {
    hs_task_type:    'TODO',
    hs_task_subject: task_subject,
    hs_timestamp:    due_date (ISO 8601),
    hs_task_body:    note_text[0:280],
    hubspot_owner_id: owner_id,
    associations:    { contact_id, deal_id }
  }
  output: task_id (string)

// ─────────────────────────────────────────────────────────────────────
// AGENT 3: Reporting and Nudge Agent (scheduled 18:00 daily)
// ───────────────────────────────────────────────────────────────���─────

STEP 3.1 — HubSpot daily activity query
  action: HubSpot Engagements Search API POST /crm/v3/objects/engagements/search
  filter: { last_modified_date: { gte: TODAY_00:00, lte: TODAY_23:59 } }
  group results by: hubspot_owner_id
  output: daily_summary[] { owner_id, calls, emails, meetings, tasks }

STEP 3.2 — Stale contact query
  action: HubSpot Contacts Search, filter { notes_last_activity_date: { lte: NOW()-7d } }
          exclude deal_stage IN ['closedwon','closedlost']
  output: stale_contacts[] { contact_id, firstname, lastname, owner_id, last_activity_date }

STEP 3.3 — Append Google Sheets row
  action: Sheets API POST /v4/spreadsheets/{spreadsheetId}/values/{range}:append
  row:    [date, rep_name, calls, emails, meetings, tasks, total, stale_count]
  output: updated_range (confirmation string)

STEP 3.4 — Post Slack digest
  action: Slack API POST /chat.postMessage OR incoming webhook URL
  channel: sales_channel_id (env var)
  payload: Block Kit message {
    header block:  'Daily Activity Digest — {date}'
    section blocks: one per rep { rep_name, calls, emails, meetings, tasks, total }
    stale block:   'Contacts needing attention (7+ days): {stale_contact_list}'
    context block: 'Powered by FullSpec | support@gofullspec.com'
  }
  output: slack_message_ts (timestamp, stored for audit log)
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform running one workflow per agent (three workflows total). Each workflow is isolated with its own error handler and retry logic. All three workflows share a single credential store so that API tokens for HubSpot, Gmail, Google Calendar, Zoom, Google Sheets, and Slack are managed centrally and rotated in one place without touching individual workflow configs.
Webhook configuration
Zoom meeting.ended events are received via a publicly accessible HTTPS webhook endpoint registered in the Zoom Marketplace app settings. Google Calendar uses a push notification channel registered via the Calendar API watch endpoint, auto-renewed every 6 days by a maintenance sub-workflow. Gmail uses either a Pub/Sub push subscription (preferred) or a polling step every 60 seconds if Pub/Sub setup is not available on the client's Google Workspace tier. All webhook endpoints must be secured with a shared secret header validated on receipt.
Templating approach
HubSpot note text is generated using a structured prompt template with clearly delimited sections: Meeting Type, Attendees, Key Topics, Outcomes, and Next Steps. The template is stored as a versioned string in the credential store so it can be updated without redeploying the workflow. Slack Block Kit message structure is templated in a reusable JSON schema within the Reporting workflow to allow digest layout changes without code edits.
Error logging
All agent errors, triage records, dedupe skips, and owner-not-found events are written to a Supabase table (schema: id, workflow_name, error_type, payload_json, created_at). A secondary alert step sends a Slack direct message to the FullSpec monitoring account at support@gofullspec.com for any error_type classified as 'critical' (for example, HubSpot write failure or Zoom transcript fetch failure). Non-critical events such as dedupe skips are logged only, not alerted.
Testing approach
All three agents are built and validated against sandbox environments first. HubSpot sandbox account is used for engagement and task writes. Zoom test meetings are conducted in the developer's Zoom account with cloud recording enabled. Gmail and Calendar triggers are tested using a dedicated test rep email address. Slack digest is posted to a private test channel before switching to the live sales channel. A one-week live pilot with two reps runs before full-team go-live, with FullSpec monitoring error logs daily during the pilot window.
Estimated total build time
Activity Capture Agent: 10 hours. Follow-Up and Task Agent: 4 hours. Reporting and Nudge Agent: 5 hours. End-to-end integration testing and pilot monitoring: 3 hours. Total: 22 hours. This matches the scoped build effort in the confirmed process snapshot.
Three build prerequisites must be confirmed before any workflow is started: (1) HubSpot account tier is Starter or above with Engagements API access enabled. (2) Zoom account has cloud recording and auto-transcription active on the host account. (3) The HubSpot property names for activity type, outcome, and any custom fields have been shared by the client. Missing any of these will block Agent 1 from completing. Contact support@gofullspec.com if any prerequisite cannot be confirmed within the first two days of build.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Sales Rep Activity Tracking.

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