Back to Proposal Creation

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

Proposal Creation Automation

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

This document is the authoritative technical reference for the FullSpec team building the Proposal Creation automation. It covers the current-state step map, full agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. Everything needed to begin implementation without additional discovery is contained here. Where a field name, variable, or rule is marked as requiring confirmation, that item must be resolved before the relevant agent is wired to a live credential.

01Process snapshot

Step
Name
Description
1
Confirm Deal Details in CRM
The rep opens the HubSpot deal record and verifies contact details, deal value, and product selections. Missing fields are chased via email or Slack before drafting begins. Time cost: 15 min.
2
Pull Correct Pricing From Spreadsheet
The rep locates the current pricing tier in a shared Google Sheet, cross-checking product line, volume, and any active discounts. An outdated sheet version is a common error source. Time cost: 20 min. BOTTLENECK.
3
Open and Duplicate Proposal Template
The rep finds the correct template in PandaDoc and creates a fresh copy, renaming it to match the prospect. Time cost: 10 min.
4
Populate Proposal With Deal Data
Contact name, company, pricing rows, scope description, and terms are typed or pasted into the document. Most typos and stale copy enter here. Time cost: 40 min. BOTTLENECK.
5
Internal Review and Approval
The draft is sent to the sales manager via Slack or email. The manager replies with edits or approval, often after several hours or the next business day. Time cost: 60 min. BOTTLENECK.
6
Incorporate Manager Edits
The rep reads feedback, applies requested changes, and may send a revised version for a second confirmation if edits were significant. Time cost: 20 min.
7
Convert and Send Proposal to Prospect
The approved document is sent as a PandaDoc link via Gmail. The rep manually records the send date in HubSpot. Time cost: 15 min.
8
Log Activity and Update Deal Stage
The rep returns to HubSpot, logs the proposal send as an activity, and moves the deal to 'Proposal Sent'. Often skipped when reps are busy. Time cost: 10 min.
Time cost summary: Total manual time per proposal cycle is 190 minutes (approximately 3 hours and 10 minutes). At a run rate of approximately 20 proposals per month, that equals roughly 63 hours per month and 7 hours per week of manual labour across the sales team. The Proposal Assembly Agent eliminates steps 1, 2, 3, and 4 (85 min per cycle). The Approval and Delivery Agent eliminates steps 5, 7, and 8 (85 min per cycle). Step 6 (manager edits, 20 min) remains a conditional human step. The one non-negotiable human action is the manager's Approve or Request Changes click in Slack.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Proposal Assembly Agent

Fires on a HubSpot webhook when a deal moves to the 'Proposal Requested' stage. The agent reads all deal properties from HubSpot, resolves the correct pricing tier and any applicable discount rules against the documented pricing logic, validates that all required template fields are present, and creates a fully populated PandaDoc document from the master template. If any required field is missing or unpopulated, the agent halts, logs the error, and posts a Slack notification to the deal owner before exiting. No document is created unless all required fields resolve cleanly. Build complexity is Moderate; the pricing resolution logic and field-validation layer are the primary complexity drivers.

Trigger
HubSpot webhook: deal property 'dealstage' changes to value 'Proposal Requested' (internal HubSpot stage ID to be confirmed during discovery).
Tools
HubSpot (deal property read), PandaDoc (document create from template), Google Docs (reference-only: pricing rule source during build; replaced by encoded logic at runtime).
Replaces steps
Steps 1, 2, 3, and 4 (85 min/cycle eliminated).
Estimated build
16 hours. Complexity: Moderate.
// Input: HubSpot deal webhook payload
deal_id            : string   // HubSpot internal deal ID
dealstage          : string   // must equal 'Proposal Requested' stage ID
contact_id         : string   // associated contact record ID
company_id         : string   // associated company record ID
deal_name          : string   // used as proposal document title
deal_value         : number   // USD, used for pricing tier resolution
product_line       : string   // enum: must match documented tier keys
discount_code      : string?  // optional; validated against discount table
contact_firstname  : string
contact_lastname   : string
contact_email      : string
company_name       : string
proposal_expiry_days : number // default 14 if not set on deal

