Back to Accounts Receivable & Chasing

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

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

Step
Name
Description
1
Pull Overdue Invoice Report
Bookkeeper opens Xero and screens the aged receivables report to identify invoices past their due date. Time cost: 15 min/cycle.
2 BOTTLENECK
Cross-Check Previous Reminders
Staff checks Gmail sent folder and a manual Google Sheets log to determine which clients have already received reminders and how many. Prone to gaps when the log is out of date. Time cost: 20 min/cycle.
3
Draft Reminder Email
Reminder email written from scratch or adapted from a saved draft, referencing invoice number, amount, and due date. Time cost: 12 min/cycle.
4
Send Reminder and Log in Tracker
Email sent and Google Sheets tracker updated with date, reminder number, and response status. Time cost: 8 min/cycle.
5 BOTTLENECK
Monitor for Client Replies
Replies and promises to pay are read in Gmail and the tracker updated manually, requiring several inbox passes each day. Time cost: 15 min/cycle.
6
Escalate Seriously Overdue Accounts
Invoices past 60 days flagged to Finance Manager by email; escalation decision made manually. Time cost: 20 min/cycle.
7
Reconcile Incoming Payments
When payment appears in the bank feed, bookkeeper matches it to the open invoice in Xero and marks it paid. Time cost: 10 min/cycle.
8
Update CRM Contact Record
Bookkeeper logs a note on the client record in HubSpot reflecting payment status and any promises or disputes raised. Time cost: 8 min/cycle.
Time cost summary: Total manual time per full cycle is 108 minutes (1.8 hours). At approximately 60 invoices per month and a daily check cadence, this accumulates to 5 hours per week across the finance team. The automation replaces steps 1, 2, 3, 4, 5, 7, and 8 entirely. Step 6 (escalation review by the Finance Manager) remains a human decision point and is retained in the automated flow as a prompted review triggered by a Slack alert.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

AR Chase Agent

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.

Trigger
Daily scheduled poll of Xero aged receivables. Runs once every 24 hours at a fixed time (recommended: 07:00 business timezone). Produces one execution branch per overdue invoice with an outstanding balance.
Tools
Xero (invoice read, payment write), Google Sheets (reminder log read and append), Gmail (send tiered email), Slack (escalation alert post), HubSpot (contact note write)
Replaces steps
1, 2, 3, 4, 5, 7, 8
Estimated build
18 hours / 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.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

AR Chase Agent: full trigger-to-output data flow with field names at each stage
// ─── 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.
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform TBC). One workflow per agent: a single 'AR Chase Agent' workflow handles the full daily poll-to-CRM cycle. All credentials are stored in the platform's shared credential store, never in individual node configurations. The workflow uses a loop node to iterate over each overdue invoice returned by the Xero poll, with error handling at the loop item level so a single failed invoice does not abort the batch.
Webhook configuration
No inbound webhooks are required for this build. The trigger is outbound: a scheduled HTTP GET to the Xero API. If Xero's webhook push (invoice.updated events) becomes available and is preferred in a future iteration, the orchestration layer should expose a public HTTPS endpoint with HMAC-SHA256 signature verification using the Xero webhook signing key. For the Standard build, polling is sufficient and avoids the complexity of webhook registration and secret rotation.
Templating approach
All three reminder email templates (polite, firm, final_notice) are stored as parameterised HTML strings within the platform workflow, not in an external CMS. Template variables use double-brace syntax: {{contact_name}}, {{invoice_number}}, {{amount_due}}, {{due_date}}, {{days_overdue}}, {{reminder_count}}. Variable injection is performed by the orchestration layer before the Gmail send step. Templates must be reviewed and approved by the Finance Manager before the workflow is activated. Store the approved templates in the platform alongside their version date so changes are auditable.
Error logging
All step-level errors are caught by a try/catch equivalent at the loop item level and written to a dedicated 'ar_chase_errors' tab in the same Google Sheet as the chase log (or a separate Supabase table if the team prefers a database-backed log). Error row schema: run_timestamp, invoice_id, invoice_number, contact_email, failed_step (e.g. 'gmail_send', 'hubspot_note'), error_message, retry_count. If three consecutive daily runs log an error for the same invoice_id and failed_step, the orchestration layer posts an alert to '#finance-escalations' in Slack with the error details so the FullSpec team can investigate. Contact support@gofullspec.com for any persistent error that is not self-resolving.
Testing approach
All integration connections are validated in sandbox mode before live credentials are used. Xero provides a demo company environment for testing invoice polling and payment writes. Gmail send tests use a dedicated internal test address (e.g. ar-test@[YourCompany.com]) so no real clients receive test emails. Google Sheets tests use a duplicate 'AR Chase Log - TEST' tab. Slack alerts are tested against a '#finance-test' channel. HubSpot tests use a sandbox portal or a dedicated test contact record. The workflow is run against a minimum of five test invoices covering each reminder tier, the escalation threshold, the payment match scenario, and the duplicate-send guard before any live activation.
Estimated total build time
AR Chase Agent build: 18 hours. End-to-end integration testing and QA: 4 hours. Total: 22 hours. This matches the confirmed build_effort_hours in the process snapshot. The Standard build timeline is 3 to 4 weeks, accounting for credential access delays and template sign-off cycles.
Pre-build checklist: (1) Xero connected app created and OAuth tokens issued with the three required scopes. (2) Gmail OAuth consent screen configured for the sending address with gmail.send scope. (3) Google Sheets chase log created with the exact column schema specified in the agent implementation notes. (4) Slack Bot Token issued with chat:write scope and added to '#finance-escalations'. (5) HubSpot private app token issued with contacts and engagements scopes. (6) All three reminder email templates reviewed and signed off by the Finance Manager. Build must not begin until items 1 through 6 are confirmed. Raise any access blockers with support@gofullspec.com before the build start date.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Accounts Receivable & Chasing.

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