The Business Situation

Cedar Vale Equipment is a fictional 85-person manufacturer and installer of custom material-handling equipment. Its projects commonly include deposits, design approvals, factory acceptance tests, shipment milestones, installation, and final acceptance. Each completed milestone may trigger a separate customer invoice.

Note: This case study is provided as a representative example of the types of AI integration and digital transformation solutions Intelligex designs and delivers. Actual engagements are tailored to each client’s goals, constraints, existing systems, timeline, and available resources, so the approach, tools, and outcomes may vary.

The operations team includes project leads who confirm completed work. Sales maintains commercial terms and approved changes. Finance validates billing eligibility, creates invoices, monitors payment status, and manages disputes. A controller owns the process, while an automation administrator maintains the integrations.

The company processes approximately 55 billable milestones per month. The representative average invoice value is $14,500, producing approximately $797,500 in monthly milestone billing. Individual invoices range from a few thousand dollars to more than $50,000.

Before implementation, project activity was tracked in a project-management board, contract schedules were stored in spreadsheets and project folders, invoices were entered in QuickBooks Online, and follow-up happened through Gmail. These systems did not share a reliable milestone identifier or workflow status.

Completed work did not consistently reach finance. Some project leads sent an email, others updated the project board, and others mentioned completion during a meeting. Finance relied on manual reviews and repeated follow-ups. The representative average delay from milestone completion to invoice creation was eight calendar days.

The business wanted to reduce this delay without allowing software to make final billing or commercial decisions. The new process therefore needed structured intake, deterministic validation, human approval, controlled invoice creation, payment synchronization, dispute holds, automatic reminders, and operational reporting.

The Existing Process

The original workflow followed this general sequence:

  1. Sales documented milestone amounts and payment terms in a contract spreadsheet or project folder.
  2. Operations tracked delivery tasks in a separate project-management tool.
  3. A project lead decided that a milestone was complete and sent an email, updated a task, or mentioned it during a meeting.
  4. A finance specialist searched for the contract, customer record, purchase order, and acceptance evidence.
  5. Finance asked operations or sales for missing information.
  6. Once the milestone appeared billable, finance manually created an invoice in QuickBooks Online.
  7. The invoice number and due date were copied into a billing spreadsheet.
  8. Finance periodically checked QuickBooks Online for open balances and overdue invoices.
  9. Reminder emails were written manually in Gmail.
  10. Customer disputes were recorded in email threads or spreadsheet notes, sometimes without a formal reminder hold.

Process weaknesses

  • Milestone completion was reported through inconsistent channels.
  • Contract data was re-entered during invoice creation.
  • Customer and item identifiers were not validated before approval.
  • Approval ownership was unclear.
  • Invoice references were copied manually.
  • Payment status became stale between finance reviews.
  • Disputes did not always stop reminders.

Business effects

  • Invoices could be delayed even when work was complete.
  • Finance spent time searching for supporting evidence.
  • Incorrect amounts or customer mappings were more likely.
  • Managers could not see where billing was blocked.
  • Customers sometimes received inconsistent follow-up.
  • Cash collection activity started later than necessary.
  • The process depended heavily on individual employees.

The problem was not simply the time required to enter an invoice. Most of the delay occurred before invoice entry, while finance determined whether a milestone was complete, correctly valued, approved, supported, and associated with the right QuickBooks customer.

The existing spreadsheet also lacked dependable automation fields. It did not record retry counts, external invoice IDs, synchronization times, error messages, reminder holds, or the difference between workflow status, invoice status, payment status, and dispute status.

What the New System Needed to Do

The team defined the process requirements before selecting the implementation tools.

Business and technical requirements
Requirement Required behavior
Structured intake Project leads submit a project ID, milestone code, completion date, amount, evidence link, and completion note.
Contract validation The submitted project and milestone must match an active billing schedule.
Unique identifiers Every milestone receives a durable internal ID and deterministic idempotency key.
Duplicate prevention The same project and contract milestone cannot create multiple billing records or invoices.
Ownership Each record has a project lead, finance owner, and commercial approver where required.
Human approval Finance approves every invoice. Sales or a commercial manager approves high-value or materially changed amounts.
Invoice creation Approved records create invoices in QuickBooks Online using validated customer and item IDs.
Document handling Acceptance evidence and the generated invoice PDF are linked to the billing record.
Delivery The invoice PDF is delivered through a controlled Gmail account, with the message identifier recorded.
Accounting synchronization QuickBooks invoice balances and payment events are synchronized into the register.
Reminder controls Due and overdue reminders run only for approved addresses and stop during an open dispute or manual hold.
Exceptions Missing data, invalid mappings, failed API requests, and ambiguous outcomes enter a visible manual-review queue.
Audit evidence The register records decisions, timestamps, external IDs, emails, errors, and manual overrides.
Reporting Managers can see unbilled milestones, approval delays, overdue invoices, disputes, and automation failures.
Recovery Authorized users can correct data and request a controlled retry without recreating successful work.

The team also separated deterministic rules from human decisions. Required fields, amount comparisons, duplicate checks, due-date calculations, reminder dates, and payment-status calculations would be automated. Approval of a changed amount, release of an invoice, resolution of a dispute, and any accounting correction would remain under human control.

Implementation Approaches Considered

Implementation options considered
Approach Connected tools Effort Customization Main limitation
Improve the existing manual process Project tool, email, spreadsheets, QuickBooks Online Low Low Relies on people to copy statuses and invoice references.
Use project-management automation Project tool, QuickBooks Online, email Moderate Moderate Project tasks do not necessarily contain contract, accounting, dispute, or payment fields.
Use Airtable with an automation platform Airtable, QuickBooks Online, Gmail, n8n or another platform Moderate High Adds another core platform and migration requirement.
Use Google Sheets with n8n Google Sheets, Google Drive, n8n, QuickBooks Online, Gmail Moderate High Requires disciplined permissions, data design, and monitoring.
Adopt dedicated project accounting software Project accounting, CRM, ERP, and document systems High Varies Longer implementation and broader process change than the immediate billing problem required.

Improving the manual process

A standardized email template and a better spreadsheet would reduce some missing information. It would not eliminate duplicate entry, synchronize payment status, or provide reliable failure handling. The process would still depend on finance noticing each email.

Using the project-management tool as the billing trigger

This was attractive because operations already used a project board. However, project tasks represented delivery work rather than contract billing records. A task could be reopened, duplicated, or completed without representing an invoiceable event. The project tool also lacked the full customer, accounting item, approval, payment, and dispute structure needed by finance.

Using Airtable

Airtable could provide stronger relational behavior and user interfaces than a spreadsheet. It was a credible alternative, particularly if the business expected more operational applications to move into Airtable. Cedar Vale Equipment did not want to introduce another primary data platform for this phase, so the added migration and administration were not justified.

Using Google Sheets and n8n

This approach retained tools already familiar to employees while adding a dedicated automation and integration layer. It provided enough control for the representative volume, supported QuickBooks Online API requests, and allowed the company to introduce the process incrementally.

Buying dedicated software

A professional services automation platform, project accounting application, or broader ERP could manage milestone billing. That approach becomes more attractive when billing requires complex revenue recognition, resource planning, extensive customer portals, multi-entity accounting, or formal workflow segregation. It was more change than this representative scenario required.

The Selected Solution

The selected implementation used Google Sheets as the operational system of record, n8n as the automation layer, QuickBooks Online as the accounting system, Google Drive for evidence and invoice files, and Gmail for notifications and controlled invoice delivery.

Selected tools and responsibilities
Tool Responsibility
Google Sheets Milestone intake, project and contract mappings, billing register, approval fields, disputes, synchronization cursor, logs, and operational reporting.
n8n Scheduling, validation, lookups, branching, QuickBooks API calls, document retrieval, email delivery, synchronization, retries, and error workflows.
QuickBooks Online Customer records, product or service items, accounts receivable invoices, balances, and payment information.
Google Drive Acceptance evidence, milestone billing folders, and immutable copies of generated invoice PDFs.
Gmail Approval notifications, exception notices, invoice delivery, reminders, escalation notices, and reply handling.
Google Sheets reporting tabs Operational queues, pivot tables, aging views, volume measures, delay measures, and automation monitoring.
Optional AI service Billing-readiness summaries and suggested missing-information categories, subject to human review.

The existing project-management board remained the operational task system. It was not made the billing system of record. A project lead completed delivery tasks there and then submitted the corresponding contract milestone through the controlled Google Sheets intake.

Manual copying of invoice IDs, due dates, balances, reminder dates, and payment status was removed. Human approval remained mandatory before invoice creation. Finance also retained control over credit memos, voids, write-offs, dispute resolution, and any invoice correction after posting.

The implementation used scheduled QuickBooks Change Data Capture requests rather than a public inbound webhook. This avoided exposing a public accounting webhook during the initial phase while still synchronizing changed invoices and payments on a frequent schedule.

System Architecture and Data Flow

  • Intake: A protected Google Sheets milestone-intake tab.
  • System of record: The Google Sheets billing register and related reference tabs.
  • Automation layer: n8n scheduled workflows, Code nodes, filters, loops, and error workflows.
  • Document storage: Structured Google Drive project and billing folders.
  • Notifications: Gmail through a controlled billing automation account.
  • Reporting: Filtered views, pivot tables, formulas, and charts in Google Sheets.
  • AI layer: An optional structured-output API call after deterministic validation.
  1. Submit a milestone. A project lead selects an active project, enters the contract milestone code, confirms the completion date and proposed billable amount, links acceptance evidence, and changes the submission state to Submit. Incomplete drafts are ignored.
  2. Read the submission. An n8n schedule trigger reads submitted rows that do not have a processed register ID. It receives the source row key and all user-entered values.
  3. Join reference data. n8n looks up the project record and contract milestone schedule. The workflow adds the QuickBooks customer ID, item ID, expected amount, payment terms, billing email, finance owner, and approval threshold.
  4. Validate and transform. A Code node checks required fields, active status, dates, currency, amounts, evidence links, email format, and duplicate keys. It creates a normalized idempotency key and internal milestone ID.
  5. Create the register record. If no matching idempotency key exists, n8n appends the record to the billing register. It writes the returned milestone ID back to the intake row. If the register append succeeds but the source update fails, the next run finds the existing idempotency key instead of creating another record.
  6. Route approval. Every valid record enters finance review. High-value records or records with a material contract variance also require commercial approval. Gmail sends the owner a link to the protected review view.
  7. Create the invoice. After approval, n8n marks the record as being processed, queries QuickBooks Online for the deterministic document number, and creates the invoice only when no match exists.
  8. Retrieve and deliver the document. n8n retrieves the QuickBooks-generated invoice PDF, stores it in Google Drive, and sends it through Gmail. The Drive file ID and Gmail message ID are stored in the register.
  9. Synchronize accounting events. A scheduled n8n workflow requests changed invoices and payments from QuickBooks Online. It updates invoice totals, balances, payment status, last synchronization time, and relevant external payment identifiers.
  10. Manage reminders and disputes. A daily workflow identifies invoices approaching or passing their due dates. It sends permitted reminders unless a dispute, manual hold, invalid address, or delivery failure blocks the action.
  11. Handle failures. Failed records receive an error status, retry count, error category, and diagnostic message. The error workflow appends a log entry and notifies the automation owner. Ambiguous invoice-creation outcomes are reconciled by document number before another create request is attempted.