// Output: PandaDoc document object
pandadoc_doc_id    : string   // stored back to HubSpot deal field
pandadoc_doc_url   : string   // preview link passed to Approval and Delivery Agent
resolved_unit_price: number   // pricing tier output, USD
resolved_discount  : number   // percentage, 0 if none
resolved_total     : number   // final proposal value, USD
doc_status         : string   // 'draft' on success; 'error' if field validation fails
missing_fields     : string[] // populated only on error; lists field names that blocked generation
  • HubSpot tier requirement: the connected HubSpot account must be on Sales Hub Starter or above to expose deal property webhooks. Confirm account tier before configuring the trigger.
  • PandaDoc tier requirement: the account must be on the Business tier or above to access the Document Create from Template API endpoint. Confirm template ID and variable schema before build.
  • Pricing resolution logic must be fully documented before this agent is built. All product lines, volume bands, and discount rules must exist as a structured reference in the automation platform credential store or an internal lookup table. Do not read from the live Google Sheet at runtime.
  • PandaDoc template variable names must match HubSpot deal field API names exactly, or be explicitly mapped in the agent. A mismatch produces a blank field in the generated document with no API error thrown. Validate the full variable list against the confirmed PandaDoc template before wiring.
  • Field validation gate: if any of the following fields are null or empty on the incoming deal, the agent must halt and not create a document: contact_email, company_name, deal_value, product_line. Log the missing field names and post a Slack alert to the deal owner.
  • Dedupe behaviour: if a document already exists in PandaDoc with the same deal_id stored on the HubSpot deal record, the agent must skip creation and post an alert rather than creating a duplicate. Check the 'pandadoc_document_id' custom HubSpot field before calling the create endpoint.
  • Multi-product deals: if a single deal record contains multiple product line entries (HubSpot line items), the agent must iterate and produce one pricing row per line item in the PandaDoc token map. Confirm whether the PandaDoc template supports a line-item repeater block or requires a fixed number of rows.
  • Google Docs is listed in the template as a connected tool. At runtime, the agent does not read from Google Docs. The Google Docs connection is used only during the discovery and pricing-mapping phase. Confirm with the process owner that all pricing rules have been exported before build begins.
Approval and Delivery Agent

Fires when the Proposal Assembly Agent marks the PandaDoc document as complete and passes the document URL and deal metadata to this agent. It posts a structured Slack message to the designated sales manager channel, containing the prospect name, deal value, resolved pricing summary, a PandaDoc preview link, and two inline interactive buttons: Approve and Request Changes. On Approve, the agent immediately sends the proposal to the prospect via Gmail using a pre-approved email template, then updates the HubSpot deal stage to 'Proposal Sent', logs a timestamped activity, and stores the PandaDoc document ID on the deal record. On Request Changes, the agent posts the manager's comment back to the rep in Slack and pauses the send flow. A timed reminder re-posts the approval request to Slack if no response is received within 4 hours. Build complexity is Moderate; the Slack interactive message handler and the two-branch approval logic are the primary complexity drivers.

Trigger
Internal: Proposal Assembly Agent emits a completion event with doc_status = 'draft'. This passes pandadoc_doc_url, pandadoc_doc_id, deal_id, deal_name, contact_email, contact_firstname, company_name, resolved_total, and deal_owner_slack_id to this agent.
Tools
Slack (interactive message post and response handling), PandaDoc (document status read), Gmail (proposal send to prospect), HubSpot (deal stage update and activity log).
Replaces steps
Steps 5, 7, and 8 (85 min/cycle eliminated). Step 6 (manager edits) remains a conditional human step.
Estimated build
14 hours. Complexity: Moderate.
// Input: payload from Proposal Assembly Agent completion event
deal_id              : string
deal_name            : string
contact_firstname    : string
contact_email        : string
company_name         : string
resolved_total       : number   // USD
pandadoc_doc_id      : string
pandadoc_doc_url     : string   // PandaDoc shareable preview link
deal_owner_slack_id  : string   // Slack user ID of the assigned rep
manager_slack_channel: string   // confirmed channel ID for approval messages

// On approval (manager clicks Approve in Slack)
approval_action      : 'approved'
approved_by_slack_id : string
approved_at          : ISO8601 timestamp

// On change request (manager clicks Request Changes)
approval_action      : 'changes_requested'
manager_comment      : string   // free-text field from Slack modal

// Output: proposal sent path
gmail_message_id     : string   // returned by Gmail send API
sent_at              : ISO8601 timestamp
hubspot_activity_id  : string   // logged engagement ID
hubspot_deal_stage   : 'Proposal Sent'
pandadoc_doc_id_stored: string  // written to custom HubSpot deal field

