Back to Customer Testimonial Collection

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 Testimonial Collection

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

This document is the complete technical reference for the FullSpec team building the Customer Testimonial Collection automation. It covers the current-state step map, agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything needed to implement, test, and hand over the two-agent workflow is contained here. Where the owner side requires decisions, those are called out explicitly as confirmation items before build begins.

01Process snapshot

Step
Name
Description
1
Identify Eligible Customers
Marketing Manager reviews HubSpot manually on a weekly or ad-hoc basis to find recently closed or completed deals. No automated filter or alert in place. Time cost: 20 min/cycle.
2
Draft Personalised Request Email
A bespoke outreach email is written per customer referencing their specific project or purchase, including a feedback form link or request for direct reply. Time cost: 15 min/cycle.
3
Send Initial Testimonial Request
The drafted email is sent manually from Gmail. Timing depends entirely on manager availability, not on the optimal post-close window. Time cost: 5 min/cycle.
4
Log Outreach in Spreadsheet or CRM
The manager manually updates a HubSpot record or spreadsheet to note the request date and customer name. Frequently skipped under workload pressure. Time cost: 5 min/cycle.
5
Monitor Inbox for Responses
Manager checks Gmail repeatedly over following days for customer replies. No alert or routing logic exists. Responses are missed during busy periods. Time cost: 30 min/cycle.
6
Send Manual Follow-Up Reminder
A follow-up email is written and sent if no response arrives within a week. Frequently forgotten or delayed because it relies entirely on the manager remembering. Time cost: 10 min/cycle.
7
Receive and Review Submission
When a customer responds via email, the testimonial text is read and assessed for quality and suitability. Responses received by email must be manually copied out. Time cost: 10 min/cycle.
8
Seek Internal Approval if Needed
Sensitive or high-profile testimonials are forwarded by email to a senior stakeholder for sign-off. No structured workflow or audit trail exists for this step. Time cost: 15 min/cycle.
9
Format Quote for Marketing Use
The approved testimonial is reformatted and trimmed to brand style for use on the website, in decks, or in sales materials, then saved to Google Drive. Time cost: 15 min/cycle.
10
Save to Testimonial Library
The formatted quote, customer name, role, and date are manually added to Notion. Frequently delayed by days or skipped entirely when workload is high. Time cost: 10 min/cycle.
11
Notify Team of New Testimonial
The manager posts an informal Slack message to let sales and leadership know a new testimonial is available. Step is inconsistent and not tracked. Time cost: 5 min/cycle.
Time cost summary: total manual time per cycle is 140 minutes (approximately 2.3 hours). At the current volume of approximately 30 requests per month this equates to roughly 4.5 hours per week and 225 hours per year. Steps 1, 2, 3, 4, and 6 are fully replaced by the Outreach Sequence Agent. Steps 5, 7, 9, 10, and 11 are fully replaced by the Submission Review and Library Agent. Step 8 (internal approval for sensitive submissions) is retained as a human-in-the-loop decision point routed through Slack. The one remaining human action is the manager clicking Approve or Flag for Review on the Slack card.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Outreach Sequence Agent

Monitors HubSpot for deals that transition to the Closed Won or Completed stage and manages the complete email outreach sequence on behalf of the Marketing Manager. The agent personalises each email using CRM contact and deal fields, embeds the Typeform submission link, creates a logged CRM note immediately after send, and schedules two timed follow-up reminders at day 7 and day 14 post-close. The sequence is cancelled automatically when a matching Typeform submission is detected, preventing over-contact. This agent replaces all manual email drafting, sending, logging, and reminder tasks. Estimated build: 12 hours. Complexity: Moderate.

Trigger
HubSpot deal stage changes to 'Closed Won' or 'Completed' (exact stage label must be confirmed per account pipeline before build).
Tools
HubSpot (deal webhook and contact read/write), Gmail (send via OAuth-authenticated sending address), Typeform (form link injection).
Replaces steps
Steps 1, 2, 3, 4, and 6.
Estimated build
12 hours. Complexity: Moderate.
// Input
hubspot.deal.id          : string   // HubSpot deal ID
hubspot.deal.stage       : string   // Must match confirmed stage label
hubspot.contact.id       : string   // Associated primary contact
hubspot.contact.email    : string   // Recipient address
hubspot.contact.firstname: string   // For email personalisation
hubspot.contact.lastname : string   // For email personalisation
hubspot.deal.name        : string   // Project or deal name for body copy
hubspot.deal.closedate   : date     // Used to anchor follow-up timing