Data Structure

The workbook contains related tabs rather than one overloaded worksheet.

Workbook entities and relationships
Tab Primary key Purpose Relationship
Projects Project_ID Active projects, customer mappings, payment terms, owners, and billing contacts. One project has many scheduled milestones and billing records.
Milestone_Schedule Project_ID plus Milestone_Code Contract milestone names, expected amounts, QuickBooks item IDs, and approval rules. Each schedule row may produce one billing record unless explicitly configured otherwise.
Milestone_Intake Source_Row_Key User-entered completion submissions. A processed intake row points to one Billing_Register record.
Billing_Register Milestone_ID System of record for approval, invoice, payment, dispute, and automation status. Links to one project, one schedule row, one QuickBooks invoice, and zero or more payment events.
Disputes Dispute_ID Customer disputes, owners, reasons, holds, and resolution evidence. Many disputes can reference one invoice, although concurrent open disputes are discouraged.
Payment_Events QBO_Payment_ID plus Invoice_ID Payment events linked to affected invoices. Many payment applications can affect one invoice.
Automation_Log Log_ID Workflow executions, actions, errors, retries, and manual recovery notes. Many log entries can reference one milestone.
Config Config_Key Environment values, reminder rules, synchronization cursor, and escalation addresses. Used by all workflows.
Important Billing_Register fields
Field Type Required and source Validation and purpose
Milestone_ID Text Required, generated by n8n Unique internal record ID. Used as the QuickBooks document number when compatible with accounting policy.
Idempotency_Key Text Required, generated by n8n Normalized Project_ID plus Milestone_Code. Duplicate values are rejected.
Project_ID Text Required, intake Must exist in Projects and be active.
Milestone_Code Text Required, intake Must match an active Milestone_Schedule row for the project.
Milestone_Name Text Required, schedule Contract description used in approvals and invoice detail.
Completion_Date Date Required, intake Valid ISO date that is not unreasonably in the future.
Created_Date Date-time Required, automation Time the billing register record was created.
Last_Updated Date-time Required, automation Updated whenever the record changes.
Submitted_By Email Required, intake Must be a permitted company account.
Project_Owner Email Required, Projects lookup Owns missing completion information.
Finance_Owner Email Required, Projects lookup Owns billing validation, invoice delivery, and collection exceptions.
Commercial_Approver Email Conditional, Projects lookup Required when the threshold or variance rule triggers.
Expected_Amount Currency Required, schedule Contract milestone value.
Billable_Amount Currency Required, intake Must be greater than zero and use the project currency.
Variance_Pct Decimal Required, automation Calculated as billable amount minus expected amount, divided by expected amount.
Currency Text Required, intake and project lookup Must match the project currency. The representative implementation uses USD.
PO_Number Text Conditional, intake Required when the project record indicates that a purchase order is mandatory.
Acceptance_Evidence_URL URL Required, intake Must be an approved Google Drive or Google Docs HTTPS link accessible to the automation account.
Billing_Folder_ID Text Required after folder creation Google Drive folder used for evidence and invoice documents.
Workflow_Status Controlled text Required, automation Tracks the operational stage independently of accounting and payment status.
Approval_Status Controlled text Required, automation Allowed values include Pending, Approved, Returned, Rejected, and Not Required.
Finance_Decision Controlled text Required before billing Approve, Return, or Reject. Entered in a protected finance column.
Finance_Decided_By Email Required with decision Must match an authorized finance reviewer.
Finance_Decided_At Date-time Automation Set when n8n validates and processes the decision.
Commercial_Decision Controlled text Conditional Required for high-value or materially changed milestones.
Due_Date Date Required after invoice creation Calculated from the invoice date and project payment terms.
QBO_Customer_ID Text Required, Projects lookup Validated against QuickBooks Online before invoice creation.
QBO_Item_ID Text Required, schedule lookup References the approved QuickBooks product or service item.
QBO_Invoice_ID Text Required after creation External invoice identifier returned by QuickBooks Online.
Invoice_Number Text Required after creation QuickBooks DocNumber, normally the deterministic Milestone_ID.
Invoice_Created_At Date-time Automation Used to calculate milestone-to-invoice delay.
Invoice_Total Currency QuickBooks synchronization Must reconcile to the approved amount before delivery.
Invoice_Balance Currency QuickBooks synchronization Determines open, partial, or paid status.
Payment_Status Controlled text Automation Open, Partially Paid, Paid, or Overdue.
Dispute_Status Controlled text Finance or automation None, Open, Under Review, Resolved, or Withdrawn.
Dispute_Reason Text Conditional Required when a dispute is open.
Reminder_Hold Boolean Required, default false Automatically true for an open dispute and manually controllable by finance.
Last_Reminder_At Date-time Automation Prevents repeated reminders during the same interval.
Next_Reminder_Date Date Automation Calculated from the configured reminder schedule.
Invoice_PDF_File_ID Text Required after retrieval Google Drive identifier for the stored QuickBooks invoice PDF.
Document_Link URL Automation Controlled link to the billing folder or invoice PDF.
Gmail_Message_ID Text Required after delivery Returned identifier from Gmail for audit and troubleshooting.
Automation_Status Controlled text Required, automation Ready, Running, Completed, Retry Pending, Error, or Manual Review.
Last_Automation_Run Date-time Automation Last processing attempt.
Retry_Count Integer Required, default zero Incremented only when a failed action is retried.
Exception_Type Controlled text Conditional Validation, Mapping, Approval, Authentication, API, Document, Notification, Duplicate, or Reconciliation.
Error_Message Text Conditional Sanitized diagnostic information without credentials or customer-sensitive payloads.
Override_Status Controlled text Controller only Records an authorized manual override.
Override_Reason Text Required for override Explains why normal processing was bypassed or resumed.
Notes Text Optional Operational context not represented by a structured field.

Google Sheets does not provide a database-level unique constraint. The workflow therefore checks Idempotency_Key before every append, uses single-concurrency invoice processing, and performs a second QuickBooks document-number lookup immediately before invoice creation. If the volume or concurrency grows substantially, the register should move to a database with an enforced unique index.

Workflow Statuses and Ownership

Workflow stages and controls
Status Meaning and owner Entry and exit Reminder and escalation
Draft Project lead is preparing intake. Created manually; exits when Submission_State becomes Submit. No automated reminder unless the project schedule says the milestone is expected.
Validation Error Project lead owns missing or invalid information. Entered after failed validation; exits after correction and resubmission. Initial email immediately, then reminder after one business day.
Pending Finance Approval Finance owner validates evidence, amount, customer, tax treatment, and billing readiness. Entered after successful validation; exits with Approve, Return, or Reject. Reminder after one business day; escalation to controller after two business days.
Pending Commercial Approval Commercial approver reviews threshold or variance exceptions. Entered after finance approval when a commercial rule applies. Reminder after one business day; escalation after three business days.
Needs Information Project lead or sales owner supplies requested information. Entered by Return decision; exits through resubmission. Daily internal reminder for two business days, then manager escalation.
Rejected Finance or commercial approver has stopped billing. Entered by Reject decision; reopened only through a controller override with reason. No customer reminder or invoice action.
Approved for Billing Automation owns invoice creation. Entered after all required approvals; exits after invoice creation or error. Error alert if not processed within the expected automation interval.
Invoice Created, Delivery Pending Finance owns document-delivery exceptions. Entered after QuickBooks creation but before PDF storage and Gmail delivery. Automation retries document retrieval; finance alerted after final failure.
Invoice Issued Finance owner monitors receivables. Entered after successful delivery; exits when partially paid, paid, overdue, or disputed. Reminder schedule based on due date and customer policy.
Partially Paid Finance owner monitors the remaining balance. Balance is above zero and below invoice total. Reminder uses remaining balance unless held.
Overdue Finance owner manages collection. Due date has passed and balance remains open. Escalation intervals are controlled by Config.
Disputed Finance owns coordination; sales or operations may assist. Entered when an open dispute is recorded. All automated customer reminders are held.
Paid No active operational owner. QuickBooks reports a zero invoice balance. No further billing reminders.
Manual Review Automation administrator and finance jointly own recovery. Entered for ambiguous API outcomes, mapping conflicts, or repeated failures. Immediate internal alert and daily open-exception report.

A returned record moves backward to the project lead without erasing prior decisions. A rejection closes normal processing. A controller may reopen it only with a documented override. An open dispute changes the workflow status and reminder behavior but does not alter the synchronized QuickBooks balance.

Step-by-Step Implementation

Step 1: Prepare the Accounts and Permissions

  1. Create a production Google Sheets workbook and a separate test copy. Store both in a restricted shared Google Drive location rather than an employee’s personal folder.
  2. Create a controlled Google Workspace automation user such as billing-automation@YOUR_DOMAIN. Give it access only to the billing workbook, required project evidence folders, billing folders, and the Gmail sending account.
  3. Use a real mailbox rather than a distribution group when n8n must authenticate to Gmail and record sent-message identifiers. Configure delegation only if the business requires employees to inspect the mailbox directly.
  4. Create separate n8n projects or instances for test and production where practical. Restrict workflow editing to the automation administrator and backup owner.
  5. Configure n8n encryption and protected credential storage. Do not place OAuth client secrets, refresh tokens, or API credentials in Code nodes or spreadsheet cells.
  6. Create an Intuit developer application and connect it to a QuickBooks Online sandbox for testing. The integration requires the QuickBooks accounting scope and permission to read customers, items, invoices, and payments and to create invoices.
  7. Configure a production QuickBooks connection only after sandbox testing. Record the production realm ID as a protected environment value such as YOUR_REALM_ID.
  8. Use a QuickBooks administrator or approved integration owner to authorize the connection. The day-to-day finance user does not need n8n administration rights.
  9. Create Google OAuth credentials for Google Sheets, Google Drive, and Gmail. Use the smallest scopes supported by the selected n8n credential types.
  10. Create test users representing a project lead, finance reviewer, commercial approver, controller, and unauthorized employee.
  11. Confirm the business time zone, base currency, payment-term conventions, tax configuration, and custom transaction-number policy before writing invoice logic.
  12. Create a rollback copy of the workbook and export n8n workflows without credentials before production activation.

The test environment uses a QuickBooks sandbox, a test workbook, non-customer email addresses, and a separate Drive folder. Production customer addresses must not be used during testing.

QuickBooks OAuth access tokens expire and must be refreshed. The n8n QuickBooks credential or generic OAuth2 credential should manage token refresh. If the connection is revoked or the refresh token becomes invalid, invoice creation and synchronization must stop and enter an authentication exception rather than repeatedly retrying.

