Quick overview
I stopped trusting a model to add up its own numbers, so this one checks its work in plain code. It reads the table off a PDF, sums the line items in JavaScript, and only writes to your books when that sum matches the printed total.
How it works
- Triggers when a new Gmail message arrives under a specific label and includes an attachment, downloading the attachment for processing.
- Filters to PDF attachments only and converts each PDF into plain text.
- Uses OpenAI with a structured output schema to extract the report title, currency, stated total (as printed), and all line-item rows from the PDF text.
- Calculates the sum of extracted line totals in JavaScript and compares it to the stated total using the configured absolute and percentage tolerances.
- If the totals reconcile, splits the extracted rows into individual line items and appends them to the “Line Items” tab in Google Sheets.
- If the totals do not reconcile (or no stated total is available), appends a summary row to the “Needs Review” tab in Google Sheets and posts a mismatch alert to a Slack channel.
Setup
- Add credentials for Gmail, OpenAI, Google Sheets, and Slack (OAuth2) in n8n.
- Create or choose a Gmail label for incoming report emails and set its label ID/name in the Gmail trigger filter.
- Update the Google Sheets document URL and ensure it contains the “Line Items” and “Needs Review” tabs (or rename the sheet targets in the Report Settings).
- Set the Slack channel name for reconciliation alerts in the Slack message node.
- Review and adjust the tolerance values and default currency in Report Settings to match your reconciliation rules.