// Output
gmail.message_id_initial : string   // Message ID of initial outreach email
gmail.message_id_followup1: string  // Message ID of day-7 follow-up
gmail.message_id_followup2: string  // Message ID of day-14 follow-up (if needed)
hubspot.note.id          : string   // CRM note recording outreach activity
hubspot.note.body        : string   // 'Outreach sent [date]; template: testimonial_request_v1'
typeform.form_url        : string   // Personalised Typeform link embedded in email

// Cancellation condition
typeform.submission_received: boolean // If true, cancel pending follow-up tasks immediately
  • HubSpot pipeline stage names must be confirmed before build. The trigger fires on an exact string match. If the business uses multiple pipelines, each eligible stage label must be listed in the trigger configuration. Do not use deal stage ID alone as a fallback without confirming whether the account exposes stage IDs consistently via webhook.
  • Gmail sending must use an OAuth 2.0 authenticated connection scoped to gmail.send only. The sending address should be the Marketing Manager's connected account, not a generic alias, to preserve deliverability and reply threading. Confirm the Google Workspace account holder grants OAuth consent before build begins.
  • The Typeform link embedded in the email must be the specific form built for this process, not a generic feedback form. The form must include fields for testimonial text, customer name, customer role, and a permission/consent checkbox. Reusing an existing form without these fields will break the Submission Review and Library Agent's field mapping.
  • Follow-up scheduling must use the deal close date as the anchor, not the send date of the initial email, to maintain consistent timing across all contacts. Day-7 and day-14 delays are relative to hubspot.deal.closedate.
  • Dedupe behaviour: before sending the initial email, the agent must check whether a CRM note with the tag 'testimonial_outreach' already exists on the contact within the last 90 days. If found, skip send and log a 'duplicate suppressed' note. This prevents re-contacting customers across multiple deal cycles within the same quarter.
  • The sequence cancellation check (Typeform submission received) must be evaluated before each scheduled follow-up fires, not only at the point of initial send.
  • Confirm before build: exact HubSpot pipeline stage label(s), OAuth consent from sending Gmail account, Typeform form ID for the dedicated testimonial form, and whether any deal types should be excluded from outreach (for example, internal accounts or partner deals).
Submission Review and Library Agent

Receives Typeform submissions via webhook, constructs a formatted Slack approval card with the full testimonial content and two interactive buttons (Approve and Flag for Review), and routes the outcome accordingly. On approval, the agent writes a new entry to the Notion testimonial database and updates the HubSpot contact record with a 'Testimonial Collected' property and the approval date. On a Flag for Review response, the submission is held and a Slack thread reply is posted instructing the Marketing Manager to complete manual review before the record is written. This agent replaces all manual inbox monitoring, submission review, library entry, and team notification steps. Estimated build: 14 hours. Complexity: Moderate.

Trigger
Typeform webhook fires on new submission to the designated testimonial form (identified by form_id).
Tools
Typeform (webhook receiver), Slack (Block Kit approval card with interactive actions), Notion (database page create), HubSpot (contact property update).
Replaces steps
Steps 5, 7, 9, 10, and 11.
Estimated build
14 hours. Complexity: Moderate.
// Input (from Typeform webhook payload)
typeform.form_id              : string   // Must match the dedicated testimonial form ID
typeform.response_id          : string   // Used as idempotency key to prevent duplicate writes
typeform.submitted_at         : datetime // Submission timestamp
typeform.answers.testimonial_text : string // Customer's submitted quote
typeform.answers.customer_name    : string // Customer's full name
typeform.answers.customer_role    : string // Customer's job title or role
typeform.answers.permission_given : boolean // Consent checkbox — must be true to proceed
typeform.hidden.hubspot_contact_id: string // Passed via Typeform hidden field from email link