Step 2: Build the Intake

Create a tab named Milestone_Intake. Freeze the header row, protect automation-owned columns, and restrict workbook access to authenticated employees.

Milestone intake fields
Field User control Validation
Project_ID Dropdown from active Projects rows Required and must match an active project.
Milestone_Code Dropdown or controlled text Required and must match the selected project’s schedule.
Completion_Date Date Required and not materially in the future.
Billable_Amount Currency Required, numeric, and greater than zero.
Currency Dropdown Required and must match the project currency.
PO_Number Text Conditionally required according to the project record.
Acceptance_Evidence_URL URL Required HTTPS link from an approved Google domain.
Completion_Note Text Required, with a reasonable length limit defined by policy.
Submitted_By Company email Required and compared with an allowed domain or user list.
Submission_State Draft, Submit, or Resubmit Only Submit and Resubmit trigger processing.
Submitted_At Automation only Written on the first processing attempt.
Processing_Result Automation only Processed, Validation Error, or Duplicate.
Register_ID Automation only Milestone_ID returned after record creation.

Project leads upload completion evidence to the approved project folder before submitting. They paste the Drive link into the intake row. This avoids placing binary attachments in spreadsheet cells and makes folder permissions easier to govern.

The confirmation behavior is implemented by n8n. A successful submission changes Processing_Result to Processed, records the register ID, and sends a Gmail confirmation. An incomplete submission changes the result to Validation Error and lists the missing or invalid fields.

Duplicate prevention uses the combination of Project_ID and Milestone_Code. If a milestone legitimately supports multiple invoices, the contract schedule must define separate installment codes such as INSTALL-01 and INSTALL-02.

Spam controls are primarily identity and access controls because intake is internal. The workbook is not publicly shared, anonymous editing is disabled, and only designated project leads can edit intake columns.

Step 3: Create the System of Record

Create the tabs listed in the data model. Use exact header names without punctuation changes because n8n field mappings depend on them.

  1. Populate Projects with active project IDs, QuickBooks customer IDs, customer names, billing email addresses, payment-term days, currency, project owner, finance owner, commercial approver, reminder permission, and purchase-order requirements.
  2. Populate Milestone_Schedule with Project_ID, Milestone_Code, Milestone_Name, Expected_Amount, QBO_Item_ID, active status, commercial threshold, and permitted variance.
  3. Create Billing_Register with the fields defined earlier. Protect all automation columns.
  4. Allow finance to edit only finance decision, finance comment, finance decision identity, reminder hold, and dispute-related columns.
  5. Allow commercial reviewers to edit only commercial decision, comment, and identity columns.
  6. Restrict override fields to the controller and backup controller.
  7. Create filter views for each owner and status. Do not rely on row position as an identifier because sorting changes row numbers.
  8. Create data validation for all controlled values. Reject invalid entries rather than merely displaying a warning where the interface permits it.
  9. Create the Config tab with one row per setting. Protect the tab from general users.
Representative Config values
Config_Key Representative value Purpose
ENVIRONMENT TEST or PRODUCTION Prevents test workflows from using production destinations.
BUSINESS_TIME_ZONE YOUR_BUSINESS_TIME_ZONE Controls invoice, due-date, and reminder calculations.
COMMERCIAL_VARIANCE_THRESHOLD 0.05 Requires commercial review above a 5 percent absolute variance.
DEFAULT_COMMERCIAL_AMOUNT_THRESHOLD 25000 Representative threshold, replace with approved policy.
FINANCE_REMINDER_BUSINESS_DAYS 1 First finance approval reminder.
FINANCE_ESCALATION_BUSINESS_DAYS 2 Controller escalation.
QBO_CDC_CURSOR ISO date-time Last successfully processed QuickBooks change time.
MAX_AUTOMATED_RETRIES 3 Maximum normal retries before manual review.
REMINDER_INTERVAL_DAYS 7 Representative recurring overdue interval.
AUTOMATION_ALERT_EMAIL YOUR_EMAIL_ADDRESS Recipient for workflow failures.

Filtered views should include a stable key column and should never hide errors from the automation administrator. Google Sheets version history supplements the decision fields, but it is not a substitute for stronger database audit controls where formal segregation or nonrepudiation is required.

Step 4: Connect the Tools

Integration connections
Source and destination Trigger and authentication Mapping and result
Google Sheets to n8n Schedule trigger with Google OAuth Reads submitted intake rows and returns project, milestone, amount, evidence, and source-row values.
n8n to Google Sheets Workflow actions using the same controlled credential Appends register and log rows, then updates records by stable IDs rather than row position.
n8n to QuickBooks Online OAuth 2.0 accounting connection Validates customer references, queries document numbers, creates invoices, retrieves PDFs, and reads changed invoices and payments.
n8n to Google Drive Google OAuth Finds or creates billing folders, copies evidence where permitted, and stores invoice PDFs. Returned file IDs are written to the register.
n8n to Gmail Google OAuth for the billing mailbox Sends approval, exception, invoice, reminder, and escalation messages. Returned message IDs are recorded.
QuickBooks Online to Google Sheets Scheduled Change Data Capture request through n8n Maps changed Invoice and Payment entities to register and payment-event rows.

For QuickBooks Online, use the production base URL only in the production workflow:

Production base:
https://quickbooks.api.intuit.com/v3/company/YOUR_REALM_ID

Sandbox base:
https://sandbox-quickbooks.api.intuit.com/v3/company/YOUR_REALM_ID

OAuth authorization URL:
https://appcenter.intuit.com/connect/oauth2

OAuth token URL:
https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer

Accounting scope:
com.intuit.quickbooks.accounting

Use the n8n QuickBooks credential when it supports the required operations. Otherwise, configure a generic OAuth2 credential and use HTTP Request nodes. Interface labels may vary by n8n version, but the required trigger, credential, URL, method, headers, body, and returned fields remain the same.

All QuickBooks requests send Accept: application/json. Invoice creation also sends Content-Type: application/json. PDF retrieval requests an application PDF response and stores it in an n8n binary property.

Step 5: Build the Core Automation

Workflow WF-01: Intake and Validation

  • Trigger: Schedule every five minutes.
  • Conditions: Submission_State is Submit or Resubmit, and Register_ID is blank.
  • Actions: Read intake, look up project and schedule rows, validate data, check idempotency, append the register record, update the intake row, create the Drive folder, and notify the finance owner.
  • Fields updated: Submitted_At, Processing_Result, Register_ID, Workflow_Status, Automation_Status, Created_Date, and Last_Updated.
  • Notification: Confirmation to the submitter and review request to finance.
  • Exception: Validation errors return a field list; duplicate keys point to the existing record; lookup failures enter Manual Review.

The exact action order is important:

  1. Read candidate rows.
  2. Look up Projects by Project_ID.
  3. Look up Milestone_Schedule by Project_ID and Milestone_Code.
  4. Run validation and transformation.
  5. Route invalid records to an intake error update.
  6. For valid records, search Billing_Register by Idempotency_Key.
  7. If found, mark the intake row Duplicate and write the existing Milestone_ID.
  8. If not found, append the Billing_Register row.
  9. Read the appended record back by Milestone_ID to confirm creation.
  10. Update the intake row as Processed.
  11. Create or locate the Drive billing folder.
  12. Set Workflow_Status to Pending Finance Approval.
  13. Send Gmail notifications.
  14. Append an Automation_Log entry.

Workflow WF-02: Approval Router

  • Trigger: Schedule every five minutes.
  • Conditions: A pending record has a new decision in the protected decision columns.
  • Actions: Validate the reviewer, record the decision time, route approval, return, or rejection, and notify the next owner.
  • Fields updated: Approval_Status, Workflow_Status, decision timestamps, Last_Updated, and Automation_Status.
  • Notification: Next approver, project lead, finance owner, or controller.
  • Exception: Blank comments on Return or Reject, unauthorized reviewer email, or invalid decision value enters Manual Review.

When finance approves, n8n evaluates two deterministic conditions:

Commercial review required when:

Billable_Amount >= Commercial_Amount_Threshold

OR

ABS(Variance_Pct) > Commercial_Variance_Threshold

If neither condition applies, the record moves directly to Approved for Billing. If either applies, it moves to Pending Commercial Approval.

Workflow WF-03: Invoice Creation and Delivery

  • Trigger: Schedule every five minutes.
  • Conditions: Workflow_Status is Approved for Billing, Automation_Status is Ready or Retry Pending, and QBO_Invoice_ID is blank.
  • Actions: Mark processing, validate the QuickBooks customer, query the document number, create or recover the invoice, retrieve its PDF, store it in Drive, send it through Gmail, and update the register.
  • Fields updated: QBO_Invoice_ID, Invoice_Number, Invoice_Total, Due_Date, Invoice_Created_At, Invoice_PDF_File_ID, Document_Link, Gmail_Message_ID, Workflow_Status, and Automation_Status.
  • Notification: Invoice to the customer billing address and confirmation to finance.
  • Exception: An amount mismatch, PDF failure, invalid email, ambiguous API timeout, or delivery failure stops the workflow at the appropriate recovery stage.

Set the invoice workflow concurrency to one for this spreadsheet-based implementation. Before the create request, set Automation_Status to Running and Workflow_Status to Approved for Billing. Then query QuickBooks for an invoice whose DocNumber equals Milestone_ID.

If a matching invoice exists, do not create another invoice. Validate its customer and amount, then populate the register using the existing QuickBooks response. If the values conflict, move the record to Manual Review.

If no match exists, send the create request. After a successful response, immediately write the returned invoice ID to Google Sheets before attempting PDF retrieval or Gmail delivery. This separates accounting success from downstream document success.

Workflow WF-04: QuickBooks Change Synchronization

  • Trigger: Schedule every 15 minutes.
  • Conditions: A valid QBO_CDC_CURSOR exists and no earlier synchronization is still running.
  • Actions: Request changed Invoice and Payment entities, normalize the response, update register balances and statuses, append payment events, and advance the cursor only after all updates succeed.
  • Fields updated: Invoice_Total, Invoice_Balance, Payment_Status, Last_QBO_Sync, external payment IDs, Last_Updated, and QBO_CDC_CURSOR.
  • Notification: Internal notification for paid high-value invoices or reconciliation exceptions, according to policy.
  • Exception: A failed request leaves the old cursor unchanged so the same interval can be requested again.

The Change Data Capture request pattern is:

GET /v3/company/YOUR_REALM_ID/cdc
Query parameter entities: Invoice,Payment
Query parameter changedSince: LAST_SUCCESSFUL_ISO_TIMESTAMP

A separate daily reconciliation workflow reads all register records with an open balance and compares them with QuickBooks. This catches missed synchronization windows, manually changed invoices, and cursor problems.

