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
Renewal and Contract Expiry Management
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, configure, and hand over the three-agent renewal automation. It covers the current-state process with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The process owner's responsibilities are limited to providing API credentials, confirming HubSpot field names, and signing off on UAT. All build, test, and deployment work is handled by FullSpec.
01Process snapshot
02Agent specifications
Runs on a daily 8am schedule against HubSpot deal records to identify all active contracts whose expiry date falls within a 90, 60, or 30-day window and where no renewal activity has been logged. It enriches each flagged record with deal value, primary contact details, and last activity date, then scores and ranks the list by days remaining and deal value. The ranked output is written back to HubSpot as a custom property update and mirrored to a Google Sheet for audit purposes. Estimated build time: 10 hours. Complexity: Moderate.
// Input
HubSpot deal query: dealstage = 'active', expiry_date <= TODAY + 90 days
Fields pulled: deal_id, deal_name, expiry_date, deal_value, owner_id,
contact_id, last_activity_date, renewal_status
// Processing
Score = (deal_value * 0.6) + (urgency_weight[days_remaining] * 0.4)
urgency_weight: 0-30 days = 3, 31-60 days = 2, 61-90 days = 1
Filter: exclude deals where renewal_status = 'outreach_active' or 'closed_won'
// Output
HubSpot deal property update: renewal_priority_score, renewal_queue_date
Google Sheet row append: deal_id, deal_name, expiry_date, deal_value,
owner_email, priority_score, queue_timestamp
Payload to Renewal Outreach Agent: ranked_deal_list[]- HubSpot tier must be Sales Hub Starter or above to access deal property write via API. Confirm the connected account has the oauth scope crm.objects.deals.write before build.
- The expiry_date field must be a HubSpot date property (not a text field). The data audit must confirm this field is populated for all active deals before the agent can run. A one-off migration script may be required.
- The Google Sheet must be pre-created with a named tab 'Renewal Queue' and the header row matching the output fields exactly. The sheet ID is stored in the shared credential store.
- Dedupe logic: if a deal already has renewal_queue_date set within the current calendar day, skip the write to avoid duplicate Slack notifications downstream.
- Fallback: if HubSpot API returns a 429 rate-limit response, retry after 60 seconds up to three times, then log the failure to the error table and send a Slack alert to the build team channel.
- Confirm with the process owner which HubSpot pipeline and stages map to 'active contract' before coding the deal query filter.
Receives the ranked deal list from the Contract Monitoring Agent and sends a personalised renewal email from the assigned account manager's connected Gmail account at each of the 90, 60, and 30-day marks. It monitors inbound Gmail threads for client replies using label-based detection and pauses the sequence automatically when a reply is detected on a tracked thread. Each sent and scheduled email is logged as a HubSpot activity on the corresponding deal record with timestamp and sequence stage. Estimated build time: 12 hours. Complexity: Moderate.
// Input
ranked_deal_list[]: { deal_id, deal_name, expiry_date, deal_value,
contact_email, contact_first_name, owner_email,
priority_score, sequence_stage }
sequence_stage: '90_day' | '60_day' | '30_day'
// Processing
Select email template by sequence_stage
Populate template variables: {contact_first_name}, {deal_name},
{expiry_date}, {account_manager_name}
Send via Gmail API using owner OAuth token (per-account-manager credential)
Store thread_id against deal_id in tracking table
Poll Gmail label 'renewal_replied' every 15 min; if thread_id matched, set
HubSpot renewal_status = 'client_replied', pause sequence for deal
// Output
Gmail: sent email from owner_email to contact_email
HubSpot activity log: { deal_id, activity_type: 'email_sent',
sequence_stage, timestamp, gmail_thread_id }
HubSpot deal property update: renewal_status, last_outreach_date
Payload to Approval and Closing Agent (on reply detection or manual trigger):
{ deal_id, deal_name, deal_value, contact_email, owner_email,
sequence_stage, thread_id }- Each account manager must individually authorise the Gmail OAuth connection using their own Google account. The required OAuth scopes are: gmail.send, gmail.readonly, gmail.labels, gmail.modify. A shared inbox or alias connection requires a separate service account flow and must be confirmed before build.
- Three email templates must be provided by the process owner before build starts: 90-day initial outreach, 60-day follow-up, and 30-day final notice. Templates must include the merge field placeholders listed in the IO block.
- Reply detection uses a Gmail label named 'renewal_replied' applied by an automated Gmail filter rule. This filter must be created in the connected Gmail account as part of build setup.
- If the team uses multiple account managers, each will need a separate OAuth credential entry in the shared credential store keyed by HubSpot owner_id.
- Fallback: if a Gmail send fails (token expired or rate limit), log to the error table, flag the deal in HubSpot with renewal_status = 'outreach_failed', and send a Slack alert to the account manager.
- Confirm whether BCC or blind logging to a shared inbox is required for compliance before configuring the send step.
Manages the internal approval step, DocuSign contract delivery, and all post-signature write-backs. When a deal is flagged as ready for approval (either by the account manager via a HubSpot property update or by clicking the action button in the Slack summary message), the agent posts a structured Slack approval request to the sales manager's channel. On approval, it generates a DocuSign envelope from the standard renewal template pre-filled with deal terms, sends it to the client contact, and waits for a signed callback. On signature completion it updates the HubSpot deal stage to Renewed, writes the new contract start and end dates, and raises a recurring invoice in Xero. Estimated build time: 14 hours. Complexity: Complex.
// Input
{ deal_id, deal_name, deal_value, contract_start_date, contract_end_date,
contact_email, contact_name, account_manager_name, owner_email,
is_standard_terms: true | false }
// Branch: non-standard terms
IF is_standard_terms = false:
Set HubSpot renewal_status = 'manual_proposal_required'
Send Slack alert to owner_email: 'Custom proposal needed for {deal_name}'
STOP (account manager prepares proposal manually, then re-triggers)
// On standard terms: Slack approval request
Slack message to sales_manager_channel:
{ deal_name, deal_value, contract_end_date, account_manager_name,
action_buttons: ['Approve', 'Decline'] }
Await Slack interactive payload (timeout: 48 hours)
// On approval
DocuSign envelope: template_id (renewal_standard), prefill fields:
{ client_name: contact_name, deal_value, contract_start_date,
contract_end_date, account_manager_name }
Send envelope to contact_email; cc owner_email
Store envelope_id against deal_id in tracking table
// On DocuSign signed webhook callback
HubSpot deal update: { dealstage: 'closed_won_renewed',
contract_start_date, contract_end_date,
renewal_status: 'signed' }
Xero invoice create: { contact_name, line_item: 'Contract Renewal',
amount: deal_value, due_date: contract_start_date,
invoice_type: 'ACCREC', status: 'DRAFT' }
// Output
Slack confirmation to owner_email: 'Contract signed and invoice raised for {deal_name}'
HubSpot deal: updated stage, dates, renewal_status = 'signed'
Xero: draft invoice created (InvoiceID stored on HubSpot deal as custom property)- DocuSign integration requires an account at DocuSign Business Pro or above for template-based envelope creation via API. Confirm the connected DocuSign account has at least one published renewal template before build begins. The template_id must be stored in the credential store.
- The Slack approval message must use Slack Block Kit with interactive buttons. The Slack app must have the scopes chat:write, commands, and interactivity enabled. The app's request URL for interactive payloads must point to the automation platform's public webhook endpoint.
- Slack approval timeout is set at 48 hours. If no action is taken, the agent re-posts a reminder to the sales manager channel and logs the stall to the error table. After 72 hours with no response, it escalates to the HubSpot deal owner via email.
- Xero invoice creation uses the Xero Accounting API (OAuth 2.0). The connected Xero organisation must have a contact record matching the HubSpot contact name or email before the invoice step fires. Mismatched contact names will require a fuzzy-match lookup step in the flow.
- The is_standard_terms flag must be set as a HubSpot deal property (boolean). Confirm the criteria that define a non-standard contract with the process owner before coding the branch logic.
- DocuSign signed status is delivered via a Connect webhook (event: envelope_completed). The automation platform endpoint for this webhook must be registered in the DocuSign admin console and secured with HMAC verification.
- Xero invoices are created in DRAFT status. Confirm with the process owner whether auto-approval to SUBMITTED or AUTHORISED status is acceptable, or whether a finance team member must approve before sending.
03End-to-end data flow
// ============================================================
// TRIGGER
// ============================================================
SCHEDULE: daily cron 08:00 (owner timezone)
OR
WEBHOOK: HubSpot deal.properties.expiry_date updated
HubSpot deal.properties.renewal_status updated
// ============================================================
// AGENT 1: Contract Monitoring Agent
// ============================================================
-> HubSpot Deals API GET /crm/v3/objects/deals
filter: dealstage='active', expiry_date <= today+90, renewal_status != 'outreach_active'
fields: deal_id, deal_name, expiry_date, deal_value, owner_id,
contact_id, last_activity_date, renewal_status
-> Score each deal:
priority_score = (deal_value * 0.6) + (urgency_weight * 0.4)
days_remaining = expiry_date - today
urgency_weight: [0-30]=3, [31-60]=2, [61-90]=1
-> Dedupe check: skip if renewal_queue_date = today (idempotency guard)
-> HubSpot Deals API PATCH /crm/v3/objects/deals/{deal_id}
body: { renewal_priority_score, renewal_queue_date }
-> Google Sheets API POST (append row to 'Renewal Queue' tab)
row: [deal_id, deal_name, expiry_date, deal_value, owner_email,
priority_score, queue_timestamp]
// HANDOFF -> Renewal Outreach Agent
ranked_deal_list: [{ deal_id, deal_name, expiry_date, deal_value,
contact_email, contact_first_name, owner_email,
priority_score, sequence_stage }]
// ============================================================
// AGENT 2: Renewal Outreach Agent
// ============================================================
FOR EACH deal IN ranked_deal_list:
-> Select template by sequence_stage:
'90_day' -> template_90_day_outreach
'60_day' -> template_60_day_followup
'30_day' -> template_30_day_final
-> Merge fields: {contact_first_name}, {deal_name},
{expiry_date}, {account_manager_name}
-> Gmail API POST /gmail/v1/users/{owner_email}/messages/send
from: owner_email (per-account-manager OAuth token)
to: contact_email
subject: templated subject line
body: templated HTML body
response: message_id, thread_id
-> Store in tracking table: { deal_id, thread_id, sequence_stage, sent_at }
-> HubSpot Engagements API POST /crm/v3/objects/emails
body: { dealId: deal_id, from: owner_email, to: contact_email,
timestamp, subject, sequence_stage, gmail_thread_id }
-> HubSpot PATCH deal: { renewal_status: 'outreach_active',
last_outreach_date: today }
// Reply detection loop (runs every 15 min via schedule)
-> Gmail API GET /gmail/v1/users/{owner_email}/threads/{thread_id}
IF thread.messages.length > 1 (reply detected):
Apply Gmail label: 'renewal_replied'
HubSpot PATCH deal: { renewal_status: 'client_replied' }
Pause outreach sequence for deal_id
// HANDOFF -> Approval and Closing Agent
// (triggered by HubSpot deal property: approval_requested = true)
payload: { deal_id, deal_name, deal_value, contract_start_date,
contract_end_date, contact_email, contact_name,
account_manager_name, owner_email, is_standard_terms }
// ============================================================
// AGENT 3: Approval and Closing Agent
// ============================================================
IF is_standard_terms = false:
-> HubSpot PATCH deal: { renewal_status: 'manual_proposal_required' }
-> Slack API POST /chat.postMessage (to owner_email DM)
text: 'Custom proposal required for {deal_name} — please prepare manually'
STOP
IF is_standard_terms = true:
-> Slack API POST /chat.postMessage (to #sales-approvals channel)
Block Kit message: { deal_name, deal_value, contract_end_date,
account_manager_name, buttons: [Approve, Decline] }
-> Await Slack interactive payload callback
action_id: 'approval_approve' | 'approval_decline'
timeout: 48h -> re-notify; 72h -> escalate via email to owner_email
IF action_id = 'approval_approve':
-> DocuSign Envelopes API POST /v2.1/accounts/{account_id}/envelopes
templateId: renewal_standard_template_id
templateRoles: [{ roleName: 'Client', email: contact_email,
name: contact_name },
{ roleName: 'AccountManager', email: owner_email,
name: account_manager_name }]
prefill: { deal_value, contract_start_date, contract_end_date }
response: envelope_id
-> Store in tracking table: { deal_id, envelope_id, sent_at }
-> HubSpot PATCH deal: { renewal_status: 'contract_sent',
docusign_envelope_id: envelope_id }
IF action_id = 'approval_decline':
-> HubSpot PATCH deal: { renewal_status: 'approval_declined' }
-> Slack DM to owner_email: 'Approval declined for {deal_name}'
STOP
// DocuSign signed webhook callback
// POST {platform_webhook_url}/docusign/signed
// Verified via HMAC header X-DocuSign-Signature-1
envelope_id -> lookup deal_id in tracking table
-> HubSpot PATCH deal: { dealstage: 'closed_won_renewed',
contract_start_date, contract_end_date,
renewal_status: 'signed' }
-> Xero Accounting API POST /api.xro/2.0/Invoices
body: { Type: 'ACCREC', Status: 'DRAFT',
Contact: { Name: contact_name },
LineItems: [{ Description: 'Contract Renewal - {deal_name}',
Quantity: 1, UnitAmount: deal_value }],
DueDate: contract_start_date }
response: invoice_id
-> HubSpot PATCH deal: { xero_invoice_id: invoice_id }
-> Slack API POST /chat.postMessage (DM to owner_email)
text: 'Contract signed and invoice raised for {deal_name}. Invoice ID: {invoice_id}'
// ============================================================
// END OF FLOW
// ============================================================04Recommended build stack
More documents for this process
Every document generated for Renewal & Contract Expiry Management.