// Output (on Approve path)
notion.page_id               : string   // ID of the new Notion testimonial library entry
notion.page.customer_name    : string
notion.page.customer_role    : string
notion.page.testimonial_text : string
notion.page.approval_date    : date
notion.page.hubspot_contact_id: string  // Cross-reference field
hubspot.contact.property.testimonial_collected : boolean // Set to true
hubspot.contact.property.testimonial_approval_date : date
slack.notification_message   : string   // 'New testimonial saved to library: [customer_name]'

// On approval (Slack interactive action)
slack.action.value           : string   // 'approve' or 'flag_for_review'
slack.action.user_id         : string   // Slack user ID of the approving manager
slack.action.action_ts       : string   // Timestamp of button click

// On Flag for Review path
slack.thread_reply           : string   // 'Submission held — please review and approve manually before library write.'
notion.write_blocked         : boolean  // No Notion page created until manual resolution
  • The Slack bot requires the chat:write, channels:read, and interactive_messages (interactivity) permission scopes enabled in the Slack app configuration. Organisations with strict Slack governance may require IT approval to enable interactive message actions on a bot. Confirm this access is available before build begins.
  • The Typeform webhook must be secured with a signature verification header (Typeform-Signature) validated on receipt. Do not process payloads without signature verification to prevent spoofed submissions from writing to Notion.
  • The permission_given field (consent checkbox) must be validated as true before any downstream action. If the field is false or absent, the workflow must halt, log a 'consent not given' error to the Supabase error table, and post a Slack alert to the marketing channel.
  • Use typeform.response_id as an idempotency key. Before writing to Notion, query the database for an existing page with a matching response_id. If found, skip the write and log a 'duplicate suppressed' event. This prevents double entries from webhook retries.
  • The Notion database schema must include the following properties before build: Customer Name (title), Customer Role (rich text), Testimonial Text (rich text), Approval Date (date), HubSpot Contact ID (rich text), Response ID (rich text, used for deduplication), and Status (select: Approved, Flagged, Pending).
  • The hubspot_contact_id must be passed as a Typeform hidden field appended to the form URL at the point of link generation in the Outreach Sequence Agent. Confirm the Typeform plan tier supports hidden fields (all paid plans support this; the free tier does not).
  • HubSpot contact property 'testimonial_collected' (boolean) and 'testimonial_approval_date' (date) must be created as custom contact properties in the HubSpot account before build. Confirm these do not already exist under a different label.
  • Confirm before build: Slack workspace admin access to install and configure the bot app, Typeform plan tier (must support hidden fields and webhooks), Notion database ID for the testimonial library, and HubSpot custom property names as they will appear in the API.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow for the Customer Testimonial Collection automation. Field names match the property keys used in HubSpot, Typeform, Slack, and Notion API calls.
// ============================================================
// CUSTOMER TESTIMONIAL COLLECTION — END-TO-END DATA FLOW
// Volume: ~30 cycles/month | Agents: 2 | Human steps retained: 1
// ============================================================

// TRIGGER
// Source: HubSpot deal webhook
HubSpot.deal.stage_changed
  -> deal.id           : string   // e.g. '12345678'
  -> deal.stage        : string   // Must equal confirmed 'Closed Won' or 'Completed' label
  -> deal.name         : string   // e.g. 'Acme Corp — Website Redesign Q2'
  -> deal.closedate    : date     // Anchor for follow-up timing
  -> contact.id        : string   // Primary associated contact ID
  -> contact.email     : string   // Recipient address
  -> contact.firstname : string
  -> contact.lastname  : string

// ============================================================
// AGENT 1: Outreach Sequence Agent
// ============================================================

// STEP 1 — Dedupe check
HubSpot.notes.query(contact.id, tag='testimonial_outreach', days=90)
  -> IF note_found == true  : halt; log 'duplicate suppressed' to error_log
  -> IF note_found == false : continue

// STEP 2 — Send initial testimonial request email
Gmail.send(
  to         : contact.email,
  from       : manager_oauth_address,         // OAuth-authenticated Marketing Manager account
  subject    : 'A quick favour, {contact.firstname}',
  body       : template_testimonial_request_v1,  // Populated with deal.name, contact.firstname
  typeform_link: 'https://form.typeform.com/to/{FORM_ID}#hubspot_contact_id={contact.id}'
)
  -> gmail.message_id_initial : string        // Stored for thread reference

