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
Event & Webinar Promotion Automation
[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, connect, and deploy the three-agent Event and Webinar Promotion automation. It covers the current-state step map with bottlenecks identified, full agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. All implementation decisions, tool tiers, and field names referenced here are the confirmed facts from the discovery and mapping phase. The process owner's responsibilities are limited to approving social post copy at the human review step and confirming event brief rows in Google Sheets; everything else is handled by the automation.
01Process snapshot
02Agent specifications
Reads a newly confirmed event row from the Google Sheets event calendar and orchestrates the two most time-expensive setup tasks automatically: creating the Zoom webinar registration and activating the pre-built HubSpot announcement plus reminder email sequence. Send dates for the sequence are calculated dynamically from the event date field in the brief row. This agent eliminates the manual Zoom form build, the copy-paste of the registration link, and the HubSpot sequence rebuild that currently stalls promotion launch by up to four days. Complexity: Moderate.
// Input (from Google Sheets confirmed event row) event_id : string // unique row identifier event_title : string event_date : ISO 8601 date event_start_time : HH:MM (24h, UTC offset stored in sheet) event_duration_min: integer speaker_name : string speaker_bio : string hubspot_list_id : string // target contact list for email sequence sequence_template_id: string // HubSpot sequence to clone and activate // Output zoom_webinar_id : string zoom_registration_url : string // passed to Social Promotion Agent hubspot_sequence_id : string // newly activated sequence announcement_email_id : string reminder_1_send_date : ISO 8601 datetime // event_date minus 7 days reminder_2_send_date : ISO 8601 datetime // event_date minus 1 day reminder_3_send_date : ISO 8601 datetime // event_date minus 1 hour setup_status : 'success' | 'partial' | 'error' setup_error_message : string | null
- Google Sheets trigger: poll interval should be set to 5 minutes maximum. The monitored sheet tab name must be confirmed before build ('Event Calendar' is the assumed default). The Status column header value must match exactly; case sensitivity applies.
- Zoom API tier: a Zoom Pro or higher account is required. Basic accounts do not support webinar registration endpoints. Confirm the account tier before build starts. Use OAuth 2.0 server-to-server app credentials, not JWT (deprecated).
- HubSpot sequence activation: the sequence template must already exist in HubSpot and must be cloneable via the Sequences API. The agent clones the template and updates send dates rather than creating a sequence from scratch. Confirm the sequence_template_id with the marketing coordinator before build.
- Date calculation: all event_date values in the sheet must be stored in ISO 8601 format (YYYY-MM-DD). If the sheet contains formatted date strings (e.g. '5 May 2025'), a normalisation step must be added to the agent before the Zoom API call.
- Fallback on Zoom error: if the Zoom webinar creation call fails, the agent must write 'ZOOM_ERROR' to the sheet Status column and post an alert to the designated Slack error channel. The agent must not proceed to HubSpot sequence activation without a confirmed zoom_registration_url.
- Dedupe: if a row with the same event_id has already been processed (check against a processed_events log table), the agent must skip and log a duplicate warning rather than creating a second Zoom webinar.
Triggered immediately after the Event Setup Agent confirms a valid zoom_registration_url. Uses an AI content generation step to draft LinkedIn post copy for three scheduled slots: announcement (day of setup), mid-cycle reminder (event_date minus 7 days), and day-of post (event_date minus 1 day). The coordinator reviews and approves or revises the drafts before the agent schedules them via the LinkedIn Pages API. After approval, the agent also fires a formatted Slack message to the internal launch channel. This agent removes the manual writing and scheduling load while keeping a human in the loop for brand quality control. Complexity: Moderate.
// Input (from Event Setup Agent output payload) event_id : string event_title : string event_date : ISO 8601 date event_start_time : HH:MM speaker_name : string zoom_registration_url : string linkedin_page_id : string // LinkedIn Page URN, stored in credential store slack_channel_id : string // stored in credential store // Output (after AI draft generation, before approval) draft_post_announcement : string // copy for immediate scheduling draft_post_midcycle : string // copy for event_date minus 7 days draft_post_dayof : string // copy for event_date minus 1 day approval_request_url : string // link sent to coordinator for review // On approval linkedin_post_id_1 : string // scheduled announcement post UGC ID linkedin_post_id_2 : string // scheduled mid-cycle post UGC ID linkedin_post_id_3 : string // scheduled day-of post UGC ID slack_message_ts : string // Slack message timestamp social_status : 'scheduled' | 'pending_approval' | 'error'
- LinkedIn API access: scheduling posts requires a LinkedIn Page (not a personal profile) with the connected app granted the w_member_social and r_organization_social OAuth scopes. The Page admin must authorise the app. Confirm Page admin credentials are available before build.
- AI copy generation: the content generation step must receive the event_title, speaker_name, event_date, and zoom_registration_url as prompt variables. The system prompt must enforce the brand voice guidelines provided by the marketing coordinator. Store the system prompt as an environment variable so it can be updated without a code change.
- Human approval gate: the approval step must pause the workflow and send the three draft posts to the coordinator via email or an internal Slack DM with an approve/revise link. The workflow must not proceed to LinkedIn scheduling until approval_status equals 'approved'. Set a 24-hour timeout; if no approval is received, escalate to the marketing manager via Slack and hold the posts in draft.
- Revision loop: if the coordinator returns a 'revise' response, the agent must regenerate the copy with the coordinator's comments appended to the prompt and re-request approval. Limit revision loops to three iterations before flagging for manual handling.
- LinkedIn rate limits: the LinkedIn Pages API enforces a limit of 100 post creations per day per app. For the expected volume of 3 to 6 events per month this is not a constraint, but the agent should log each API call response code for audit purposes.
- Slack notification: the internal Slack message must include event_title, event_date, event_start_time, and zoom_registration_url. Use a Block Kit formatted message, not plain text, for readability. The target slack_channel_id must be confirmed with the marketing coordinator before build.
Runs on a schedule triggered one hour after the event end time recorded in the Google Sheets brief row. Connects to the Zoom Reports API to pull the full attendee and no-show list, updates each contact's HubSpot record with an attendance tag and lifecycle stage change, triggers the correct segmented follow-up email sequence in HubSpot (attendee replay email or no-show re-invite), and writes registration count, attendance count, and email engagement figures back to the reporting row in Google Sheets. This agent closes the post-event data gap that currently leaves CRM records inaccurate and follow-ups delayed by one to two days. Complexity: Complex.
// Input event_id : string zoom_webinar_id : string // from Event Setup Agent output event_end_datetime : ISO 8601 datetime hubspot_list_id : string attendee_sequence_id : string // HubSpot sequence for confirmed attendees noshow_sequence_id : string // HubSpot sequence for no-shows sheet_row_id : string // Google Sheets row reference for metrics write-back // Output attendee_count : integer noshow_count : integer registrant_count : integer contacts_updated : integer // HubSpot records touched attendee_tag : string // e.g. 'event-attended-[event_id]' noshow_tag : string // e.g. 'event-noshow-[event_id]' followup_sent_attendees: boolean followup_sent_noshows : boolean metrics_written : boolean sync_status : 'success' | 'partial' | 'error' sync_error_message : string | null
- Zoom Reports API availability: attendance data is only available after the host has formally ended the session using the 'End Meeting for All' control. If attendance data returns empty, the agent must wait 15 minutes and retry up to three times before writing a 'DATA_PENDING' status to the sheet and alerting via Slack. A manual override option must be documented in the runbook.
- HubSpot contact matching: match Zoom registrants to HubSpot contacts by email address. If no match is found, create a new HubSpot contact with the attendee tag and source set to 'webinar-registration'. Do not overwrite existing lifecycle stage if it is already 'Customer' or 'Evangelist'.
- Segmentation logic: a contact is classified as an attendee if their attended_duration_minutes is greater than or equal to 50% of event_duration_min. All other registrants are classified as no-shows. This threshold must be configurable as an environment variable.
- HubSpot rate limits: the HubSpot Contacts API allows 100 requests per 10 seconds on Marketing Hub Professional. For events with large registrant lists, implement a batched update loop using the Batch Contacts Update endpoint (up to 100 contacts per call) rather than individual PATCH calls.
- Google Sheets write-back: write to the specific row identified by sheet_row_id. Fields to write: registrant_count, attendee_count, noshow_count, and email open rate (fetched from HubSpot Campaign API after 24 hours). The open rate write-back should be a separate scheduled step triggered 24 hours after follow-up emails are sent.
- Complexity justification: this agent is rated Complex due to the conditional segmentation logic, multi-tool data joins, retry handling, and the two-phase write-back requirement for metrics.
03End-to-end data flow
// ============================================================
// EVENT & WEBINAR PROMOTION AUTOMATION -- END-TO-END DATA FLOW
// ============================================================
// TRIGGER
// Google Sheets: 'Event Calendar' tab, Status column = 'Confirmed'
TRIGGER google_sheets.row_added OR google_sheets.row_updated
WHERE sheet.Status == 'Confirmed'
AND sheet.event_id NOT IN processed_events_log
EXTRACT fields:
event_id, event_title, event_date, event_start_time,
event_duration_min, speaker_name, speaker_bio,
hubspot_list_id, sequence_template_id
// ============================================================
// AGENT 1: EVENT SETUP AGENT
// ============================================================
// Step A: Create Zoom Webinar
CALL zoom.webinars.create
PAYLOAD: { topic: event_title, start_time: event_date + event_start_time,
duration: event_duration_min, host_email: [zoom_host_email env var],
settings: { registrants_email_notification: true,
registration_type: 1 } }
ON SUCCESS: zoom_webinar_id, zoom_registration_url
ON ERROR: write 'ZOOM_ERROR' to sheet Status column
-> POST alert to Slack #automation-errors
-> HALT agent
// Step B: Clone and activate HubSpot email sequence
CALL hubspot.sequences.clone
PAYLOAD: { sequence_template_id: sequence_template_id }
RETURNS: new_sequence_id
CALL hubspot.sequences.update_steps
PAYLOAD: { sequence_id: new_sequence_id,
steps: [
{ type: 'EMAIL', send_at: event_date - 7 days }, // reminder_1
{ type: 'EMAIL', send_at: event_date - 1 day }, // reminder_2
{ type: 'EMAIL', send_at: event_date - 60 min } // reminder_3
],
registration_url: zoom_registration_url }
RETURNS: hubspot_sequence_id, announcement_email_id
CALL hubspot.sequences.enroll
PAYLOAD: { sequence_id: hubspot_sequence_id,
contact_list_id: hubspot_list_id }
// Write event_id to processed_events_log
LOG processed_events_log.insert: { event_id, zoom_webinar_id,
hubspot_sequence_id,
processed_at: now() }
// OUTPUT HANDOFF: Event Setup Agent -> Social Promotion Agent
EMIT payload:
{ event_id, event_title, event_date, event_start_time,
speaker_name, zoom_registration_url,
linkedin_page_id: [env var], slack_channel_id: [env var] }
// ============================================================
// AGENT 2: SOCIAL PROMOTION AGENT
// ============================================================
// Step C: Generate LinkedIn post copy via AI content step
CALL ai_content.generate
PROMPT_VARS: { event_title, event_date, speaker_name, zoom_registration_url }
SYSTEM_PROMPT: [BRAND_VOICE_PROMPT env var]
RETURNS:
draft_post_announcement // slot: now
draft_post_midcycle // slot: event_date - 7 days
draft_post_dayof // slot: event_date - 1 day
// Step D: Human approval gate
SEND approval_request TO coordinator_email
BODY: { draft_post_announcement, draft_post_midcycle, draft_post_dayof,
approval_link, revise_link }
TIMEOUT: 24 hours
ON TIMEOUT: POST escalation to Slack #marketing-ops, HOLD workflow
ON REVISE: append coordinator_comments to prompt, regenerate (max 3 loops)
ON APPROVE: continue to Step E
// Step E: Schedule LinkedIn posts
CALL linkedin.ugcPosts.create // x3
PAYLOAD_1: { author: linkedin_page_id, text: draft_post_announcement,
scheduled_publish_time: now() + 30 min }
PAYLOAD_2: { author: linkedin_page_id, text: draft_post_midcycle,
scheduled_publish_time: event_date - 7 days at 09:00 }
PAYLOAD_3: { author: linkedin_page_id, text: draft_post_dayof,
scheduled_publish_time: event_date - 1 day at 09:00 }
RETURNS: linkedin_post_id_1, linkedin_post_id_2, linkedin_post_id_3
// Step F: Slack internal launch notification
CALL slack.chat.postMessage
PAYLOAD: { channel: slack_channel_id,
blocks: Block_Kit_template {
event_title, event_date, event_start_time,
zoom_registration_url } }
RETURNS: slack_message_ts
// ============================================================
// AGENT 3: POST-EVENT SYNC AGENT
// ============================================================
// Triggered at: event_date + event_start_time + event_duration_min + 60 min
// Step G: Pull Zoom attendance report
CALL zoom.report.webinar_participants
PAYLOAD: { webinar_id: zoom_webinar_id }
RETRY: up to 3x at 15 min intervals if response is empty
ON PERSISTENT_EMPTY: write 'DATA_PENDING' to sheet, alert Slack #automation-errors
RETURNS: participants[] { email, name, attended_duration_minutes }
// Step H: Classify attendees vs no-shows
FOR EACH contact IN participants:
IF contact.attended_duration_minutes >= (event_duration_min * ATTENDANCE_THRESHOLD)
// ATTENDANCE_THRESHOLD default 0.5, stored as env var
tag = 'event-attended-' + event_id
sequence_to_enroll = attendee_sequence_id
ELSE:
tag = 'event-noshow-' + event_id
sequence_to_enroll = noshow_sequence_id
// Step I: Update HubSpot contacts in batches of 100
CALL hubspot.contacts.batch_upsert
MATCH_BY: email
ON_NO_MATCH: create new contact
PAYLOAD_PER_CONTACT: { tag, lifecycle_stage: 'Lead' (skip if Customer/Evangelist),
event_source: 'webinar-registration' }
RETURNS: contacts_updated
// Step J: Enroll contacts in segmented HubSpot follow-up sequences
CALL hubspot.sequences.enroll (attendees -> attendee_sequence_id)
CALL hubspot.sequences.enroll (noshows -> noshow_sequence_id)
// Step K: Write metrics to Google Sheets reporting row
CALL google_sheets.rows.update
ROW: sheet_row_id
FIELDS: { registrant_count, attendee_count, noshow_count,
contacts_updated, sync_status, synced_at: now() }
// Step L: Scheduled 24h later -- write email open rate
CALL hubspot.campaigns.get_stats
PAYLOAD: { campaign_id: [derived from announcement_email_id] }
RETURNS: open_rate_pct
CALL google_sheets.rows.update
ROW: sheet_row_id
FIELDS: { email_open_rate_pct: open_rate_pct }
// ============================================================
// FLOW COMPLETE
// ============================================================04Recommended build stack
More documents for this process
Every document generated for Event & Webinar Promotion.