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 Magnet & Landing Page Follow-up
[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, wire, and test the Lead Magnet and Landing Page Follow-up automation end to end. It covers the current-state step map, full agent specifications with IO contracts, the complete data flow from Typeform trigger to Slack alert, and the recommended build stack with estimated hours. All decisions about tooling, field names, and agent boundaries have been confirmed against the process template. The FullSpec team owns the build in its entirety; the sections below define exactly what that build must produce.
01Process snapshot
02Agent specifications
This agent is the entry point of the workflow. It receives the raw Typeform submission payload the moment a new response is posted, extracts all field values, and performs a deduplication lookup against HubSpot before creating or updating the contact record. All CRM properties, list memberships, lead source tags, and lead magnet labels are applied in this agent without any human input. A confirmation row is then appended to the Google Sheets tracking log. The agent must handle multi-magnet contacts gracefully: if an existing HubSpot record is found, it updates properties and adds the new magnet label rather than creating a duplicate. Estimated build time is 10 hours at Moderate complexity.
// Input: Typeform webhook payload event.form_response.answers[*].field.ref // all question refs event.form_response.answers[*].text // raw answer values event.form_response.submitted_at // ISO 8601 timestamp event.form_response.token // unique submission ID // Extracted fields passed downstream contact.email // from email field ref contact.first_name // from short_text ref: 'first_name' contact.last_name // from short_text ref: 'last_name' contact.lead_magnet_id // from hidden field or choice ref: 'magnet_id' contact.form_id // Typeform form_id from event metadata contact.submission_ts // submitted_at normalised to UTC // HubSpot upsert result hubspot.contact_id // hs_object_id of created or updated record hubspot.is_new_contact // boolean: true = created, false = updated hubspot.lists_enrolled // array of list IDs the contact was added to hubspot.tags_applied // array: [lead_source, lead_magnet_label] // Google Sheets log row appended sheet.row.contact_email sheet.row.contact_name sheet.row.lead_magnet_id sheet.row.submission_ts sheet.row.hubspot_contact_id sheet.row.delivery_status // set to 'pending' at this stage
- HubSpot tier must be at least Starter (Marketing Hub) to allow list membership writes and custom property updates via API. Confirm the active subscription tier before build.
- All HubSpot contact properties used (lead_magnet_label, lead_source, magnet_id, nurture_sequence_id) must be created and named exactly as specified in the field mapping table before the agent is wired. Any inconsistency in property names will cause silent write failures.
- The deduplication logic uses email address as the unique key via HubSpot's contacts/v1/contact/createOrUpdate/:email endpoint. If a matching record exists, properties are merged and new list memberships appended; no duplicate record is created.
- The lead magnet mapping table (magnet_id to HubSpot list ID, tag value, and Mailchimp sequence ID) must be supplied by the marketing team before build begins. This table drives all routing decisions downstream.
- The Google Sheets tracking sheet must have a named range or fixed tab name confirmed before wiring (suggested: 'Submissions_Log'). The service account credential must have Editor access to the sheet.
- If the Typeform payload does not contain a recognisable magnet_id (e.g. hidden field missing or empty), the agent must route the contact to a fallback HubSpot list named 'Unclassified_Submissions' and append a flag in the Sheets row rather than failing silently.
- Confirm before build: does the client have more than one active Typeform form? If yes, the webhook must filter by form_id so submissions from unrelated forms do not enter this workflow.
This agent is triggered immediately after the Lead Capture and Enrichment Agent confirms the HubSpot contact has been created or updated. It retrieves the correct Google Drive asset link for the submitted lead magnet, sends the asset delivery email via Mailchimp transactional within 60 seconds of the original form submission, enrols the contact in the correct Mailchimp nurture automation sequence, and monitors HubSpot engagement data to determine when the warm-lead threshold is crossed. When the threshold is met (two email opens plus one link click recorded in HubSpot), it posts a structured Slack alert to the sales channel. Estimated build time is 10 hours at Moderate complexity. The remaining 2 hours cover end-to-end integration testing across both agents.
// Input: from Lead Capture Agent contact.email contact.first_name contact.lead_magnet_id hubspot.contact_id hubspot.nurture_sequence_id // resolved from magnet mapping table hubspot.is_new_contact // Google Drive lookup drive.file_id // resolved from magnet_id via asset mapping table drive.shareable_link // webViewLink with 'anyone with link can view' // Mailchimp transactional send mailchimp.template_name // e.g. 'asset-delivery-v2' mailchimp.merge_vars.FNAME // contact.first_name mailchimp.merge_vars.ASSET_LINK // drive.shareable_link mailchimp.message.to[0].email // contact.email mailchimp.send_result.status // 'sent' | 'queued' | 'rejected' mailchimp.send_result.message_id // unique message ID for tracking // Mailchimp nurture enrolment mailchimp.automation_id // from hubspot.nurture_sequence_id lookup mailchimp.enrolment_status // 'subscribed' confirmation // Google Sheets delivery_status update sheet.row.delivery_status // updated to 'sent' on success sheet.row.message_id // mailchimp message ID logged // Warm-lead check (polled via HubSpot engagement timeline) hubspot.engagement.email_opens // integer count hubspot.engagement.link_clicks // integer count hubspot.warm_threshold_met // boolean: opens >= 2 AND clicks >= 1 // On warm threshold: Slack alert payload slack.channel // #sales-warm-leads slack.text // formatted message string slack.blocks[0].contact_name slack.blocks[1].lead_magnet_id slack.blocks[2].hubspot_contact_url slack.blocks[3].engagement_summary // 'X opens, Y clicks' // On bounced or flagged contact: routed to manual exception step exception.reason // 'bounce' | 'invalid_email' | 'unsubscribed' exception.hubspot_id // contact ID for coordinator review exception.sheets_row_ref // row index in Submissions_Log for update
- Mailchimp Transactional (Mandrill) is a separate add-on to standard Mailchimp and requires its own API key distinct from the main Mailchimp marketing API key. Confirm both keys are provisioned before build.
- The asset delivery email template ('asset-delivery-v2' or the agreed name) must be created and approved in Mailchimp before the agent is wired. FullSpec cannot write email copy; the marketing team must supply the template content and merge variable names.
- Each lead magnet must have a corresponding Mailchimp automation sequence ID and a Google Drive file ID recorded in the magnet mapping table. If any magnet lacks a sequence, the agent must enrol the contact in a generic fallback sequence ('nurture-default') rather than failing.
- The warm-lead threshold check (2 opens + 1 click) is implemented by polling the HubSpot contact's engagement timeline on a schedule (suggested: every 4 hours) or by listening to HubSpot workflow-triggered webhooks if the account tier supports it. Confirm HubSpot tier before choosing the polling vs. webhook approach.
- The Slack Incoming Webhook URL must be scoped to the #sales-warm-leads channel specifically. Do not use a general workspace webhook. Confirm the channel name and webhook URL during tool audit.
- If the Mailchimp transactional send returns a 'rejected' status (e.g. the email is on the suppression list), the agent must flag the contact as an exception in Google Sheets and not attempt a retry. The marketing coordinator handles these cases in the manual exception review step.
- Google Drive asset links must be set to 'anyone with the link can view' sharing. The build must verify this permission via Drive API before embedding the link in the email. If the file is not publicly accessible, the agent raises an exception rather than sending a broken link.
03End-to-end data flow
// ===================================================================
// TRIGGER: New Typeform submission received
// ===================================================================
TYPEFORM_WEBHOOK_POST /webhook/lead-magnet-followup
payload.event_type = 'form_response'
payload.form_response.token = '<unique_submission_id>'
payload.form_response.submitted_at = '2024-04-19T09:14:32Z'
payload.form_response.answers[0].field.ref = 'email'
payload.form_response.answers[0].email = 'alex@example.com'
payload.form_response.answers[1].field.ref = 'first_name'
payload.form_response.answers[1].text = 'Alex'
payload.form_response.answers[2].field.ref = 'last_name'
payload.form_response.answers[2].text = 'Jordan'
payload.form_response.hidden.magnet_id = 'magnet_seo_checklist'
payload.form_response.form_id = 'tf_form_abc123'
// --- AGENT 1 BEGINS: Lead Capture and Enrichment Agent ---
// Step 1: Extract and normalise fields
contact.email = answers['email'].email // 'alex@example.com'
contact.first_name = answers['first_name'].text // 'Alex'
contact.last_name = answers['last_name'].text // 'Jordan'
contact.lead_magnet_id = hidden.magnet_id // 'magnet_seo_checklist'
contact.form_id = payload.form_response.form_id // 'tf_form_abc123'
contact.submission_ts = payload.form_response.submitted_at
// Step 2: HubSpot deduplication and upsert
POST /contacts/v1/contact/createOrUpdate/email/alex@example.com
body.properties[].property = 'firstname' value = 'Alex'
body.properties[].property = 'lastname' value = 'Jordan'
body.properties[].property = 'lead_source' value = 'typeform_landing_page'
body.properties[].property = 'lead_magnet_label' value = 'SEO Checklist'
body.properties[].property = 'magnet_id' value = 'magnet_seo_checklist'
body.properties[].property = 'nurture_sequence_id' value = 'mc_seq_seo_001'
body.properties[].property = 'hs_lead_status' value = 'NEW'
response.vid -> hubspot.contact_id // e.g. 12345678
response.isNew -> hubspot.is_new_contact // true
// Step 3: HubSpot list membership write
POST /contacts/v1/lists/<list_id_seo_checklist>/add
body.vids = [hubspot.contact_id]
response.updated[] -> hubspot.lists_enrolled // ['list_seo_leads']
// Step 4: Google Sheets row append
POST sheets/v4/spreadsheets/<SHEET_ID>/values/Submissions_Log!A:J:append
values = [
contact.email,
contact.first_name + ' ' + contact.last_name,
contact.lead_magnet_id,
contact.submission_ts,
hubspot.contact_id,
'pending' // delivery_status
'' // message_id: populated in Agent 2
]
response.updates.updatedRange -> sheet.row_ref // e.g. 'Submissions_Log!A47:J47'
// --- AGENT 1 COMPLETE: emits {contact.*, hubspot.*, sheet.row_ref} ---
// --- AGENT 2 BEGINS: Asset Delivery and Nurture Agent ---
// Step 5: Resolve asset link from Google Drive
GET drive/v3/files/<drive_file_id_seo_checklist>?fields=webViewLink,permissions
drive_file_id resolved via magnet_id -> asset mapping table
response.webViewLink -> drive.shareable_link
response.permissions[] -> assert 'anyone' role = 'reader' // else raise exception
// Step 6: Mailchimp transactional asset delivery email
POST https://mandrillapp.com/api/1.0/messages/send-template
body.template_name = 'asset-delivery-v2'
body.template_content[]
body.message.to[0].email = contact.email
body.message.to[0].name = contact.first_name + ' ' + contact.last_name
body.message.merge_vars[0].name = 'FNAME' content = contact.first_name
body.message.merge_vars[1].name = 'ASSET_LINK' content = drive.shareable_link
body.message.merge_vars[2].name = 'MAGNET_NAME' content = 'SEO Checklist'
response[0].status -> mailchimp.send_result.status // 'sent'
response[0]._id -> mailchimp.send_result.message_id
// Step 7: Update Google Sheets delivery_status
PUT sheets/v4/spreadsheets/<SHEET_ID>/values/sheet.row_ref
values[5] = 'sent'
values[6] = mailchimp.send_result.message_id
// Step 8: Mailchimp nurture sequence enrolment
POST https://us1.api.mailchimp.com/3.0/automations/<mc_seq_seo_001>/emails/<email_1_id>/queue
body.email_address = contact.email
response.id -> mailchimp.enrolment_status // 'subscribed'
// Step 9: Warm-lead threshold check (polled every 4 hours via HubSpot engagement timeline)
GET /engagements/v1/engagements/associated/CONTACT/<hubspot.contact_id>/paged
filter: type = 'EMAIL'
aggregate: email_opens = count(engagement.metadata.openedAt != null)
aggregate: link_clicks = count(engagement.metadata.linkClicked != null)
hubspot.warm_threshold_met = (email_opens >= 2 AND link_clicks >= 1)
// Step 10 (conditional): Post warm-lead Slack alert
IF hubspot.warm_threshold_met == true:
POST https://hooks.slack.com/services/<SLACK_WEBHOOK_PATH>
body.channel = '#sales-warm-leads'
body.blocks[0].type = 'section'
body.blocks[0].text = 'Warm lead alert: Alex Jordan (alex@example.com)'
body.blocks[1].text = 'Lead magnet: SEO Checklist'
body.blocks[2].text = 'Engagement: 2 opens, 1 click'
body.blocks[3].text = 'HubSpot: https://app.hubspot.com/contacts/<hubspot.contact_id>'
response.ok -> slack.alert_sent = true
// Step 10 (exception path): Bounce or rejected email
IF mailchimp.send_result.status IN ['rejected','bounced']:
exception.reason = mailchimp.send_result.reject_reason
exception.hubspot_id = hubspot.contact_id
exception.sheets_row_ref = sheet.row_ref
// Flag row in Submissions_Log, update delivery_status = 'exception'
// No retry attempted; routed to Marketing Coordinator manual review
// --- AGENT 2 COMPLETE ---
// ===================================================================
// END OF AUTOMATED FLOW
// ===================================================================
// Remaining manual step: Marketing Coordinator reviews exception contacts
// flagged in Submissions_Log (delivery_status = 'exception') and corrects
// or removes records in HubSpot before re-triggering delivery if appropriate.04Recommended build stack
More documents for this process
Every document generated for Lead Magnet & Landing Page Follow-up.