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
Accounts Receivable & Chasing
[YourCompany.com] · Finance Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, configure, and hand over the AR Chase Agent. It covers the current-state step map, the full agent specification with IO contracts, the end-to-end data flow, and the recommended build stack. The process owner's responsibilities are limited to credential access and template sign-off. FullSpec handles all build, integration, and testing work described here.
01Process snapshot
02Agent specifications
The AR Chase Agent runs on a daily schedule and is the single automated actor in this process. It polls Xero for all invoices that have passed their due date with an outstanding balance, reads the Google Sheets chase log to determine the reminder tier for each invoice, composes and sends the appropriate templated reminder via Gmail, appends a new row to the chase log, evaluates whether the invoice meets the escalation threshold (60 or more days overdue, or three or more reminders sent without payment), posts a formatted Slack alert to the finance channel when that threshold is met, monitors the Xero bank feed for incoming payments that match open invoices, marks matched invoices as paid in Xero, and writes a closing note to the HubSpot contact record. The agent is stateless between runs: all state is held in the Google Sheets log and in Xero. Estimated build time: 18 hours. Complexity: Moderate.
// Input xero.invoice_id : string // Xero UUID for the overdue invoice xero.contact_id : string // Xero UUID for the debtor contact xero.contact_name : string // Full name of the debtor xero.contact_email : string // Primary email from Xero contact record xero.invoice_number : string // e.g. INV-0042 xero.amount_due : number // Outstanding balance in USD xero.due_date : date // Original due date (ISO 8601) xero.days_overdue : integer // Calculated at poll time: today - due_date sheets.reminder_count : integer // Number of reminders already sent (0 if none) sheets.last_reminder_date: date // Date of most recent reminder (null if none) // Output gmail.message_id : string // ID of the sent reminder email gmail.subject : string // Subject line used gmail.reminder_tier : string // 'polite' | 'firm' | 'final_notice' sheets.row_appended : boolean // True if log row written successfully sheets.new_reminder_count: integer // Updated reminder count after this run slack.alert_posted : boolean // True if escalation threshold was met and alert sent xero.payment_matched : boolean // True if a bank feed payment was matched and applied hubspot.note_id : string // ID of the note written to the contact record // On escalation (days_overdue >= 60 OR reminder_count >= 3 with no payment) slack.channel : string // '#finance-escalations' or configured equivalent slack.payload.invoice_id : string slack.payload.contact : string slack.payload.amount_due : number slack.payload.days_overdue: integer slack.payload.reminder_count: integer
- Xero API access must be authorised via OAuth 2.0 using a connected app registered in the Xero developer portal. Required scopes: accounting.transactions.read, accounting.contacts.read, accounting.payments.write. The platform credential store must hold the client ID, client secret, and refresh token. Token refresh must be handled automatically by the orchestration layer; do not hard-code access tokens.
- Google Sheets log must have the following named columns before build begins: invoice_id, contact_name, contact_email, invoice_number, amount_due, due_date, days_overdue, reminder_count, last_reminder_date, last_reminder_tier, last_email_subject, payment_matched, notes. The sheet ID and tab name must be stored in the credential store. The agent reads this sheet to determine state; if no row exists for an invoice_id the reminder_count defaults to 0.
- Gmail sending must use the OAuth 2.0 Google Workspace API (gmail.send scope) authenticated as the finance sending address (e.g. accounts@[YourCompany.com]). SMTP fallback is not permitted due to deliverability risk. Confirm the sending address and Google Workspace plan tier supports API send-as before build starts.
- Reminder tier selection logic: reminder_count == 0 sends 'polite' template; reminder_count == 1 or 2 sends 'firm' template; reminder_count >= 3 sends 'final_notice' template. All three templates must be approved and stored in the platform before the agent is activated. Template variables: {{contact_name}}, {{invoice_number}}, {{amount_due}}, {{due_date}}, {{days_overdue}}, {{reminder_count}}.
- Escalation condition: post Slack alert when days_overdue >= 60 OR reminder_count >= 3 and xero.payment_matched == false. Slack alert must include invoice number, contact name, amount due, days overdue, and reminder count. Alert should be posted to a dedicated channel (e.g. '#finance-escalations'). The Slack Bot Token must be stored in the credential store and have chat:write scope.
- Deduplification: before sending any email, the agent must check the Google Sheets log for a row matching the invoice_id where last_reminder_date == today. If a reminder was already sent today for that invoice, skip and log a 'skipped-duplicate' note. This prevents double-sends if the daily trigger fires more than once due to a platform retry.
- Payment matching: the agent polls the Xero bank feed for unreconciled transactions each run. Match is attempted by amount and contact. If a confident match is found (amount within $0.01, same contact), apply the payment via the Xero payments.write endpoint and set payment_matched = true in the Sheets log. If no confident match is found, do not apply and leave for manual reconciliation.
- HubSpot note write: use the HubSpot Engagements API v1 (POST /engagements/v1/engagements) with type NOTE. Associate to the contact by looking up the Xero contact_email against HubSpot contacts. If no matching HubSpot contact is found, log a warning to the error table and skip the HubSpot step without failing the run.
- Confirm the HubSpot plan tier supports private app API tokens. Required scope: crm.objects.contacts.read, crm.objects.contacts.write, engagements.read, engagements.write.
- All credentials (Xero OAuth tokens, Google OAuth tokens, Slack Bot Token, HubSpot private app token, Google Sheets ID) must be stored in the platform's shared credential store and never embedded in workflow node configuration directly.
03End-to-end data flow
// ─── TRIGGER ───────────────────────────────────────────────────────────────
// Orchestration layer executes a daily scheduled poll at 07:00 (business TZ)
XERO.GET /api.xro/2.0/Invoices
params: { Status: 'AUTHORISED', DueDateTo: today, HasAttachments: false }
returns: Invoice[]
Invoice {
InvoiceID : string // xero.invoice_id
InvoiceNumber : string // xero.invoice_number
Contact.ContactID: string // xero.contact_id
Contact.Name : string // xero.contact_name
Contact.EmailAddress: string // xero.contact_email
AmountDue : number // xero.amount_due
DueDate : date // xero.due_date
}
computed: xero.days_overdue = today - xero.due_date
// ─── AGENT HANDOFF 1: Xero → Google Sheets (state read) ─────────────────
// For each Invoice in the returned array, read the chase log
SHEETS.GET rows WHERE invoice_id == xero.invoice_id
returns: LogRow | null
LogRow {
invoice_id : string
reminder_count : integer // sheets.reminder_count
last_reminder_date: date // sheets.last_reminder_date
last_reminder_tier: string
payment_matched : boolean
}
if no LogRow found: sheets.reminder_count = 0, sheets.last_reminder_date = null
// ─── DEDUPE CHECK ───────────────────────────────────────────────────────
if sheets.last_reminder_date == today: SKIP invoice, log 'skipped-duplicate'
else: CONTINUE
// ─── TIER SELECTION ─────────────────────────────────────────────────────
if sheets.reminder_count == 0: gmail.reminder_tier = 'polite'
elif sheets.reminder_count <= 2: gmail.reminder_tier = 'firm'
else: gmail.reminder_tier = 'final_notice'
// ─── AGENT HANDOFF 2: AR Chase Agent → Gmail (email send) ───────────────
GMAIL.POST /gmail/v1/users/me/messages/send
payload: {
to: xero.contact_email,
from: 'accounts@[YourCompany.com]',
subject: gmail.subject, // rendered from tier template
body: gmail.body_html, // template vars injected:
// {{contact_name}} = xero.contact_name
// {{invoice_number}} = xero.invoice_number
// {{amount_due}} = xero.amount_due
// {{due_date}} = xero.due_date
// {{days_overdue}} = xero.days_overdue
// {{reminder_count}} = sheets.reminder_count + 1
}
returns: { id: gmail.message_id }
// ─── AGENT HANDOFF 3: AR Chase Agent → Google Sheets (log append) ────────
SHEETS.APPEND row to chase log
payload: {
invoice_id : xero.invoice_id,
contact_name : xero.contact_name,
contact_email : xero.contact_email,
invoice_number : xero.invoice_number,
amount_due : xero.amount_due,
due_date : xero.due_date,
days_overdue : xero.days_overdue,
reminder_count : sheets.reminder_count + 1,
last_reminder_date: today,
last_reminder_tier: gmail.reminder_tier,
last_email_subject: gmail.subject,
payment_matched : false,
notes : ''
}
returns: sheets.row_appended = true
// ─── ESCALATION EVALUATION ──────────────────────────────────────────────
if xero.days_overdue >= 60 OR (sheets.reminder_count + 1) >= 3:
CONTINUE to Slack alert
else:
SKIP Slack alert
// ─── AGENT HANDOFF 4: AR Chase Agent → Slack (escalation alert) ──────────
SLACK.POST /api/chat.postMessage
payload: {
channel : '#finance-escalations',
text : 'Escalation: ' + xero.invoice_number,
blocks : [
{ type: 'section', fields: [
{ label: 'Contact', value: xero.contact_name },
{ label: 'Invoice', value: xero.invoice_number },
{ label: 'Amount due', value: xero.amount_due },
{ label: 'Days overdue', value: xero.days_overdue },
{ label: 'Reminders sent', value: sheets.reminder_count + 1 }
]}
]
}
returns: slack.alert_posted = true
// ─── AGENT HANDOFF 5: Xero bank feed → Xero payment write ───────────────
XERO.GET /api.xro/2.0/BankTransactions
params: { Status: 'AUTHORISED', Type: 'RECEIVE' }
match: BankTransaction.Total ~= xero.amount_due (within $0.01)
AND BankTransaction.Contact.ContactID == xero.contact_id
if match found:
XERO.PUT /api.xro/2.0/Payments
payload: { Invoice: { InvoiceID: xero.invoice_id }, Amount: xero.amount_due, Date: today }
returns: xero.payment_matched = true
SHEETS.UPDATE row: payment_matched = true, notes = 'Auto-reconciled'
else:
xero.payment_matched = false // leave for manual reconciliation
// ─── AGENT HANDOFF 6: AR Chase Agent → HubSpot (CRM note) ──────────────
HUBSPOT.GET /crm/v3/objects/contacts?email=xero.contact_email
returns: { id: hubspot.contact_id } | null
if null: log warning to error table, skip HubSpot step
else:
HUBSPOT.POST /engagements/v1/engagements
payload: {
engagement : { type: 'NOTE', active: true, timestamp: now() },
associations: { contactIds: [hubspot.contact_id] },
metadata : {
body: 'AR reminder sent: ' + gmail.reminder_tier
+ ' | Invoice: ' + xero.invoice_number
+ ' | Amount: $' + xero.amount_due
+ ' | Days overdue: ' + xero.days_overdue
+ ' | Reminders sent: ' + (sheets.reminder_count + 1)
}
}
returns: hubspot.note_id : string
// ─── RUN COMPLETE ───────────────────────────────────────────────────────
// Repeat for every invoice in the Xero poll result set.
// Any step-level error is caught, logged to the error table, and does not
// halt processing of remaining invoices in the batch.04Recommended build stack
More documents for this process
Every document generated for Accounts Receivable & Chasing.