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
Post-Meeting Recap & Follow-up
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for the FullSpec team building the Post-Meeting Recap and Follow-up automation. It covers the current-state process map, all agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything here is derived from the confirmed discovery session and tool-access checks completed with Jordan Miles and Priya Shah. The FullSpec team owns all build, integration, and testing work described below. Your team's responsibility is limited to confirming credentials, reviewing pilot output, and approving the go-live checklist.
01Process snapshot
02Agent specifications
Monitors Google Calendar for events tagged with a sales label and polls for meeting-end status via the Zoom webhook. When a qualifying sales call reaches its scheduled end time (or the Zoom session closes), the agent filters out internal-only meetings using attendee email domain rules, then packages the calendar metadata and transcript reference into a structured payload and passes it downstream. No AI inference occurs in this agent. It is purely an event-detection and filtering layer. Estimated build time: 5 hours. Complexity: Moderate.
// Input
calendar_event_id: string // Google Calendar event UID
event_title: string // e.g. 'Discovery Call - Acme Corp'
event_end_time: ISO8601 // scheduled end timestamp
attendees: [{ email, name, domain }] // all calendar attendees
zoom_meeting_id: string | null // null if not a Zoom call
// Processing
1. Filter: exclude attendees where domain === internal_domain
2. If external_attendees.length === 0 -> abort, log 'internal-only call'
3. Call Fireflies.ai GET /transcripts?title={event_title}&date={event_date}
4. If transcript not yet ready -> retry after 3 min (max 3 retries)
5. If transcript unavailable after retries -> set fallback_mode = true
// Output
meeting_record: {
calendar_event_id: string,
meeting_title: string,
meeting_date: ISO8601,
attendees: [{ email, name }],
transcript_url: string | null,
transcript_text: string | null,
fallback_mode: boolean,
fireflies_meeting_id: string | null
}- Google Calendar API requires OAuth 2.0 with scope https://www.googleapis.com/auth/calendar.readonly. The service account must be granted domain-wide delegation if the automation runs under a shared credential rather than each rep's individual account.
- Zoom webhook must be registered in the Zoom App Marketplace under the account owner's credentials. The meeting.ended event payload includes meeting_id and end_time. Verify that host_id can be mapped to a rep email before the pilot.
- Fireflies.ai transcript matching uses meeting title and participant email as a composite key. If two meetings share the same title on the same day, the agent must select by closest start_time match and log the ambiguity.
- Fallback mode (no transcript available) must produce a calendar-only summary containing attendees, meeting title, and date. This reduced payload is passed forward and the Recap Drafting Agent is flagged to use the fallback prompt template.
- Internal domain list (e.g. @[YourCompany.com]) must be stored as a configurable environment variable, not hardcoded, so the sales manager can update it without a code change.
- Confirm with Jordan Miles whether Fireflies.ai is already added as a bot participant to Zoom, or whether the Zoom native integration in Fireflies.ai is in use. If neither is configured, the agent cannot fetch transcripts and the build cannot proceed past this point.
Receives the meeting record from the Meeting Trigger Agent and calls an LLM to extract key themes, objections, agreements, and named next steps from the transcript. Produces two outputs: a personalised recap email draft saved to the rep's Gmail drafts folder, and a structured Notion summary block appended to the deal's Notion page. If fallback mode is active (no transcript), a shorter template is used that acknowledges the meeting and confirms next steps only. A Slack nudge is sent to the rep immediately after the Gmail draft is created. Estimated build time: 9 hours. Complexity: Moderate.
// Input
meeting_record: {
meeting_title: string,
meeting_date: ISO8601,
attendees: [{ email, name }],
transcript_text: string | null,
fallback_mode: boolean,
rep_email: string // derived from calendar organiser
}
// LLM extraction (prompt fields)
extracted_pain_points: string[]
extracted_agreements: string[]
extracted_next_steps: [{ action, owner, due_date_hint }]
extracted_decision_makers: string[]
deal_stage_keyword: string | null // e.g. 'proposal', 'demo', 'closed'
// Output
gmail_draft_id: string // ID of created Gmail draft
gmail_draft_subject: string // e.g. 'Follow-up: Discovery Call - Acme Corp'
notion_block_id: string // ID of appended Notion block
slack_nudge_sent: boolean
recap_payload: {
pain_points: string[],
agreements: string[],
next_steps: [{ action, owner, due_date_hint }],
deal_stage_keyword: string | null,
meeting_title: string,
meeting_date: ISO8601,
attendees: [{ email, name }]
}- Gmail API requires OAuth 2.0 scope https://www.googleapis.com/auth/gmail.compose for draft creation. The draft must be addressed to all external attendees (To field) with the rep as sender. Do not use https://www.googleapis.com/auth/gmail.send at this stage as the rep retains send authority.
- Notion API integration must be connected to the workspace where deal pages live. The page ID for each deal must be resolved by querying the Notion database filtered on meeting_title or a linked HubSpot deal ID. Confirm database ID and property names with Jordan Miles before build.
- The LLM prompt must include a phrasing-style note if historical rep emails are available. If not, use a neutral professional tone as default. The prompt must be version-controlled and stored as a named template in the automation platform's credential or config store so it can be updated without redeploying.
- Slack nudge must be sent to the rep's direct message channel, not a shared channel. Use the Slack users.lookupByEmail method to resolve rep_email to a Slack user ID. If the user is not found, fall back to posting in the configured sales channel and log a warning.
- For fallback_mode === true, the Gmail draft body must use the fallback template and must include a visible note at the top of the draft: '[Auto-draft: no transcript available. Please review before sending.]'
- Recap quality gate: if extracted_next_steps is empty and extracted_agreements is empty, the agent must flag the draft with a subject-line prefix '[Review needed]' and log the meeting ID to the error table before sending the Slack nudge.
Receives the recap payload from the Recap Drafting Agent and writes structured data to HubSpot. Updates the deal record with the meeting outcome summary, last-contact date, and deal stage if a stage keyword was detected. Creates dated follow-up tasks for each extracted action item and assigns them to the rep. For deals above the configured value threshold, posts a formatted Slack message to the sales channel tagging both the rep and the sales manager. Estimated build time: 8 hours. Complexity: Moderate.
// Input
recap_payload: {
pain_points: string[],
agreements: string[],
next_steps: [{ action, owner, due_date_hint }],
deal_stage_keyword: string | null,
meeting_title: string,
meeting_date: ISO8601,
attendees: [{ email, name }]
}
rep_email: string
hubspot_deal_id: string // resolved by contact email lookup
deal_value_usd: number // fetched from HubSpot deal properties
slack_threshold_usd: number // env variable, e.g. 5000
// HubSpot operations
PATCH /crm/v3/objects/deals/{deal_id}
hs_lastmodifieddate: now(),
hs_deal_stage: mapped_stage_id | unchanged,
meeting_outcome_notes: formatted_summary_string
POST /crm/v3/objects/notes
body: full recap text,
associations: [deal_id, contact_ids]
POST /crm/v3/objects/tasks (one per next_step)
subject: action string,
hs_task_body: detail string,
hs_timestamp: due_date_resolved,
associations: [deal_id, rep_owner_id]
// Slack (conditional: deal_value_usd >= slack_threshold_usd)
POST /chat.postMessage
channel: sales_channel_id,
text: formatted deal-update block with HubSpot deal link
// Output
hubspot_deal_updated: boolean
hubspot_tasks_created: number // count of tasks written
hubspot_note_id: string
deal_stage_changed: boolean
slack_alert_sent: boolean
run_log: { deal_id, timestamp, status, errors[] }- HubSpot Private App token is required with scopes: crm.objects.deals.read, crm.objects.deals.write, crm.objects.notes.write, crm.objects.tasks.write, crm.objects.contacts.read. A HubSpot sandbox account must be used for all testing before any writes touch the production portal.
- Deal ID resolution: query HubSpot contacts API by attendee email, then traverse associations to find the linked deal. If multiple open deals are found for the same contact, select the most recently modified deal and log an ambiguity warning to the error table. Do not write to multiple deals without explicit config.
- Deal stage mapping table: deal_stage_keyword values from the transcript (e.g. 'proposal', 'demo booked', 'verbal yes') must be mapped to HubSpot internal stage IDs. This mapping table must be stored as a JSON config object in the automation platform and editable by the sales manager without code access.
- Due date resolution for tasks: if due_date_hint is a relative expression (e.g. 'end of week', 'next Tuesday'), the agent must convert it to an absolute ISO8601 date using the meeting_date as the reference point. If resolution fails, default to meeting_date + 3 business days and flag the task body with '[Due date estimated]'.
- Slack threshold (slack_threshold_usd) must be stored as an environment variable. Default value for this build is $5,000 unless Jordan Miles specifies otherwise. The sales channel ID must be confirmed before go-live.
- Rate limits: HubSpot API allows 100 requests per 10 seconds on the Standard tier. For a batch of 5 tasks plus 1 deal update plus 1 note, this is well within limits at current volume (40 meetings/month). No pagination or retry backoff is needed at this scale, but a simple 3-attempt retry with 2-second delay should be implemented for 429 responses.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────
// TRIGGER: Google Calendar event end time reached OR Zoom webhook
// ─────────────────────────────────────────────────────────────────
INBOUND EVENT:
source : 'google_calendar' | 'zoom_webhook'
calendar_event_id : 'evt_abc123'
event_title : 'Discovery Call - Acme Corp'
event_end_time : '2024-04-25T11:00:00Z'
attendees : [{ email: 'priya@yourcompany.com', domain: 'yourcompany.com' },
{ email: 'buyer@acme.com', domain: 'acme.com' }]
zoom_meeting_id: '84123456789'
// ─────────────────────────────────────────────────────────────────
// AGENT 1: Meeting Trigger Agent
// ────────────────────────────────────────────────────��────────────
STEP 1.1 Filter attendees by domain -> external_attendees: ['buyer@acme.com']
STEP 1.2 If external_attendees.length === 0 -> ABORT (internal call)
STEP 1.3 GET fireflies.ai/api/transcripts
?title='Discovery Call - Acme Corp'
&date='2024-04-25'
&participant_email='buyer@acme.com'
STEP 1.4 Retry logic: poll every 3 min, max 3 attempts
STEP 1.5 If transcript unavailable -> fallback_mode = true
HANDOFF A -> Recap Drafting Agent:
meeting_record: {
calendar_event_id : 'evt_abc123',
meeting_title : 'Discovery Call - Acme Corp',
meeting_date : '2024-04-25',
attendees : [{ email: 'buyer@acme.com', name: 'Alex Buyer' }],
transcript_text : '<full transcript string>' | null,
transcript_url : 'https://app.fireflies.ai/view/abc123',
fallback_mode : false,
fireflies_meeting_id: 'ff_789xyz',
rep_email : 'priya@yourcompany.com'
}
// ─────────────────────────────────────────────────────────────────
// AGENT 2: Recap Drafting Agent
// ─────────────────────────────────────────────────────────────────
STEP 2.1 Select prompt template: 'full_transcript' | 'fallback_calendar_only'
STEP 2.2 LLM inference -> extract:
extracted_pain_points : ['budget approval needed', 'integration concern']
extracted_agreements : ['30-day pilot agreed']
extracted_next_steps : [{ action: 'Send proposal', owner: 'Priya', due_date_hint: 'end of week' },
{ action: 'Book technical demo', owner: 'Alex', due_date_hint: 'next Tuesday' }]
extracted_decision_makers: ['Alex Buyer', 'Sarah CFO']
deal_stage_keyword : 'proposal'
STEP 2.3 Quality gate: if next_steps.length === 0 AND agreements.length === 0
-> prefix subject '[Review needed]', log to error table
STEP 2.4 Gmail API POST /gmail/v1/users/priya@yourcompany.com/drafts
subject : 'Follow-up: Discovery Call - Acme Corp'
to : ['buyer@acme.com']
body : <rendered recap email from template>
-> gmail_draft_id: 'draft_def456'
STEP 2.5 Notion API POST /v1/blocks/{deal_page_id}/children
-> notion_block_id: 'block_ghi789'
STEP 2.6 Slack API POST /api/chat.postMessage
channel : 'D_priya_slack_id' // direct message
text : 'Your recap draft for Acme Corp is ready. Review and send: [link]'
-> slack_nudge_sent: true
HANDOFF B -> CRM and Task Sync Agent:
recap_payload: {
pain_points : ['budget approval needed', 'integration concern'],
agreements : ['30-day pilot agreed'],
next_steps : [{ action: 'Send proposal', owner: 'Priya', due_date_hint: 'end of week' },
{ action: 'Book technical demo', owner: 'Alex', due_date_hint: 'next Tuesday' }],
deal_stage_keyword: 'proposal',
meeting_title : 'Discovery Call - Acme Corp',
meeting_date : '2024-04-25',
attendees : [{ email: 'buyer@acme.com', name: 'Alex Buyer' }]
}
rep_email : 'priya@yourcompany.com'
gmail_draft_id : 'draft_def456'
// ─────────────────────────────────────────────────────────────────
// AGENT 3: CRM and Task Sync Agent
// ─────────────────────────────────────────────────────────────────
STEP 3.1 HubSpot contacts API -> lookup by 'buyer@acme.com'
-> contact_id: 'ct_111', associated deal_id: 'deal_222'
STEP 3.2 Fetch deal properties -> deal_value_usd: 12000
STEP 3.3 Map deal_stage_keyword 'proposal' -> hs_dealstage: 'appointmentscheduled'
(from configurable stage_mapping.json)
STEP 3.4 PATCH /crm/v3/objects/deals/deal_222
hs_dealstage : 'appointmentscheduled',
notes_last_contacted : '2024-04-25T11:00:00Z',
meeting_outcome_notes : '<formatted summary>'
-> deal_stage_changed: true
STEP 3.5 POST /crm/v3/objects/notes
body : '<full recap text>',
associations : ['deal_222', 'ct_111']
-> hubspot_note_id: 'note_333'
STEP 3.6 POST /crm/v3/objects/tasks (per next_step):
Task 1: subject='Send proposal', due='2024-04-26', owner=priya_owner_id
Task 2: subject='Book technical demo', due='2024-04-30', owner=priya_owner_id
-> hubspot_tasks_created: 2
STEP 3.7 deal_value_usd (12000) >= slack_threshold_usd (5000) -> true
STEP 3.8 Slack API POST /api/chat.postMessage
channel : '#sales-channel-id'
text : 'Deal update: Acme Corp (Priya) | Stage -> Proposal | Value: $12k'
+ HubSpot deal link
-> slack_alert_sent: true
// ─────────────────────────────────────────────────────────────────
// TERMINAL OUTPUT
// ─────────────────────────────────────────────────────────────────
run_log: {
deal_id : 'deal_222',
timestamp : '2024-04-25T11:08:43Z',
gmail_draft_id : 'draft_def456',
notion_block_id : 'block_ghi789',
hubspot_note_id : 'note_333',
hubspot_tasks_created: 2,
deal_stage_changed : true,
slack_alert_sent : true,
slack_nudge_sent : true,
status : 'success',
errors : []
}04Recommended build stack
More documents for this process
Every document generated for Post-Meeting Recap & Follow-up.