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
Insurance Renewal Management
[YourCompany.com] · Legal Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for the Insurance Renewal Management automation build. It covers the current-state process map with bottleneck identification, full specifications for each of the three agents, an end-to-end data flow trace, and the recommended build stack. The FullSpec team is responsible for the entire build, test, and deployment cycle. Your team's input is limited to confirming tool access, supplying broker contact data, and validating the policy register schema before build begins.
01Process snapshot
02Agent specifications
Monitors the Notion policy register on a daily schedule and identifies any policy record whose expiry date falls within the 90-day, 60-day, 30-day, or 7-day warning windows. For each matching record, the agent fires a staged outreach email to the designated broker contact via Gmail, logs the outreach event in HubSpot as a contact activity against the broker record, starts a 7-day chase timer in HubSpot, and posts a Slack notification to the compliance channel confirming outreach status. If the chase timer expires with no quote logged, the agent sends a follow-up email automatically and resets the timer. This cycle repeats until a quote document is received or the policy is manually marked as resolved. Complexity: Moderate. Estimated build: 8 hours.
// Input
Notion DB row: { policy_id, policy_name, insurer, broker_email, expiry_date, last_outreach_date, status }
Trigger condition: (expiry_date - today) IN [90, 60, 30, 7] days AND status NOT IN ['quote_received', 'resolved']
// Output
Gmail: templated outreach email -> broker_email (policy_id, expiry_date, coverage_type in body)
HubSpot: create activity { contact: broker_email, type: 'renewal_outreach', policy_id, outreach_stage, timestamp }
HubSpot: set follow-up task { due: now + 7 days, policy_id, action: 'chase_email' }
Slack: post to #compliance-renewals { text: 'Outreach sent for [policy_name] expiring [expiry_date]. Stage: [outreach_stage].' }
Notion: update row { last_outreach_date: today, outreach_stage: '90d|60d|30d|7d' }- Notion API tier required: Notion integration with read and write access to the policy register database. The database must have a dedicated date property named expiry_date and a single-select status property. Confirm schema before build starts.
- HubSpot tier required: Starter or above for contact activity logging and task creation via the CRM API. Free tier does not support task creation by API. Confirm tier before build.
- Gmail send credentials must use a dedicated service account or OAuth token scoped to the sending mailbox. The token must have the gmail.send scope only. Do not use a personal account token.
- Broker email addresses must be present on the Notion policy record or mapped to a HubSpot contact before build. A data-prep task is required to verify and populate these fields for all active policies.
- Dedupe behaviour: if the daily poll fires and the policy has already received outreach at the current window stage today (last_outreach_date = today AND outreach_stage matches), the agent must skip that record without sending a duplicate email.
- Fallback: if broker_email is null on a Notion record, post a Slack alert to #compliance-renewals with the policy_id and skip the outreach step. Do not fail silently.
- Chase timer reset: after each follow-up email the HubSpot task due date is extended by 7 days. Maximum of 3 automated chases per window before escalating to a Slack alert for manual review.
Activates when a broker quote document arrives in the designated Gmail intake label or is uploaded to the designated Google Drive intake folder. The agent reads the document using an AI extraction step, pulls premium, excess, exclusions, and coverage limit fields, and compares them line by line against the matching prior policy record stored in Notion. If any premium increase exceeds the configured threshold (default 10%) or any coverage term changes materially, the agent flags the record as requiring human review. Regardless of flag status, the agent assembles a structured comparison summary and posts it to the Slack compliance channel and attaches it to the approval workflow. The Notion record status is updated to quote_received. Complexity: Complex. Estimated build: 10 hours.
// Input
Gmail attachment OR Drive file: { filename, mime_type ('application/pdf' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'), raw_bytes }
Notion prior policy record: { policy_id, prior_premium, prior_excess, prior_exclusions[], prior_coverage_limits{}, insurer, expiry_date }
// Output
Extracted quote fields: { quote_premium, quote_excess, quote_exclusions[], quote_coverage_limits{}, quote_effective_date, insurer_name }
Comparison delta: { premium_delta_pct, coverage_changes[], excess_delta, material_flag: true|false }
Slack: post to #compliance-renewals { comparison_summary_block, material_flag, policy_id, recommendation }
Notion: update row { status: 'quote_received', quote_file_url, comparison_summary_url, material_flag, quote_premium }
// On approval trigger
Passes { policy_id, comparison_summary_url, quote_file_url, approver_email, material_flag } to Approval and Completion Agent- AI document extraction must handle PDF (text-layer) and .docx formats reliably. Image-scanned PDFs (no text layer) require an OCR fallback step. During QA, test at least three real broker quote formats to confirm extraction confidence before go-live.
- Premium threshold for material flag is configurable in the workflow as a variable (default: 10%). This must be exposed as a named parameter so the compliance team can update it without touching the workflow logic.
- Coverage change detection compares string arrays for exclusions and numeric ranges for coverage limits. Any new exclusion string not present in the prior record triggers a material_flag regardless of premium delta.
- Notion prior policy record must contain structured fields for prior_premium, prior_excess, and prior_coverage_limits. If any of these fields are empty, the agent must halt and post a Slack alert rather than producing an inaccurate comparison.
- Google Drive intake folder path must be confirmed and the automation platform service account granted Editor access before build. Confirm folder naming convention with the client before build starts.
- Fallback for unreadable documents: if AI extraction confidence score falls below 0.75, move the file to a /Renewals/Manual-Review folder, update the Notion record status to 'manual_review_required', and post a Slack alert.
- The comparison summary document must be saved back to Google Drive and the URL stored in the Notion record so the Approval and Completion Agent can attach it to the DocuSign envelope.
Triggered when the Quote Comparison Agent updates the Notion record to quote_received and posts the comparison summary to Slack. This agent routes the comparison summary and broker quote to the designated approver via DocuSign, attaches both documents to the envelope, and monitors for a signed response. On sign-off, the agent sends a confirmation acceptance email to the broker via Gmail, creates a draft bill in Xero assigned to the correct supplier record and account code, and writes all new policy details back to the Notion policy register, including the new policy number, premium, coverage dates, and the next renewal trigger date. The final Notion status is set to renewed. Complexity: Moderate. Estimated build: 8 hours.
// Input
Notion record: { policy_id, policy_name, approver_email, comparison_summary_url, quote_file_url, quote_premium, broker_email, insurer, xero_supplier_id, xero_account_code }
// Output (DocuSign sent)
DocuSign envelope: { signer: approver_email, documents: [comparison_summary, broker_quote], subject: 'Approval required: [policy_name] renewal', expiry: +48h }
Notion: update row { docusign_envelope_id, status: 'pending_approval' }
// On approval (DocuSign webhook: envelope status = 'completed')
Gmail: send to broker_email { body: 'We confirm acceptance of the renewal quote for [policy_name]. Please issue final policy documents and invoice.' }
Xero: POST /invoices { type: 'ACCPAY', contact_id: xero_supplier_id, line_items: [{ description: policy_name, unit_amount: quote_premium, account_code: xero_account_code }], status: 'DRAFT' }
Notion: update row { status: 'renewed', new_policy_number, new_expiry_date, next_renewal_trigger_date: new_expiry_date - 90d, renewed_premium: quote_premium, docusign_signed_url }
Slack: post to #compliance-renewals { text: '[policy_name] renewal confirmed. Xero bill drafted. Notion record updated.' }- DocuSign account must have API access enabled (eSignature REST API v2.1). The approver must have an active DocuSign user account or must be configured as a recipient who can sign via email link. Confirm with the client before build.
- DocuSign envelope expiry should be set to 48 hours. If the envelope expires unsigned, the agent must post a Slack escalation alert to #compliance-renewals tagging the compliance manager and reset the envelope.
- Xero bill creation requires the insurer to exist as a Supplier contact in Xero with a valid contact_id, and the correct account code to be confirmed for insurance expense. A data-prep task must verify and create these records for all active insurers before launch.
- The Xero bill is created in DRAFT status only. Accounts payable staff retain responsibility for approving and scheduling payment in Xero. Do not configure the automation to submit or approve bills.
- DocuSign webhook must be configured to POST the envelope completion event to the automation platform's webhook receiver endpoint. Do not use polling as the primary sign-off detection method; polling may be used as a fallback only.
- next_renewal_trigger_date written to Notion must be exactly new_expiry_date minus 90 calendar days. This is the value the Renewal Tracker Agent reads on its next daily poll to initiate the following year's renewal cycle.
- If DocuSign returns a declined status (approver declines to sign), update the Notion record status to 'approval_declined', post a Slack alert, and halt. Do not re-send the envelope automatically.
03End-to-end data flow
// ─── TRIGGER ─────────────────────────────────────────────────────────────────
// Orchestration layer polls Notion daily at 07:00 UTC
Notion.query_database({
filter: { property: 'expiry_date', date: { on_or_before: today + 90d } },
filter_and: { property: 'status', select: { does_not_equal: 'renewed' } }
}) -> List<PolicyRecord>
// ─── AGENT 1: RENEWAL TRACKER AGENT ─────────────────────────────────────────
// Input fields consumed
PolicyRecord: {
policy_id, // Notion page ID
policy_name, // e.g. 'Public Liability 2025'
insurer, // e.g. 'Allianz'
broker_email, // e.g. 'renewals@brokerco.com'
expiry_date, // ISO 8601 date
last_outreach_date,
outreach_stage, // '90d' | '60d' | '30d' | '7d'
status // 'active' | 'outreach_sent' | 'chasing' | ...
}
// Dedupe check: skip if outreach_stage matches today's window AND last_outreach_date = today
if (dedupe_check == false) {
Gmail.send({ to: broker_email, template: 'renewal_request', vars: { policy_id, expiry_date, coverage_type } })
HubSpot.create_activity({ contact: broker_email, type: 'renewal_outreach', policy_id, outreach_stage, timestamp: now })
HubSpot.create_task({ due: now + 7d, policy_id, action: 'chase_email' })
Slack.post({ channel: '#compliance-renewals', text: 'Outreach sent for [policy_name] expiring [expiry_date]. Stage: [outreach_stage].' })
Notion.update_page({ policy_id, last_outreach_date: today, outreach_stage })
}
// Chase loop: HubSpot task fires at due date if status != 'quote_received'
// Maximum 3 chases per window; escalate to Slack on breach
// ─── AGENT HANDOFF 1 -> 2 ────────────────────────────────────────────────────
// Trigger: new email in Gmail label 'Renewals/Intake' OR new file in Drive '/Renewals/Intake/{year}'
// Extracted from email/file metadata: { policy_id (parsed from subject/filename), filename, mime_type, raw_bytes }
// ─── AGENT 2: QUOTE COMPARISON AGENT ────────────────────────────────────────
// AI extraction step
ai_extract(raw_bytes) -> QuoteFields: {
quote_premium, // numeric, e.g. 4200.00
quote_excess, // numeric
quote_exclusions[], // string array
quote_coverage_limits, // object { public_liability, employers_liability, ... }
quote_effective_date, // ISO 8601
insurer_name,
extraction_confidence // float 0.0 - 1.0
}
// Confidence gate: if extraction_confidence < 0.75 -> move to /Renewals/Manual-Review, Slack alert, halt
// Prior policy lookup
Notion.get_page(policy_id) -> PriorRecord: {
prior_premium, prior_excess, prior_exclusions[], prior_coverage_limits{}
}
// Comparison delta
delta: {
premium_delta_pct: ((quote_premium - prior_premium) / prior_premium) * 100,
new_exclusions: quote_exclusions[].filter(e => !prior_exclusions[].includes(e)),
limit_changes: diff(quote_coverage_limits, prior_coverage_limits),
excess_delta: quote_excess - prior_excess
}
material_flag = (delta.premium_delta_pct > THRESHOLD_PCT) || (delta.new_exclusions.length > 0)
// Outputs
comparison_summary_doc -> saved to Google Drive '/Renewals/Summaries/{policy_id}_{date}.pdf'
Slack.post({ channel: '#compliance-renewals', blocks: [comparison_summary_block], material_flag })
Notion.update_page({ policy_id, status: 'quote_received', quote_file_url, comparison_summary_url, material_flag, quote_premium })
// ─── AGENT HANDOFF 2 -> 3 ────────────────────────────────────────────────────
// Trigger: Notion page status == 'quote_received' AND comparison_summary_url != null
// Passed fields: { policy_id, policy_name, approver_email, comparison_summary_url,
// quote_file_url, quote_premium, broker_email, insurer,
// xero_supplier_id, xero_account_code }
// ─── AGENT 3: APPROVAL AND COMPLETION AGENT ─────────────────────────────────
// DocuSign envelope creation
DocuSign.create_envelope({
signer: { email: approver_email, name: 'CFO' },
documents: [comparison_summary_url, quote_file_url],
subject: 'Approval required: [policy_name] renewal',
expiry_hours: 48
}) -> { envelope_id }
Notion.update_page({ policy_id, docusign_envelope_id, status: 'pending_approval' })
// DocuSign webhook fires on envelope completion
DocuSign.webhook_event: { envelope_id, status: 'completed', signed_document_url }
// On approval
Gmail.send({ to: broker_email, template: 'broker_acceptance', vars: { policy_name, policy_id } })
Xero.create_invoice({
type: 'ACCPAY',
contact_id: xero_supplier_id,
line_items: [{ description: policy_name, unit_amount: quote_premium, account_code: xero_account_code }],
status: 'DRAFT'
}) -> { xero_bill_id }
Notion.update_page({
policy_id,
status: 'renewed',
renewed_premium: quote_premium,
new_expiry_date: quote_effective_date + 365d,
next_renewal_trigger_date: new_expiry_date - 90d,
docusign_signed_url,
xero_bill_id
})
Slack.post({ channel: '#compliance-renewals', text: '[policy_name] renewal confirmed. Xero bill drafted. Notion record updated.' })
// ─── TERMINAL STATE ──────────────────────────────────────────────────────────
// Notion status: 'renewed'
// Renewal cycle complete. next_renewal_trigger_date feeds Agent 1 in the following year.04Recommended build stack
More documents for this process
Every document generated for Insurance Renewal Management.