Workflow WF-05: Reminders and Disputes

  • Trigger: Daily schedule at a controlled business time, plus frequent polling of new Disputes rows.
  • Conditions: Balance is greater than zero, reminder date is due, billing email is valid, Reminder_Hold is false, and Dispute_Status is not Open or Under Review.
  • Actions: Send the appropriate reminder, store the Gmail message ID, calculate the next reminder date, or apply a dispute hold.
  • Fields updated: Last_Reminder_At, Next_Reminder_Date, Reminder_Count, Dispute_Status, Reminder_Hold, and Workflow_Status.
  • Notification: Customer reminder, finance dispute alert, or escalation to the controller.
  • Exception: Invalid addresses, Gmail failures, unresolved disputes, and policy exclusions prevent customer delivery.

Reminder messages use synchronized QuickBooks balances. They do not infer payment status from email replies. A finance user records a dispute in the Disputes tab after receiving a customer reply. n8n then places the invoice on hold and alerts the assigned owner.

Step 6: Add Approvals, Reminders, and Escalations

Finance approval is sequential and always occurs first. The finance reviewer confirms:

  • The milestone is supported by evidence.
  • The customer and project mapping are correct.
  • The amount agrees with the contract or an approved change.
  • The QuickBooks item is appropriate.
  • The billing email and purchase order are valid.
  • The invoice date and payment terms are appropriate.
  • No unresolved project issue should prevent billing.

Commercial approval follows only when the value or variance rule applies. A commercial approver must not change accounting fields directly. The approver selects Approve, Return, or Reject, enters their company email, and provides a comment for Return or Reject.

Approval trigger, condition, and action rules
Event Condition Action
Finance approval Authorized reviewer and complete evidence Route to commercial review or Approved for Billing.
Finance return Comment present Set Needs Information and notify project lead.
Finance rejection Comment present Set Rejected, stop automation, and notify controller.
Commercial approval Authorized approver Set Approved for Billing.
Commercial return Comment present Set Needs Information and notify finance and project lead.
Approval overdue Pending beyond configured interval Send reminder and then escalation.
Approver unavailable Delegation active in Config Notify approved delegate and record the delegation used.

Delegation is not inferred from an out-of-office message. The controller records a temporary delegate, start date, and end date in Config. n8n validates that the delegate belongs to the approved reviewer list.

Approval evidence includes the decision, comment, expected approver, entered reviewer email, automation timestamp, prior status, new status, and corresponding Automation_Log row. Google Sheets version history provides supporting editor history. Businesses requiring stronger approval evidence should use an authenticated approval application or dedicated workflow platform.

Step 7: Add Documents and File Management

Create this representative Drive structure:

Projects/
  PROJECT_ID/
    Billing/
      MILESTONE_ID/
        Evidence/
        Invoices/
        Dispute/
        Archive/

The folder workflow searches for each folder before creating it. Returned folder IDs are stored in the register so future runs do not depend on folder names.

Use these naming conventions:

  • Evidence copy: MILESTONE_ID_Evidence_YYYY-MM-DD_OriginalName
  • Invoice PDF: Invoice_MILESTONE_ID_YYYY-MM-DD.pdf
  • Dispute document: MILESTONE_ID_Dispute_DISPUTE_ID_YYYY-MM-DD

The automation account must be able to read the source evidence. If it cannot, the record remains in Validation Error or Manual Review. It should not copy files from personal accounts or externally shared locations without an approved policy.

Invoice PDFs are treated as issued records. A corrected invoice does not replace the original file silently. Finance makes the correction in QuickBooks Online, and the automation stores the revised or replacement document with a new name and audit entry.

Large evidence files should remain linked rather than being moved through n8n memory. The workflow can copy ordinary documents through Google Drive, but file-size and execution-memory limits must be tested for the chosen n8n hosting model.

Retention follows the company’s accounting and contract policy. Closed project folders may be moved to Archive, but QuickBooks IDs and register records remain available for the required retention period.

Step 8: Add Reporting and Operational Views

Create the following Google Sheets filter views or reporting tabs:

  • New submissions: Valid records created during the last seven days.
  • Awaiting finance: Pending Finance Approval by owner and age.
  • Awaiting commercial: Pending Commercial Approval by approver and age.
  • Needs information: Returned records grouped by project lead.
  • Approved but unbilled: Approved for Billing without QBO_Invoice_ID.
  • Delivery pending: QBO_Invoice_ID present but Gmail_Message_ID blank.
  • Overdue invoices: Balance above zero and due date before today.
  • Disputes: Open or under-review disputes by owner and age.
  • Automation failures: Error, Retry Pending, or Manual Review.
  • Recent payments: Paid during the selected reporting period.
  • Upcoming due dates: Open invoices due in the next seven days.
  • Processing time: Days from Completion_Date to Invoice_Created_At.
  • Volume by status: Count and amount by workflow and payment status.
  • Manual-review queue: Records requiring a person before automation can resume.

Create pivot tables from Billing_Register rather than copying records into separate manually maintained reports. Useful dimensions include finance owner, project owner, customer, milestone type, workflow status, payment status, and month.

Representative alert thresholds are:

  • Any approved record without an invoice after 30 minutes.
  • Any invoice created without successful delivery after 60 minutes.
  • Any finance approval older than two business days.
  • Any QuickBooks synchronization cursor older than 30 minutes during operating hours.
  • Any Automation_Status of Error at the end of the business day.
  • Any reminder sent while a dispute or hold is active.

The controller owns the financial dashboard. The automation administrator owns the failure and synchronization views. Operations managers receive project-specific views but do not receive unrestricted access to accounting details.

Step 9: Add Security and Governance Controls

  • Apply least-privilege access to the workbook, Drive folders, n8n projects, credentials, and QuickBooks company.
  • Protect automation-owned and approval columns in Google Sheets.
  • Do not make invoice PDFs or evidence folders publicly accessible through shared links.
  • Store OAuth credentials in n8n credentials, not environment output, code, email, or spreadsheet cells.
  • Separate test and production credentials.
  • Restrict QuickBooks write access to workflows that create approved invoices.
  • Log workflow name, execution ID, milestone ID, action, result, time, and sanitized error.
  • Remove former employees from Google groups, Drive permissions, n8n projects, approval lists, and QuickBooks access promptly.
  • Review retention obligations for contracts, invoices, acceptance evidence, payment records, and disputes.
  • Back up the workbook and export workflow definitions regularly.
  • Require a controller override reason before reopening a rejected record or bypassing a normal hold.
  • Do not send bank details, tax identifiers, personal information, credentials, or complete contract documents to an optional AI service unless explicitly approved.
  • Keep final invoice approval, accounting corrections, dispute resolution, write-offs, and credit decisions human-controlled.

QuickBooks accounting authorization is relatively broad because the workflow must create invoices and read accounting entities. Compensating controls include restricted workflow editing, human approval before creation, stable document numbers, execution logging, daily reconciliation, and periodic credential review.

Step 10: Deploy and Test

  1. Build every workflow against the test workbook and QuickBooks sandbox.
  2. Use synthetic customers, billing addresses, project IDs, and evidence files.
  3. Run Code nodes with pinned sample data and inspect every output field.
  4. Test normal, returned, rejected, duplicate, failed, and recovered records.
  5. Perform user acceptance testing with one project lead, one finance reviewer, one commercial approver, and the controller.
  6. Run a pilot using a small set of projects while finance continues a temporary reconciliation against the previous process.
  7. Compare every pilot invoice with its approved source record before customer delivery.
  8. Confirm that QuickBooks balances, payment updates, dispute holds, and reminder dates synchronize correctly.
  9. Export the tested workflows and record their versions.
  10. Replace sandbox credentials, spreadsheet IDs, folder IDs, and test addresses with production values through protected credentials or environment configuration.
  11. Activate workflows in order: intake, approvals, invoice creation, synchronization, reminders, and error handling.
  12. Monitor every execution during the first production billing cycle.
  13. Maintain a rollback procedure that deactivates invoice and reminder workflows while leaving the register available for manual processing.
  14. Provide a short operating guide for submitters, reviewers, finance users, and automation administrators.

Launch communication should explain what is automated, what remains manual, where users see status, how to correct an error, and whom to contact. It should not tell employees to resubmit repeatedly when a record is already in Manual Review.

Code and Configuration

n8n validation and identifier Code node

Place the following code in an n8n Code node after the intake row has been merged with the Projects and Milestone_Schedule lookups. Configure the node to run once for all input items. It has no external dependencies and uses only JavaScript available in the Code node.

'use strict';

/**
 * n8n Code node
 * Input: merged intake, project, and milestone schedule fields.
 * Output: normalized data with validation.valid and validation.errors.
 */

function text(value) {
  return String(value ?? '').trim();
}

function numberValue(value) {
  if (typeof value === 'number') return value;
  const normalized = text(value);
  if (normalized === '') return NaN;
  return Number(normalized);
}

function isAuthorizedEmail(value) {
  const email = text(value).toLowerCase();
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function isApprovedEvidenceUrl(value) {
  try {
    const url = new URL(text(value));
    const allowedHosts = new Set([
      'drive.google.com',
      'docs.google.com'
    ]);
    return url.protocol === 'https:' && allowedHosts.has(url.hostname);
  } catch (error) {
    return false;
  }
}

function normalizeToken(value, maxLength) {
  return text(value)
    .toUpperCase()
    .replace(/[^A-Z0-9]/g, '')
    .slice(0, maxLength);
}

function cyrb53(value, seed = 0) {
  let h1 = 0xdeadbeef ^ seed;
  let h2 = 0x41c6ce57 ^ seed;

  for (let i = 0; i < value.length; i += 1) {
    const ch = value.charCodeAt(i);
    h1 = Math.imul(h1 ^ ch, 2654435761);
    h2 = Math.imul(h2 ^ ch, 1597334677);
  }

  h1 =
    Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
    Math.imul(h2 ^ (h2 >>> 13), 3266489909);

  h2 =
    Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
    Math.imul(h1 ^ (h1 >>> 13), 3266489909);

  return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}

function buildMilestoneId(projectId, milestoneCode, idempotencyKey) {
  const readable =
    `MB-${normalizeToken(projectId, 8)}-${normalizeToken(milestoneCode, 8)}`;

  if (readable.length <= 21) {
    return readable;
  }

  return `MB-${cyrb53(idempotencyKey).toString(36).toUpperCase()}`;
}

function isIsoDate(value) {
  const dateText = text(value);
  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateText)) return false;

  const parsed = new Date(`${dateText}T00:00:00Z`);
  return !Number.isNaN(parsed.getTime());
}

