โ† agent army
๐Ÿงพ
SLACK

Cash Flow Cop

The finance team, living in a Slack DM.

A Slack finance bot I DM in plain English โ€” what's overdue, total outstanding, due in the next 7 days, biggest creditor. Scans the billing inboxes twice a day, tracks every invoice from scan to payment in one database, marks things paid on a confirm, and auto-reconciles a dropped-in Revolut CSV.

// architecture

A hub-and-spoke around one Postgres database (Neon, EU) that's the single source of truth. On GCP, a set of Cloud Functions do the work: an email scanner (cron, 9am/9pm London), the Slack bot itself (HTTP-triggered by Slack events โ€” not an always-on process), and a renderer that mirrors the database into two read-only Google Sheets. A Next.js web app (Docker โ†’ Cloud Run) is the visual Command Center and the Revolut CSV upload. Everything reads and writes the same Postgres.

Gmail billing inboxโ†’scanner (Cloud Fn, cron)โ†’Claude Haiku extractโ†’Postgresโ†’Slack bot โ‡„ meโ†’Sheets mirror

// how it works

  1. 1

    Scan the inboxes

    Twice a day โ€” 9am and 9pm London โ€” a Cloud Function sweeps the billing inboxes over the Gmail API and pulls the invoice PDF from each new mail.

  2. 2

    Extract with Claude

    Each PDF is sent to Claude Haiku 4.5 as a document block (with a cached system prompt) to pull amount, currency, vendor and due date, then de-duped and UPSERTed into Postgres.

  3. 3

    Ask in plain English

    The Slack bot doesn't write SQL. It reads all invoices from Postgres, computes the buckets and does keyword routing in Python to build context, then hands that to Claude Haiku with your question and the last few turns โ€” Claude replies with strict JSON (query / edit / clarify / refresh / confirm) that the bot acts on.

  4. 4

    Mark things paid

    'mark 1042 paid' โ†’ it asks you to confirm โ†’ 'confirm paid 1042' โ†’ it writes the status to Postgres, the sheet and an audit log. The confirm step is deterministic โ€” no LLM in the money path.

  5. 5

    Auto-reconcile Revolut

    Drop a Revolut CSV into the web app and it matches each payment to an invoice by exact reference and flips the matched ones to Paid โ€” idempotent, sheet-first-then-DB to avoid racing the sync job.

  6. 6

    Mirror everywhere

    A renderer Cloud Function pushes the same data into a web Command Center and two read-only Google Sheets, so everyone is looking at identical numbers.

// tool calls

  • gmail.messages.list / attachments.getsweeps billing inboxes and pulls the invoice PDF
  • messages.create (Claude Haiku 4.5)extracts invoice fields from the PDF; classifies the Slack question โ†’ JSON
  • chat.postMessage (Slack)the bot's replies (inbound requests HMAC-verified)
  • Postgres UPSERT ON CONFLICTde-dupes invoices by (vendor, invoice #); writes an invoice_audit row on edits
  • sheets.values.updaterenders the mirror sheets and applies live edits
  • Revolut CSV importexact reference match โ†’ flips matched invoices to Paid

// slack dm

what's overdue?
Cash Flow Cop:3 invoices overdue ยท ยฃ8,240 total. Biggest: Acme Labs ยฃ5,100 (12 days late).
mark 1042 paid
Cash Flow Cop:Confirm: mark #1042 (ยฃ1,900, Acme Labs) paid? reply `confirm paid 1042`

// stack

PythonSlack APIPostgres (Neon)Claude Haiku 4.5Gmail APIGoogle Sheets APINext.js

// infrastructure

  • โ–ธCompute: GCP Cloud Functions + Cloud Run
  • โ–ธDB: Neon Postgres (EU) โ€” single source of truth
  • โ–ธSchedule: Cloud Scheduler (scan 9am/9pm; sheet render ~5 min)
  • โ–ธModel: Claude Haiku 4.5 (extraction + NLQ)
  • โ–ธAccess: Slack-ID allowlist; Google-OAuth allowlist on the web app

// why it exists

'How much do we owe, and what's overdue?' used to be a spreadsheet archaeology dig. Now it's one Slack message, answered in seconds.

โ† back to all agents// part of the agent army