// Output: change request path
slack_notification_sent_to: string  // deal_owner_slack_id
flow_status          : 'paused_awaiting_rep_edits'
  • Slack bot configuration: the bot must be installed to the workspace with scopes chat:write, chat:write.public, and channels:read. Interactive components (approve/request-changes buttons) require the Slack app to have an Interactivity Request URL configured pointing to the automation platform's inbound webhook endpoint.
  • Slack interactive message callback: the automation platform must expose a publicly reachable HTTPS endpoint to receive Slack action payloads. Validate the Slack signing secret on every inbound request before processing.
  • Fallback reminder: if no Slack response (approve or request changes) is received within 4 hours of the initial post, the agent re-posts the approval message as a direct message to the manager's Slack user ID and logs a warning to the error table. Confirm the manager's Slack user ID during discovery.
  • Gmail send: the sending Gmail account must be authenticated via OAuth 2.0. The approval and delivery agent uses a pre-approved email template stored in the automation platform; the template must be confirmed with the process owner before go-live. The Gmail API send scope (gmail.send) is required; do not use a broader scope.
  • HubSpot deal stage update: the internal HubSpot pipeline stage ID for 'Proposal Sent' must be confirmed before wiring. Stage IDs are pipeline-specific and are not the same as the display label.
  • PandaDoc document ID storage: a custom HubSpot deal property named 'pandadoc_document_id' (type: single-line text) must exist on the deal record before this agent runs. Confirm the property API name matches exactly.
  • Error fallback: if the Gmail send call fails, the agent must not mark the HubSpot deal as 'Proposal Sent'. Log the failure, post a Slack alert to the deal owner, and leave the deal stage unchanged so the error is visible in the pipeline.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow for the Proposal Creation automation. Field names are as they must appear in the automation platform payload at each stage.
// ─────────────────────────────────────────────────────────────────
// TRIGGER: HubSpot webhook fires on deal stage change
// ─────────────────────────────────────────────────────────────────
HubSpot.webhook.dealstage -> 'Proposal Requested'
  payload.deal_id             : string
  payload.dealstage           : string  // stage ID, not label
  payload.contact_id          : string
  payload.company_id          : string
  payload.deal_name           : string
  payload.deal_value          : number
  payload.product_line        : string  // or line_items[] for multi-product
  payload.discount_code       : string?
  payload.contact_firstname   : string
  payload.contact_lastname    : string
  payload.contact_email       : string
  payload.company_name        : string
  payload.proposal_expiry_days: number
  payload.deal_owner_slack_id : string

// ─────────────────────────────────────────────────────────────────
// AGENT HANDOFF 1: Proposal Assembly Agent begins
// ─────────────────────────────────────────────────────────────────

// STEP 1: Field validation gate
validate([contact_email, company_name, deal_value, product_line])
  ON missing_fields.length > 0:
    -> log.error({ deal_id, missing_fields, timestamp })
    -> Slack.postMessage(deal_owner_slack_id, 'Proposal halted: missing fields: {missing_fields}')
    -> EXIT

// STEP 2: Dedupe check
HubSpot.getDealProperty(deal_id, 'pandadoc_document_id')
  ON value != null:
    -> log.warn({ deal_id, existing_doc_id: value })
    -> Slack.postMessage(deal_owner_slack_id, 'Document already exists for this deal.')
    -> EXIT

// STEP 3: Pricing resolution
PricingLookup.resolve({
  product_line,
  deal_value,
  discount_code
})
  -> resolved_unit_price : number
  -> resolved_discount   : number  // percentage
  -> resolved_total      : number  // USD

// STEP 4: PandaDoc document creation
PandaDoc.createDocument({
  template_id        : '<confirmed_template_uuid>',
  name               : deal_name + ' - Proposal',
  recipients         : [{ email: contact_email, role: 'Signer' }],
  tokens             : {
    '[contact.firstname]'    : contact_firstname,
    '[contact.lastname]'     : contact_lastname,
    '[company.name]'         : company_name,
    '[deal.unit_price]'      : resolved_unit_price,
    '[deal.discount]'        : resolved_discount,
    '[deal.total]'           : resolved_total,
    '[deal.expiry_date]'     : now() + proposal_expiry_days,
    '[deal.scope]'           : product_line
  }
})
  -> pandadoc_doc_id  : string
  -> pandadoc_doc_url : string
  -> doc_status       : 'draft'

// ───────────────���─────────────────────────────────────────────────
// AGENT HANDOFF 2: Approval and Delivery Agent begins
// Receives: deal_id, deal_name, contact_firstname, contact_email,
//           company_name, resolved_total, pandadoc_doc_id,
//           pandadoc_doc_url, deal_owner_slack_id
// ─────────────────────────────────────────────────────────────────

// STEP 5: Post Slack approval message
Slack.postInteractiveMessage({
  channel  : manager_slack_channel,
  text     : 'Proposal ready for review: {deal_name} | {company_name} | ${resolved_total}',
  blocks   : [preview_link(pandadoc_doc_url), button('Approve'), button('Request Changes')]
})
  -> slack_message_ts : string  // used to update message on response

// STEP 6a: Timed fallback (no response within 4 hours)
timer.wait(14400 seconds)
  ON no_response:
    -> Slack.postDirectMessage(manager_slack_id, 'Reminder: approval pending for {deal_name}')
    -> log.warn({ deal_id, event: 'approval_reminder_sent', timestamp })