const results = items.map((item) => {
  const source = item.json;
  const errors = [];

  const projectId = text(source.Project_ID);
  const milestoneCode = text(source.Milestone_Code);
  const milestoneName = text(source.Milestone_Name);
  const completionDate = text(source.Completion_Date);
  const submittedBy = text(source.Submitted_By).toLowerCase();
  const evidenceUrl = text(source.Acceptance_Evidence_URL);
  const completionNote = text(source.Completion_Note);
  const currency = text(source.Currency).toUpperCase();
  const projectCurrency = text(source.Project_Currency).toUpperCase();
  const qboCustomerId = text(source.QBO_Customer_ID);
  const qboItemId = text(source.QBO_Item_ID);
  const billingEmail = text(source.Billing_Email).toLowerCase();
  const projectActive = ['true', 'yes', 'active', '1']
    .includes(text(source.Project_Active).toLowerCase());
  const scheduleActive = ['true', 'yes', 'active', '1']
    .includes(text(source.Schedule_Active).toLowerCase());

  const billableAmount = numberValue(source.Billable_Amount);
  const expectedAmount = numberValue(source.Expected_Amount);
  const amountThreshold = numberValue(
    source.Commercial_Amount_Threshold ?? 25000
  );
  const varianceThreshold = numberValue(
    source.Commercial_Variance_Threshold ?? 0.05
  );
  const termsDays = numberValue(source.Terms_Days);

  if (!projectId) errors.push('Project_ID is required.');
  if (!milestoneCode) errors.push('Milestone_Code is required.');
  if (!milestoneName) errors.push('Milestone_Name was not found.');
  if (!projectActive) errors.push('The project is missing or inactive.');
  if (!scheduleActive) errors.push('The milestone schedule is missing or inactive.');

  if (!isIsoDate(completionDate)) {
    errors.push('Completion_Date must use YYYY-MM-DD.');
  } else {
    const completedAt = new Date(`${completionDate}T00:00:00Z`);
    const tomorrow = new Date();
    tomorrow.setUTCDate(tomorrow.getUTCDate() + 1);
    if (completedAt.getTime() > tomorrow.getTime()) {
      errors.push('Completion_Date is too far in the future.');
    }
  }

  if (!Number.isFinite(billableAmount) || billableAmount <= 0) {
    errors.push('Billable_Amount must be a number greater than zero.');
  }

  if (!Number.isFinite(expectedAmount) || expectedAmount <= 0) {
    errors.push('Expected_Amount must be a number greater than zero.');
  }

  if (!currency || currency !== projectCurrency) {
    errors.push('Currency must match the project currency.');
  }

  if (!qboCustomerId) errors.push('QBO_Customer_ID is missing.');
  if (!qboItemId) errors.push('QBO_Item_ID is missing.');

  if (!isApprovedEvidenceUrl(evidenceUrl)) {
    errors.push('Acceptance_Evidence_URL must be an approved Google HTTPS URL.');
  }

  if (completionNote.length < 10) {
    errors.push('Completion_Note must contain at least 10 characters.');
  }

  if (!isAuthorizedEmail(submittedBy)) {
    errors.push('Submitted_By must be a valid email address.');
  }

  if (!isAuthorizedEmail(billingEmail)) {
    errors.push('Billing_Email must be a valid email address.');
  }

  if (
    !Number.isInteger(termsDays) ||
    termsDays < 0 ||
    termsDays > 365
  ) {
    errors.push('Terms_Days must be an integer from 0 to 365.');
  }

  const idempotencyKey =
    `${projectId.toUpperCase()}|${milestoneCode.toUpperCase()}`;

  const milestoneId = buildMilestoneId(
    projectId,
    milestoneCode,
    idempotencyKey
  );

  const variancePct =
    Number.isFinite(billableAmount) &&
    Number.isFinite(expectedAmount) &&
    expectedAmount !== 0
      ? (billableAmount - expectedAmount) / expectedAmount
      : null;

  const commercialRequired =
    Number.isFinite(billableAmount) &&
    Number.isFinite(amountThreshold) &&
    billableAmount >= amountThreshold
      ? true
      : Number.isFinite(variancePct) &&
        Number.isFinite(varianceThreshold) &&
        Math.abs(variancePct) > varianceThreshold;

  const output = {
    ...source,
    Project_ID: projectId,
    Milestone_Code: milestoneCode,
    Milestone_Name: milestoneName,
    Completion_Date: completionDate,
    Submitted_By: submittedBy,
    Acceptance_Evidence_URL: evidenceUrl,
    Completion_Note: completionNote,
    Currency: currency,
    Billing_Email: billingEmail,
    Billable_Amount: Number.isFinite(billableAmount)
      ? Number(billableAmount.toFixed(2))
      : null,
    Expected_Amount: Number.isFinite(expectedAmount)
      ? Number(expectedAmount.toFixed(2))
      : null,
    Variance_Pct: variancePct,
    Terms_Days: termsDays,
    Idempotency_Key: idempotencyKey,
    Milestone_ID: milestoneId,
    Commercial_Approval_Required: commercialRequired,
    validation: {
      valid: errors.length === 0,
      errors
    }
  };

  console.log(JSON.stringify({
    milestoneId,
    valid: output.validation.valid,
    errorCount: errors.length
  }));

  return { json: output };
});

return results;

Route output with an n8n If node using the Boolean expression {{ $json.validation.valid }}. Invalid items update the intake row and send the submitter the joined error array. Valid items continue to the idempotency lookup.

Test the node with one valid row, one missing customer mapping, one future date, one malformed URL, and one duplicate project-milestone pair. If all items fail unexpectedly, inspect the merged field names and the Code node output in n8n Executions.

Invoice date and QuickBooks payload Code node

Place this Code node after approval and immediately before the QuickBooks document-number query. Replace Etc/UTC with the company’s approved IANA time-zone identifier.

'use strict';

/**
 * n8n Code node
 * Builds a QuickBooks Online invoice payload.
 */

const BUSINESS_TIME_ZONE = 'Etc/UTC';

function requiredText(value, fieldName) {
  const result = String(value ?? '').trim();
  if (!result) {
    throw new Error(`${fieldName} is required.`);
  }
  return result;
}

function localIsoDate(timeZone) {
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit'
  }).formatToParts(new Date());

  const values = {};
  for (const part of parts) {
    if (part.type !== 'literal') values[part.type] = part.value;
  }

  return `${values.year}-${values.month}-${values.day}`;
}

function addCalendarDays(isoDate, days) {
  const date = new Date(`${isoDate}T12:00:00Z`);
  if (Number.isNaN(date.getTime())) {
    throw new Error('Invalid invoice date.');
  }

  date.setUTCDate(date.getUTCDate() + days);
  return date.toISOString().slice(0, 10);
}

return items.map((item) => {
  const source = item.json;

  const milestoneId = requiredText(source.Milestone_ID, 'Milestone_ID');
  const customerId = requiredText(source.QBO_Customer_ID, 'QBO_Customer_ID');
  const itemId = requiredText(source.QBO_Item_ID, 'QBO_Item_ID');
  const billingEmail = requiredText(source.Billing_Email, 'Billing_Email');
  const projectId = requiredText(source.Project_ID, 'Project_ID');
  const milestoneName = requiredText(source.Milestone_Name, 'Milestone_Name');

  const amount = Number(source.Billable_Amount);
  const termsDays = Number(source.Terms_Days);

  if (!Number.isFinite(amount) || amount <= 0) {
    throw new Error('Billable_Amount must be greater than zero.');
  }

  if (
    !Number.isInteger(termsDays) ||
    termsDays < 0 ||
    termsDays > 365
  ) {
    throw new Error('Terms_Days must be an integer from 0 to 365.');
  }

  const txnDate = localIsoDate(BUSINESS_TIME_ZONE);
  const dueDate = addCalendarDays(txnDate, termsDays);
  const poNumber = String(source.PO_Number ?? '').trim();

  const description = [
    `Project ${projectId}`,
    milestoneName,
    poNumber ? `PO ${poNumber}` : ''
  ]
    .filter(Boolean)
    .join(' | ')
    .slice(0, 1000);

  const payload = {
    CustomerRef: {
      value: customerId
    },
    BillEmail: {
      Address: billingEmail
    },
    DocNumber: milestoneId,
    TxnDate: txnDate,
    DueDate: dueDate,
    PrivateNote: `Automated milestone billing reference ${milestoneId}`,
    CustomerMemo: {
      value: poNumber
        ? `Project ${projectId}, purchase order ${poNumber}`
        : `Project ${projectId}`
    },
    Line: [
      {
        DetailType: 'SalesItemLineDetail',
        Amount: Number(amount.toFixed(2)),
        Description: description,
        SalesItemLineDetail: {
          ItemRef: {
            value: itemId
          },
          Qty: 1,
          UnitPrice: Number(amount.toFixed(2))
        }
      }
    ]
  };

  console.log(JSON.stringify({
    milestoneId,
    txnDate,
    dueDate,
    amount
  }));

  return {
    json: {
      ...source,
      Invoice_Number: milestoneId,
      Invoice_Date: txnDate,
      Due_Date: dueDate,
      qboInvoicePayload: payload
    }
  };
});

Configure the QuickBooks invoice lookup HTTP Request node as follows:

Method: GET
URL: {{ $env.QBO_BASE_URL }}/query
Authentication: QuickBooks OAuth2 credential
Query parameter name: query
Query parameter value:
SELECT * FROM Invoice WHERE DocNumber = '{{ $json.Invoice_Number }}' MAXRESULTS 1

Header:
Accept: application/json

The generated invoice number contains only uppercase letters, numbers, and hyphens. This limits query-escaping risk. If the company permits other document-number formats, validate and escape them before constructing a QuickBooks query.

If QueryResponse.Invoice[0] exists, reconcile it. Otherwise configure the create request:

Method: POST
URL: {{ $env.QBO_BASE_URL }}/invoice
Authentication: QuickBooks OAuth2 credential
Send body as JSON: true
Body expression: {{ $json.qboInvoicePayload }}

Headers:
Accept: application/json
Content-Type: application/json

A successful response contains an Invoice

QuickBooks response mapping
QuickBooks response Billing register field
Invoice.Id QBO_Invoice_ID
Invoice.DocNumber Invoice_Number
Invoice.TxnDate Invoice_Date
Invoice.DueDate Due_Date
Invoice.TotalAmt Invoice_Total
Invoice.Balance Invoice_Balance
Invoice.SyncToken QBO_Sync_Token
Invoice.MetaData.CreateTime Invoice_Created_At

Retrieve the PDF only after storing the invoice ID:

Method: GET
URL: {{ $env.QBO_BASE_URL }}/invoice/{{ $json.QBO_Invoice_ID }}/pdf
Authentication: QuickBooks OAuth2 credential
Expected response: File
Binary property: invoicePdf
Accept header: application/pdf

Pass invoicePdfInvoice Created, Delivery Pending. Do not create another invoice.

Gmail invoice-delivery template

To:
{{ $json.Billing_Email }}

Subject:
Invoice {{ $json.Invoice_Number }} for project {{ $json.Project_ID }}

Body:
Hello,

Attached is invoice {{ $json.Invoice_Number }} for the completed milestone:
{{ $json.Milestone_Name }}

Invoice amount: {{ $json.Invoice_Total }}
Invoice date: {{ $json.Invoice_Date }}
Due date: {{ $json.Due_Date }}
Purchase order: {{ $json.PO_Number || 'Not provided' }}

Please reply to this message if the invoice requires review.