// STEP 3 — Log outreach note in HubSpot
HubSpot.notes.create(
  contact_id : contact.id,
  body       : 'Testimonial outreach sent {send_date}; template: testimonial_request_v1',
  tag        : 'testimonial_outreach'
)
  -> hubspot.note.id : string

// STEP 4 — Schedule follow-up 1 (day 7 from deal.closedate)
// Before send: check typeform.submission_received for contact.id
IF typeform.submission_received == false:
  Gmail.send(
    to      : contact.email,
    subject : 'Just checking in, {contact.firstname}',
    body    : template_followup_1_v1
  )
  -> gmail.message_id_followup1 : string
ELSE: cancel; log 'follow-up 1 suppressed — submission received'

// STEP 5 — Schedule follow-up 2 (day 14 from deal.closedate)
// Before send: check typeform.submission_received for contact.id
IF typeform.submission_received == false:
  Gmail.send(
    to      : contact.email,
    subject : 'Last chance to share your experience',
    body    : template_followup_2_v1
  )
  -> gmail.message_id_followup2 : string
ELSE: cancel; log 'follow-up 2 suppressed — submission received'

// ============================================================
// HANDOFF: Outreach Sequence Agent -> Submission Review and Library Agent
// Trigger: Typeform webhook fires on new submission
// ============================================================

Typeform.webhook.receive(
  form_id    : '{FORM_ID}',              // Validated against expected form ID
  signature  : Typeform-Signature header // Must pass HMAC-SHA256 verification
)
  -> typeform.response_id          : string
  -> typeform.submitted_at         : datetime
  -> typeform.answers.testimonial_text : string
  -> typeform.answers.customer_name    : string
  -> typeform.answers.customer_role    : string
  -> typeform.answers.permission_given : boolean
  -> typeform.hidden.hubspot_contact_id: string

// ============================================================
// AGENT 2: Submission Review and Library Agent
// ============================================================

// STEP 6 — Consent and dedupe validation
IF typeform.answers.permission_given == false:
  halt; log 'consent_not_given' to error_log; post Slack alert to #marketing
IF Notion.query(response_id == typeform.response_id).exists:
  halt; log 'duplicate_suppressed' to error_log

// STEP 7 — Post Slack approval card
Slack.post_message(
  channel : '#marketing-approvals',
  blocks  : [
    header      : 'New testimonial submission — action required',
    customer    : '{typeform.answers.customer_name}, {typeform.answers.customer_role}',
    quote       : '{typeform.answers.testimonial_text}',
    button_1    : { text: 'Approve', action_id: 'approve_testimonial', value: 'approve' },
    button_2    : { text: 'Flag for Review', action_id: 'flag_testimonial', value: 'flag_for_review' }
  ]
)
  -> slack.message_ts : string             // Used to update card after action

// STEP 8 — Wait for Slack interactive action callback
Slack.interaction.receive(
  action_id : 'approve_testimonial' | 'flag_testimonial',
  user_id   : string,
  action_ts : string
)

// BRANCH A — Approved
IF slack.action.value == 'approve':

  // STEP 9 — Write to Notion testimonial library
  Notion.pages.create(
    database_id       : '{NOTION_DB_ID}',
    customer_name     : typeform.answers.customer_name,    // Title property
    customer_role     : typeform.answers.customer_role,
    testimonial_text  : typeform.answers.testimonial_text,
    approval_date     : now(),
    hubspot_contact_id: typeform.hidden.hubspot_contact_id,
    response_id       : typeform.response_id,              // Dedup field
    status            : 'Approved'
  )
  -> notion.page_id : string

  // STEP 10 — Update HubSpot contact record
  HubSpot.contacts.update(
    contact_id                   : typeform.hidden.hubspot_contact_id,
    testimonial_collected        : true,
    testimonial_approval_date    : now()
  )

  // STEP 11 — Post Slack confirmation to #marketing
  Slack.post_message(
    channel : '#marketing',
    text    : 'New testimonial saved to library: {typeform.answers.customer_name} — {typeform.answers.customer_role}'
  )
  Slack.update_message(ts: slack.message_ts, text: 'Approved by {slack.action.user_id}')