// STEP 6b: Manager responds — Approve branch
Slack.action.callback({ action_id: 'approve', approved_by_slack_id, approved_at })

Gmail.send({
  to      : contact_email,
  subject : 'Your proposal from [YourCompany.com]: {deal_name}',
  body    : template('proposal_send_email', { contact_firstname, company_name, pandadoc_doc_url })
})
  -> gmail_message_id : string
  -> sent_at          : ISO8601

HubSpot.updateDeal(deal_id, {
  dealstage              : '<Proposal Sent stage ID>',
  pandadoc_document_id   : pandadoc_doc_id
})

HubSpot.logActivity(deal_id, {
  type      : 'EMAIL',
  subject   : 'Proposal sent to {company_name}',
  timestamp : sent_at
})
  -> hubspot_activity_id : string

// STEP 6c: Manager responds — Request Changes branch
Slack.action.callback({ action_id: 'request_changes', manager_comment })

Slack.postDirectMessage(deal_owner_slack_id, {
  text  : 'Manager requested changes on {deal_name}: {manager_comment}',
  link  : pandadoc_doc_url
})
  -> flow_status : 'paused_awaiting_rep_edits'

// ─────────────────────────────────────────────────────────────────
// END STATE (approve path)
// HubSpot deal: stage = 'Proposal Sent', pandadoc_document_id stored
// PandaDoc: document in 'sent' status, open tracking active
// Gmail: proposal email delivered to prospect
// Slack: approval message updated to 'Approved by {manager}' status
// ─────────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform to be confirmed at build kick-off). One discrete workflow per agent: 'Proposal Assembly Workflow' and 'Approval and Delivery Workflow'. Both workflows share a single credential store so HubSpot, PandaDoc, Slack, and Gmail OAuth tokens are managed centrally and rotated without touching individual workflow steps.
Webhook configuration
HubSpot outbound webhook: configured on the 'Proposal Requested' deal stage transition in HubSpot Workflow settings. The automation platform inbound webhook URL is registered as the target. HMAC signature verification must be enabled using the HubSpot webhook secret. Slack interactive components: a second inbound webhook endpoint on the automation platform receives Slack action payloads. The Slack signing secret is validated on every inbound request using HMAC-SHA256 before any branch logic runs.
Templating approach
PandaDoc token-based templating: all variable fields in the master PandaDoc template use the PandaDoc token syntax ('[field_name]'). The token map is assembled as a structured object inside the automation platform and passed to the PandaDoc Create Document API call in a single request. No server-side template rendering library is required. The Gmail proposal email uses a static HTML template stored in the automation platform with mustache-style placeholders ({contact_firstname}, {pandadoc_doc_url}) resolved at send time.
Error logging
All agent errors (missing fields, API failures, dedupe hits, timed-out approvals) are written to a Supabase table named 'proposal_automation_errors' with columns: id (uuid), deal_id (text), error_type (text), missing_fields (jsonb), api_response_code (integer), timestamp (timestamptz), resolved (boolean). A Supabase alert rule triggers a Slack message to the designated ops channel ('automation-alerts') whenever a new unresolved error row is inserted. The ops channel ID and Supabase project URL must be confirmed before deployment.
Testing approach
Sandbox-first on all API connections: HubSpot sandbox account, PandaDoc sandbox environment, and Slack test workspace are used for all development and pre-production testing. Gmail send is tested against an internal alias (not a live prospect address) until the QA sign-off phase. Ten real deal records (anonymised) are passed through the Proposal Assembly Agent during integration testing to validate pricing resolution and template output before connecting the Approval and Delivery Agent. Full end-to-end flow (trigger to HubSpot update) is run five times with representative deal types before pilot launch.
Estimated total build time
Proposal Assembly Agent: 16 hours. Approval and Delivery Agent: 14 hours. End-to-end integration testing and QA: 6 hours. Total: 36 hours.
Before build kick-off, the following items must be confirmed and documented in the credential store: (1) HubSpot deal stage ID for 'Proposal Requested' and 'Proposal Sent', (2) PandaDoc master template UUID and full token variable list, (3) Slack bot OAuth token, signing secret, manager channel ID, and manager Slack user ID, (4) Gmail OAuth 2.0 credentials scoped to gmail.send only, (5) the 'pandadoc_document_id' custom HubSpot deal property API name, and (6) the full pricing tier and discount rule table exported from the shared spreadsheet. Any item not confirmed at kick-off is a build blocker for the agent that depends on it.
Support contact for build queries and credential issues: support@gofullspec.com
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Proposal Creation.

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