Regards,
Cedar Vale Equipment Billing

Attachment binary property:
invoicePdf

Do not mark delivery successful until Gmail returns a message identifier. If Gmail reports an invalid recipient, set Exception_Type to Notification, keep the invoice created, and route the record to finance.

QuickBooks Change Data Capture normalization

Configure the HTTP Request node to return the parsed JSON body, then place this Code node after it. It emits one n8n item per changed invoice or payment.

'use strict';

/**
 * n8n Code node
 * Normalizes a QuickBooks Online CDC response.
 */

if (!items.length) {
  throw new Error('No QuickBooks CDC response was received.');
}

const body = items[0].json;
const cdcGroups = Array.isArray(body.CDCResponse)
  ? body.CDCResponse
  : [];

const output = [];
const responseTime = String(body.time ?? new Date().toISOString());

function numeric(value) {
  const result = Number(value);
  return Number.isFinite(result) ? result : 0;
}

function invoiceStatus(invoice) {
  const balance = numeric(invoice.Balance);
  const total = numeric(invoice.TotalAmt);
  const dueDate = String(invoice.DueDate ?? '');
  const today = new Date().toISOString().slice(0, 10);

  if (balance <= 0) return 'Paid';
  if (balance < total) return 'Partially Paid';
  if (dueDate && dueDate < today) return 'Overdue';
  return 'Open';
}

for (const group of cdcGroups) {
  const queryResponses = Array.isArray(group.QueryResponse)
    ? group.QueryResponse
    : [];

  for (const queryResponse of queryResponses) {
    const invoices = Array.isArray(queryResponse.Invoice)
      ? queryResponse.Invoice
      : [];

    for (const invoice of invoices) {
      output.push({
        json: {
          Entity_Type: 'Invoice',
          QBO_Invoice_ID: String(invoice.Id ?? ''),
          Invoice_Number: String(invoice.DocNumber ?? ''),
          Invoice_Date: String(invoice.TxnDate ?? ''),
          Due_Date: String(invoice.DueDate ?? ''),
          Invoice_Total: numeric(invoice.TotalAmt),
          Invoice_Balance: numeric(invoice.Balance),
          Payment_Status: invoiceStatus(invoice),
          QBO_Sync_Token: String(invoice.SyncToken ?? ''),
          QBO_Last_Updated: String(
            invoice.MetaData?.LastUpdatedTime ?? responseTime
          ),
          CDC_Response_Time: responseTime
        }
      });
    }

    const payments = Array.isArray(queryResponse.Payment)
      ? queryResponse.Payment
      : [];

    for (const payment of payments) {
      const lines = Array.isArray(payment.Line) ? payment.Line : [];
      const invoiceLinks = [];

      for (const line of lines) {
        const linkedTransactions = Array.isArray(line.LinkedTxn)
          ? line.LinkedTxn
          : [];

        for (const linked of linkedTransactions) {
          if (linked.TxnType === 'Invoice') {
            invoiceLinks.push({
              QBO_Invoice_ID: String(linked.TxnId ?? ''),
              Applied_Amount: numeric(line.Amount)
            });
          }
        }
      }

      if (!invoiceLinks.length) {
        output.push({
          json: {
            Entity_Type: 'Payment',
            QBO_Payment_ID: String(payment.Id ?? ''),
            Payment_Date: String(payment.TxnDate ?? ''),
            Payment_Total: numeric(payment.TotalAmt),
            QBO_Invoice_ID: '',
            Applied_Amount: 0,
            CDC_Response_Time: responseTime
          }
        });
      } else {
        for (const link of invoiceLinks) {
          output.push({
            json: {
              Entity_Type: 'Payment',
              QBO_Payment_ID: String(payment.Id ?? ''),
              Payment_Date: String(payment.TxnDate ?? ''),
              Payment_Total: numeric(payment.TotalAmt),
              QBO_Invoice_ID: link.QBO_Invoice_ID,
              Applied_Amount: link.Applied_Amount,
              CDC_Response_Time: responseTime
            }
          });
        }
      }
    }
  }
}

if (!output.length) {
  return [{
    json: {
      Entity_Type: 'None',
      CDC_Response_Time: responseTime
    }
  }];
}

console.log(JSON.stringify({
  entityCount: output.length,
  responseTime
}));

return output;

Route Invoice items to a Billing_Register lookup by QBO_Invoice_ID. Route Payment items to an idempotent lookup in Payment_Events using QBO_Payment_ID plus QBO_Invoice_ID. Advance the CDC cursor only after all emitted items have been processed successfully.

Do not advance the cursor when any update fails. The next execution can safely request the same interval because invoice rows are updated by QBO_Invoice_ID and payment rows use an idempotency key.

Retry and ambiguous-result configuration

Enable bounded retries for read-only GET requests and ordinary Google actions. For a QuickBooks invoice POST, do not blindly repeat a request after a timeout because the server may have created the invoice before the connection failed.

  1. On a create timeout, wait approximately 30 seconds.
  2. Query QuickBooks by the deterministic DocNumber.
  3. If the invoice exists and reconciles, treat the original request as successful.
  4. If it exists but conflicts, enter Manual Review.
  5. If it does not exist, retry the create request once.
  6. After another ambiguous result, stop and require manual reconciliation.

For HTTP 429 responses, honor a returned retry interval where available and use increasing waits. For 500-level responses, retry GET requests with bounded backoff. For 400-level validation responses, do not retry until the source data is corrected.

n8n error workflow

Create WF-99 Error Handler using n8n’s Error Trigger. Map the failed workflow name, execution ID, last node, time, and error message into Automation_Log. Send a Gmail alert to YOUR_EMAIL_ADDRESS with a link to the failed execution. Do not include OAuth tokens, full invoice payloads, or customer-sensitive attachments in the alert.

Activate the error workflow and select it as the error workflow for each production workflow. Inspect n8n Executions for node input and output, credential failures, retry attempts, and stack traces.

Failure Handling and Operational Reliability

Failure scenarios and recovery
Failure Automated response Manual recovery and owner
Missing required field Mark intake Validation Error and email the submitter. Project lead corrects the row and selects Resubmit.
Duplicate submission Find the existing Idempotency_Key and return its Milestone_ID. Finance reviews only if the submitter claims it is a distinct installment.
Duplicate workflow event Stable keys make register and payment updates idempotent. Automation administrator reconciles conflicting data.
Invalid project or milestone Stop before register or invoice creation. Sales or finance repairs the reference table.
Inactive QuickBooks customer Set Mapping exception and prevent invoice creation. Finance corrects or reactivates the customer mapping.
Authentication expiry Stop QuickBooks actions, log Authentication exception, and alert the owner. Integration owner reconnects OAuth and retries affected records.
QuickBooks validation error Do not retry automatically. Finance corrects customer, item, amount, date, or accounting configuration.
QuickBooks rate limit Wait according to the response and retry with bounded backoff. Automation owner reduces polling or batches requests if repeated.
Invoice POST timeout Query by DocNumber before any retry. Finance compares QuickBooks and the register if the result remains ambiguous.
Register update fails after invoice creation Next run queries DocNumber and recovers the existing invoice. Finance confirms customer and amount before completing recovery.
Drive folder creation fails Keep the record in Manual Review without losing approval data. Automation owner corrects permissions and retries document actions.
Invoice PDF retrieval fails Set Invoice Created, Delivery Pending and retry only the PDF step. Finance may download and deliver the existing invoice manually.
Gmail address invalid Do not mark delivery successful; set Notification exception. Finance corrects the customer billing contact and retries delivery.
Gmail service failure Retry within limits and retain the existing QuickBooks invoice ID. Finance sends the stored PDF manually if necessary.
Unavailable approver Use only an active, preapproved delegate. Controller updates delegation or reassigns the record.
Open dispute Set Reminder_Hold true and stop customer reminders. Finance records resolution and explicitly releases the hold.
Stale synchronization cursor Alert the automation owner and preserve the last successful cursor. Run a controlled catch-up request and daily reconciliation.
Partial completion Preserve completed external IDs and resume from the failed stage. Owner must not restart the entire workflow without reconciliation.
Repeated failure Increment Retry_Count and move to Manual Review after the limit. Automation administrator resolves the cause and records a recovery note.

The Manual Review

Idempotency is applied at several levels:

  • Project_ID plus Milestone_Code prevents duplicate billing records.
  • Milestone_ID is used as the intended QuickBooks DocNumber.
  • QBO_Payment_ID plus QBO_Invoice_ID prevents duplicate payment-event rows.
  • Gmail_Message_ID provides evidence of completed delivery.
  • Last_Reminder_At prevents the same reminder interval from running twice.

A daily reconciliation compares approved billing records, QuickBooks invoice IDs, invoice amounts, balances, delivery status, and open disputes. Differences remain visible until a person resolves them.

A Complete Example

A project lead at Cedar Vale Equipment completes a factory acceptance test for project PRJ-1042. The contract milestone code is FAT-02, the scheduled amount is $40,000, and the submitted billable amount is $42,500 because of an approved change order.

  1. On June 8, 2026, the project lead enters PRJ-1042, FAT-02, the completion date, $42,500, an internal Drive evidence link, the customer purchase order, and a note referencing approved change order CHG-017.
  2. n8n joins the project and schedule records. It receives the QuickBooks customer ID, item ID, 30-day terms, billing email, finance owner, and commercial approver.
  3. The validation Code node generates PRJ-1042|FAT-02 as the idempotency key and MB-PRJ1042-FAT02 as the milestone ID.
  4. The amount variance is calculated as ($42,500 - $40,000) ÷ $40,000 = 6.25%.
  5. The variance exceeds the representative 5 percent threshold, so commercial approval is required.
  6. The register record enters Pending Finance Approval. Gmail sends the finance owner a review notice.
  7. Finance confirms the evidence, customer, item, purchase order, change-order reference, and billing address. The reviewer selects Approve.
  8. n8n records the finance decision time and routes the record to Pending Commercial Approval.
  9. The commercial approver verifies CHG-017 and approves the $42,500 amount.
  10. n8n queries QuickBooks for DocNumber MB-PRJ1042-FAT02. No existing invoice is found.
  11. On June 10, 2026, n8n creates the invoice with a due date of July 10, 2026. The representative QuickBooks response returns invoice ID 91348271.
  12. The register is updated with the invoice ID before document delivery begins.
  13. n8n retrieves the invoice PDF, stores Invoice_MB-PRJ1042-FAT02_2026-06-10.pdf in the project billing folder, and sends it through Gmail.
  14. Gmail returns a message identifier, which is stored with the Drive file ID. The record moves to Invoice Issued.
  15. On June 18, the customer questions the changed amount. Finance records dispute DSP-MB-PRJ1042-FAT02-01. n8n sets Reminder_Hold to true, changes the workflow status to Disputed, and alerts the finance and commercial owners.
  16. Finance supplies the approved change-order evidence. The customer accepts the explanation on June 20. Finance marks the dispute Resolved and releases the hold.
  17. QuickBooks later records payment. The CDC workflow receives the changed invoice and payment, updates the invoice balance to zero, appends the payment event, and changes Payment_Status to Paid.
  18. The final record retains the original submission, two approvals, QuickBooks invoice ID, PDF file ID, Gmail message ID, dispute history, payment event, and every automation status change.