// BRANCH B — Flagged for Review
IF slack.action.value == 'flag_for_review':
  Notion.pages.create(... status: 'Flagged') // Held record, not published
  Slack.post_thread_reply(
    ts   : slack.message_ts,
    text : 'Submission held — please review and approve manually before library write.'
  )

// ============================================================
// END OF FLOW
// ============================================================
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation platform configured with one workflow per agent: 'Outreach Sequence Agent' and 'Submission Review and Library Agent'. Both workflows share a single credential store for HubSpot, Gmail (OAuth), Typeform, Slack, and Notion. No credentials are hardcoded in workflow nodes. All secrets are stored as named environment variables or credential objects within the platform's secure credential manager.
Webhook configuration
Two inbound webhooks are required. (1) HubSpot deal stage webhook: configured in HubSpot workflow settings to POST to the orchestration platform's inbound URL on deal stage change. Payload must include deal ID, stage name, close date, and associated contact ID. (2) Typeform submission webhook: configured in Typeform Connect settings for the dedicated testimonial form. The endpoint URL is the Submission Review and Library Agent's inbound trigger URL. HMAC-SHA256 signature verification using the Typeform webhook secret must be enabled and validated on every incoming request. Slack interactive actions callback URL must be registered in the Slack app's Interactivity and Shortcuts settings, pointing to the Submission Review and Library Agent's action handler endpoint.
Templating approach
All three Gmail email templates (initial request, day-7 follow-up, day-14 follow-up) are stored as named template objects within the orchestration platform or as plain-text/HTML files in a version-controlled repository. Templates use double-brace variable syntax (for example, {{contact.firstname}}, {{deal.name}}) resolved at send time from the deal and contact payload. The Slack approval card uses Slack Block Kit JSON composed programmatically within the workflow, not a static payload, to ensure customer name, role, and testimonial text are injected correctly on each invocation.
Error logging
A Supabase table named 'automation_error_log' captures all workflow exceptions, halted paths, duplicate suppressions, and consent failures. Each row records: workflow_name, error_type, contact_id, deal_id, timestamp, and error_detail. A Slack alert to the #marketing-ops channel fires automatically whenever a row is written to this table with error_type of 'consent_not_given', 'webhook_signature_failed', or 'notion_write_failed'. Non-critical suppression events (duplicate, follow-up cancelled) are logged to the same table with a 'suppressed' severity label and do not trigger a Slack alert.
Testing approach
All builds begin in a sandbox environment: a cloned HubSpot pipeline with test contacts, a Typeform test response endpoint, the Slack approval card posted to a private #uat-testing channel, and a separate Notion database flagged as 'UAT'. No live customer data is used during build or testing phases. OAuth credentials for Gmail are connected to a dedicated test sending address during development and switched to the production Marketing Manager account only at go-live. End-to-end test runs cover: valid deal trigger, dedupe suppression, day-7 and day-14 follow-up firing and cancellation, Typeform consent failure, duplicate submission rejection, Slack approve path, Slack flag path, Notion write, and HubSpot property update.
Estimated total build time
Outreach Sequence Agent: 12 hours. Submission Review and Library Agent: 14 hours. End-to-end integration testing and UAT: 6 hours (included in the 28-hour total build effort from the process snapshot). Grand total: 28 hours across a 3 to 4 week delivery window as detailed in the delivery schedule.
Before any build work begins, the FullSpec team requires the following confirmed from the owner side: (1) exact HubSpot deal stage label(s) that trigger outreach, (2) OAuth consent granted by the Gmail account holder for the sending address, (3) Typeform form ID for the dedicated testimonial form with all required fields present, (4) Slack workspace admin access for bot installation with interactive message permissions, (5) Notion database ID for the testimonial library with the correct property schema applied, and (6) confirmation of HubSpot custom property names for 'testimonial_collected' and 'testimonial_approval_date'. Contact the FullSpec team at support@gofullspec.com with these details to initiate the build.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Customer Testimonial Collection.

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