The amount variance was an expected business exception, not an automation failure. It triggered an additional human decision while the deterministic invoice creation and synchronization remained controlled.

Implementation Cost

All figures below are representative assumptions, not vendor quotations or verified client costs. Existing QuickBooks Online and Google Workspace subscriptions are treated as already owned, but they still require appropriate features, administration, and support.

Representative one-time implementation cost
Activity Hours Assumed rate Estimated cost
Requirements and process design 14 $65 $910
Workbook and data configuration 34 $75 $2,550
n8n and QuickBooks integration 28 $85 $2,380
Testing and user acceptance 18 $60 $1,080
Training 6 $50 $300
Documentation and deployment 8 $65 $520
Total representative implementation 108 $7,740
Representative recurring and optional costs
Cost category Representative assumption Treatment
Google Workspace $0 incremental because the scenario assumes it is already licensed Existing subscription cost excluded, not assumed to be free.
QuickBooks Online $0 incremental because the scenario assumes it is already licensed Existing subscription and accounting administration excluded.
n8n hosting and execution capacity $80 per month budget allowance Replace with the selected hosting model and current vendor terms.
QuickBooks API usage $0 incremental assumption Subject to current subscription, application, and API conditions.
Monthly maintenance 4 internal hours Included in the time-savings calculation.
Optional AI usage $25 per month budget allowance Excluded from the core automation cost.
Optional professional implementation $12,000 to $22,000 Representative alternative to most internal implementation labour, not an addition to the base estimate.

The actual cost depends on data quality, QuickBooks configuration, approval requirements, document complexity, security review, the chosen n8n hosting model, and whether customer invoice delivery can be automated under existing policy.

Estimated Time and Cost Savings

The calculation uses these representative assumptions:

  • 55 billable milestones per month.
  • 52 minutes of current handling time per record.
  • 16 minutes of normal human handling after core automation.
  • 12 percent exception rate.
  • 15 additional minutes per exception.
  • 4 hours of monthly automation maintenance.
  • $52 loaded hourly labour cost.
  • $80 recurring monthly automation cost.
  • $7,740 one-time implementation cost.

Current monthly labour hours: Monthly volume × current minutes per record ÷ 60

New monthly labour hours: Monthly volume × new minutes per record ÷ 60, plus exception handling and maintenance

Monthly hours recovered: Current monthly labour hours minus new monthly labour hours

Estimated monthly labour value: Monthly hours recovered × loaded hourly labour cost

Net estimated monthly value: Monthly labour value minus recurring tool costs

Estimated payback period: One-time implementation cost ÷ net estimated monthly value

Representative savings calculation
Calculation Formula Result
Current monthly labour 55 × 52 ÷ 60 47.67 hours
New normal handling 55 × 16 ÷ 60 14.67 hours
Exception handling 55 × 12% × 15 ÷ 60 1.65 hours
Maintenance 4 hours 4.00 hours
New total labour 14.67 + 1.65 + 4.00 20.32 hours
Monthly hours recovered 47.67 – 20.32 27.35 hours
Monthly labour value 27.35 × $52 $1,422.20
Net estimated monthly value $1,422.20 – $80 $1,342.20
Estimated payback period $7,740 ÷ $1,342.20 Approximately 5.8 months

Recovered hours do not automatically reduce payroll. They may instead provide additional billing capacity, quicker turnaround, reduced overtime, fewer administrative tasks, and less dependence on one finance employee.

Illustrative working-capital effect

The scenario assumes average monthly milestone billing of:

55 milestones × $14,500 average value = $797,500 per month

If the average delay from milestone completion to invoice creation decreases from eight calendar days to two, the invoice date moves forward by six days for qualifying records.

Illustrative receivable timing exposure: $797,500 × 6 ÷ 30 = $159,500

This does not mean the company earns or collects an additional $159,500. It represents the approximate rolling value of invoices entering accounts receivable six days earlier. If customers pay according to the same behavior after invoice receipt, associated cash receipts may also move earlier. Disputes, customer procedures, payment terms, and delivery timing can reduce the effect.

Using a representative 10 percent annual financing or carrying rate:

Illustrative annual timing value: $159,500 × 10% = $15,950

Illustrative monthly equivalent: $15,950 ÷ 12 = approximately $1,329

This financing estimate is shown separately and is not added to the labour-savings result. A business should use its own cost of capital, average invoice value, actual billing delay, collection behavior, and dispute rate.

Non-financial benefits include clearer ownership, fewer follow-up emails, fewer incomplete billing requests, consistent contract comparisons, better audit evidence, visible disputes, more reliable payment status, and improved reporting for finance, sales, and operations.

Readers should replace the representative volume, handling time, loaded labour rate, average invoice amount, delay reduction, exception rate, maintenance effort, software cost, implementation cost, and financing rate with their own figures.

Adding AI to the Automation

AI should be added only after the deterministic workflow operates reliably. The core implementation does not need AI to create IDs, validate required fields, compare amounts, calculate dates, enforce thresholds, synchronize balances, or stop reminders during disputes.

Potential AI uses include:

  • Summarizing long milestone completion notes.
  • Identifying references to acceptance evidence, change orders, or unresolved work.
  • Suggesting a missing-information category.
  • Classifying customer dispute emails for finance review.
  • Extracting candidate fields from unstructured acceptance documents.
  • Comparing a completion summary with a contract milestone description.

Exact matching, required fields, customer mapping, item mapping, monetary thresholds, due dates, approval authority, invoice creation, payment status, reminder holds, and final dispute decisions remain rule-based or human-controlled.

The recommended enhancement is an AI-assisted billing-readiness summary. It runs after deterministic validation but before finance approval. It helps finance read unstructured completion notes and evidence descriptions without deciding whether an invoice should be created.

  • Trigger: A valid billing record enters Pending Finance Approval.
  • AI input: Milestone name, expected amount, submitted amount, completion note, purchase-order presence, evidence filename or approved text extract, and variance percentage.
  • System instruction: Analyze only the supplied information, do not approve billing, and return the specified JSON structure.
  • Expected output: Summary, suggested category, missing items, reasons, and confidence.
  • Validation: An n8n Code node validates types, allowed values, required keys, and confidence range.
  • Record update: Store AI_Summary, AI_Suggested_Category, AI_Missing_Items, AI_Confidence, and AI_Reviewed_At.
  • Human review: Finance sees the suggestion alongside source data and evidence.
  • Low confidence: Confidence below 0.85 displays No Reliable Suggestion and requires normal review.
  • Prohibited data: Bank details, tax identifiers, credentials, personal data, full contracts, and information not approved for the AI provider.
  • Failure behavior: Continue with ordinary finance review and record AI_Status as Unavailable.

Use this reusable system prompt:

You are assisting a finance reviewer with project milestone billing readiness.

Analyze only the supplied record. Do not approve or reject billing. Do not make accounting, legal, credit, or contract decisions. Do not infer facts that are not present.

Identify whether the supplied narrative appears consistent with a completed billing milestone, summarize the evidence, and list information that may require human review.

Amounts, thresholds, customer mappings, required fields, approval authority, and invoice creation are handled by deterministic rules outside this model.

Return only JSON that conforms to the provided schema. If the information is ambiguous, select ambiguous_scope or missing_information and reduce confidence.

Use this reusable user prompt:

Review this candidate billing milestone.

Milestone ID: {{ $json.Milestone_ID }}
Project ID: {{ $json.Project_ID }}
Milestone name: {{ $json.Milestone_Name }}
Expected amount: {{ $json.Expected_Amount }}
Submitted amount: {{ $json.Billable_Amount }}
Variance percentage: {{ $json.Variance_Pct }}
Completion date: {{ $json.Completion_Date }}
Purchase order present: {{ Boolean($json.PO_Number) }}
Evidence filename or approved extract: {{ $json.Approved_Evidence_Text }}
Completion note: {{ $json.Completion_Note }}

Return a billing-readiness summary for a human finance reviewer. Do not make the final approval decision.

When using an AI API that supports strict structured output, request this schema:

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "billing_ready_signal": {
      "type": "boolean"
    },
    "suggested_category": {
      "type": "string",
      "enum": [
        "appears_ready",
        "missing_acceptance",
        "missing_information",
        "ambiguous_scope",
        "possible_duplicate",
        "change_reference_requires_review"
      ]
    },
    "summary": {
      "type": "string"
    },
    "missing_items": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "reasons": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1
    }
  },
  "required": [
    "billing_ready_signal",
    "suggested_category",
    "summary",
    "missing_items",
    "reasons",
    "confidence"
  ]
}

A representative valid response is:

{
  "billing_ready_signal": true,
  "suggested_category": "change_reference_requires_review",
  "summary": "The completion note reports successful factory acceptance and references acceptance evidence. The submitted amount exceeds the scheduled amount and cites a change order that requires human verification.",
  "missing_items": [],
  "reasons": [
    "Factory acceptance is described as complete.",
    "An evidence reference is present.",
    "The submitted amount differs from the scheduled amount.",
    "A change order is referenced."
  ],
  "confidence": 0.91
}

Place the following validator in an n8n Code node after the AI HTTP response. It assumes the provider returned Chat Completions-style content in choices[0].message.content. Adjust only the response extraction if a different approved endpoint is used.

'use strict';

/**
 * n8n Code node
 * Validates structured AI billing-readiness output.
 */

const allowedCategories = new Set([
  'appears_ready',
  'missing_acceptance',
  'missing_information',
  'ambiguous_scope',
  'possible_duplicate',
  'change_reference_requires_review'
]);

function fail(message) {
  throw new Error(`Invalid AI output: ${message}`);
}

return items.map((item) => {
  const source = item.json;
  const content = source.choices?.[0]?.message?.content;

  if (typeof content !== 'string' || !content.trim()) {
    fail('response content is missing.');
  }

  let parsed;

  try {
    parsed = JSON.parse(content);
  } catch (error) {
    fail(`response is not valid JSON. ${error.message}`);
  }

  if (typeof parsed.billing_ready_signal !== 'boolean') {
    fail('billing_ready_signal must be Boolean.');
  }

  if (!allowedCategories.has(parsed.suggested_category)) {
    fail('suggested_category is not allowed.');
  }

  if (typeof parsed.summary !== 'string' || !parsed.summary.trim()) {
    fail('summary is required.');
  }

  if (
    !Array.isArray(parsed.missing_items) ||
    !parsed.missing_items.every((value) => typeof value === 'string')
  ) {
    fail('missing_items must be an array of strings.');
  }

  if (
    !Array.isArray(parsed.reasons) ||
    !parsed.reasons.every((value) => typeof value === 'string')
  ) {
    fail('reasons must be an array of strings.');
  }

  if (
    typeof parsed.confidence !== 'number' ||
    parsed.confidence < 0 ||
    parsed.confidence > 1
  ) {
    fail('confidence must be a number from 0 to 1.');
  }

  const lowConfidence = parsed.confidence < 0.85;

  console.log(JSON.stringify({
    category: parsed.suggested_category,
    confidence: parsed.confidence,
    lowConfidence
  }));

  return {
    json: {
      ...source,
      AI_Status: lowConfidence ? 'Low Confidence' : 'Completed',
      AI_Billing_Ready_Signal: parsed.billing_ready_signal,
      AI_Suggested_Category: lowConfidence
        ? 'No Reliable Suggestion'
        : parsed.suggested_category,
      AI_Summary: parsed.summary,
      AI_Missing_Items: parsed.missing_items.join(' | '),
      AI_Reasons: parsed.reasons.join(' | '),
      AI_Confidence: parsed.confidence,
      AI_Reviewed_At: new Date().toISOString()
    }
  };
});

Configure the AI HTTP node with a protected API credential such as YOUR_API_KEY and an approved model identifier such as YOUR_APPROVED_MODEL_ID. Set a timeout and use bounded retries for transient failures. Log token usage or provider-reported usage fields for cost monitoring, but do not log prohibited source content.

Benefits of the AI Enhancement

  • Finance can review a concise completion summary before reading long notes.
  • Potentially missing acceptance evidence is highlighted consistently.
  • Change-order references are easier to identify.
  • Ambiguous scope descriptions are routed to human attention sooner.
  • Structured categories can improve reporting on common billing-readiness problems.
  • Reviewers retain access to the original note and evidence instead of relying on the summary.

These are specifically AI-assisted benefits. Unique IDs, duplicate prevention, approvals, QuickBooks invoice creation, payment synchronization, reminder holds, audit fields, and dashboards are benefits of the core rule-based automation.

What Remains Rule-Based or Human-Controlled

  • Required data: Deterministic validation decides whether required fields are present.
  • Duplicate detection: Exact project and milestone keys prevent duplicate billing records.
  • Amount calculations: Formulas and numeric comparisons calculate variance and thresholds.
  • Customer and item mappings: Approved reference tables determine QuickBooks identifiers.
  • Finance approval: A finance reviewer decides whether the invoice is appropriate.
  • Commercial approval: An authorized person approves changed or high-value amounts.
  • Invoice creation: n8n creates an invoice only after deterministic conditions and human approvals are complete.
  • Accounting corrections: Finance controls voids, credit memos, tax treatment, and write-offs.
  • Dispute resolution: A person assesses customer evidence and decides the response.
  • Reminder release: Finance explicitly releases a dispute or manual hold.
  • Final risk acceptance: The controller decides whether an exceptional record can proceed.

These controls remain human or rule-based because they affect accounting records, customer obligations, contract interpretation, or financial risk. An AI suggestion cannot approve an invoice, alter a contract amount, release a disputed balance, or post an accounting correction.

Estimating the Additional Value of AI

The representative estimate assumes AI reduces ordinary finance reading time by three minutes per record. Human review remains required.

Representative AI value estimate
Measure Assumption or formula Result
Original manual handling 55 × 52 minutes ÷ 60 47.67 hours
Core automation normal handling 55 × 16 minutes ÷ 60 14.67 hours
AI-assisted normal handling 55 × 13 minutes ÷ 60 11.92 hours
AI correction effort 55 × 10% × 4 minutes ÷ 60 0.37 hours
AI failure fallback 55 × 2% × 3 minutes ÷ 60 0.06 hours
AI output sampling and maintenance Representative monthly allowance 0.50 hours
Net additional capacity 2.75 – 0.37 – 0.06 – 0.50 1.82 hours
Additional labour value 1.82 × $52 $94.64
Representative AI usage cost Monthly allowance $25.00
Net additional monthly value $94.64 – $25.00 $69.64

The correction and failure assumptions are planning estimates, not expected model guarantees. Actual value depends on note length, evidence quality, model cost, provider reliability, reviewer behavior, and the percentage of records that benefit from summarization.

Testing Checklist

Use synthetic sample data before processing real customer, project, invoice, or payment information.

Required implementation tests
Test Expected result
Normal submission One register record is created and routed to finance.
Missing required field No invoice record is created; submitter receives a clear error.
Invalid field Invalid date, amount, email, currency, or URL is rejected.
Duplicate submission Existing Milestone_ID is returned without a second record.
Duplicate event Repeated workflow execution remains idempotent.
Failed authentication Accounting action stops and an authentication exception is logged.
Expired credential OAuth refresh succeeds or the workflow alerts the owner without repeated unsafe retries.
Failed API request Retry policy follows status type; invalid requests wait for correction.
Ambiguous invoice timeout DocNumber is queried before another create request.
Unavailable approver Only an approved active delegate receives the request.
Rejection Record closes, no invoice is created, and reason is retained.
Return for information Record moves backward and project lead receives the comment.
Reassignment New owner receives notice and the prior owner remains in history.
Overdue approval Reminder and escalation occur at configured intervals.
Invoice due reminder Correct customer, invoice number, balance, and due date are used.
Open dispute Reminder_Hold is true and customer reminders stop.
Failed file upload Invoice remains created but delivery does not falsely complete.
Failed document creation Drive or PDF exception enters the recovery queue.
Failed notification Gmail failure is logged and delivery status remains incomplete.
Unauthorized user Protected columns or access controls prevent the action.
Partial payment Balance and Partially Paid status synchronize correctly.
Full payment Balance becomes zero and status becomes Paid.
Malformed AI output Validator rejects the response and ordinary human review continues.
Inaccurate AI output Reviewer can ignore or correct the suggestion without affecting approval logic.
AI service failure AI_Status becomes Unavailable and core processing continues.
Successful completion Register contains invoice, PDF, Gmail, approval, and payment references.
Correct reporting Views and pivots match source register totals and statuses.
Correct audit record Every status and external action has a timestamp and owner.
Correct retry behavior Retries are bounded and Retry_Count increments accurately.
Daily reconciliation Register totals and balances match QuickBooks for sampled invoices.

Ongoing Maintenance

The controller is the primary business owner. The automation administrator is the technical owner, and a trained backup administrator must be able to review failures and deactivate workflows.

Maintenance schedule
Frequency Activity Owner
Daily Review failed executions, Manual Review records, stale synchronization cursors, and delivery-pending invoices. Automation administrator and finance
Weekly Reconcile approved milestones, QuickBooks invoices, open balances, disputes, and reminders. Finance owner
Monthly Review automation costs, execution volume, exception trends, and aging measures. Controller and automation administrator
Monthly Sample invoice PDFs, Gmail delivery records, approval evidence, and optional AI outputs. Finance and control owner
Quarterly Review workbook, Drive, n8n, Gmail, and QuickBooks permissions. System owners
Quarterly Test an authentication failure, API failure, duplicate event, and restore procedure. Automation administrator
Quarterly Check reference data for inactive customers, items, projects, and former approvers. Finance and sales operations
According to policy Rotate client secrets, reconnect OAuth, and review authorized applications. Security or system administrator
Semiannually Review reminder templates, approval thresholds, escalation timing, and delegation rules. Controller
Annually Review retention, archive closed records, verify backups, and reassess whether the architecture remains appropriate. Business and technical owners

Workflow changes should be tested in the sandbox and test workbook. Export versioned workflow definitions before deployment. Update operating documentation whenever fields, thresholds, credentials, mappings, templates, or recovery procedures change.

If AI is enabled, sample both high-confidence and low-confidence outputs. Track malformed responses, correction categories, usage cost, provider incidents, and any evidence that employees are treating suggestions as approvals.

When to Move to Dedicated Software

The implementation should not be replaced merely because it uses Google Sheets. It remains appropriate while volume, security needs, workflow complexity, and maintenance effort are controlled.

Reassess the architecture when one or more of these conditions become persistent:

  • Monthly milestone volume grows enough that spreadsheet reads and row-level processing become slow or costly.
  • Multiple users create concurrent records and database-level uniqueness becomes necessary.
  • The business needs formal, immutable approval evidence.
  • Multiple legal entities, currencies, tax regimes, or accounting systems must be supported.
  • Revenue recognition differs materially from invoice creation.
  • Milestones require complex percentage completion, retainage, progress claims, or multi-line allocations.
  • Customers require a secure portal for acceptance, invoice delivery, disputes, or payment status.
  • Mobile or offline project submission becomes a major requirement.
  • Role-based permissions become too granular for a shared workbook.
  • Exception rates or maintenance hours increase steadily.
  • Formal audit, regulatory, or segregation-of-duties requirements exceed the available controls.
  • More project-management, CRM, inventory, procurement, and accounting systems must be integrated.
  • The company requires vendor-supported service levels for billing operations.
  • Advanced forecasting, collections management, revenue schedules, or consolidated reporting becomes necessary.

Relevant upgrade categories include project accounting platforms, professional services automation systems, construction or manufacturing billing applications, accounts-receivable automation platforms, and broader ERP systems. A database-backed application can also replace the spreadsheet while retaining n8n and QuickBooks Online if the workflow logic remains useful.

Implementation Checklist

  • Confirm business requirements, billing policies, and human decision points.
  • Document the current milestone-to-invoice process and baseline delay.
  • Select the production and test tools.
  • Create controlled accounts, credentials, and backup owners.
  • Configure least-privilege permissions.
  • Build Projects and Milestone_Schedule reference data.
  • Create the protected milestone intake.
  • Create the Billing_Register and related tabs.
  • Define exact field names, types, allowed values, and ownership.
  • Define Milestone_ID and Idempotency_Key rules.
  • Connect Google Sheets, Google Drive, QuickBooks Online, Gmail, and n8n.
  • Validate every source-to-destination field mapping.
  • Build intake validation and duplicate prevention.
  • Build finance and commercial approval routing.
  • Configure reminders, escalations, delegation, rejection, and return behavior.
  • Build QuickBooks invoice lookup, creation, and recovery logic.
  • Store returned invoice IDs before downstream document actions.
  • Retrieve, store, and deliver invoice PDFs.
  • Build payment and invoice synchronization.
  • Implement dispute holds and controlled reminder release.
  • Create operational, exception, aging, and processing-time views.
  • Configure error workflows, logging, retries, and manual recovery.
  • Review security, privacy, retention, and audit controls.
  • Test all code with synthetic data.
  • Complete user acceptance testing and a controlled pilot.
  • Document deployment, rollback, and support procedures.
  • Replace representative implementation-cost assumptions.
  • Replace representative labour, volume, delay, and working-capital assumptions.
  • Add AI only after the core workflow is stable.
  • Validate AI schemas, fallback behavior, prohibited data, and human review.
  • Assign primary and backup maintenance owners.
  • Define objective criteria for moving to a database or dedicated billing platform.

You need a similar solution?

Get a FREE
Proof of Concept
& Consultation

No Cost, No Commitment!