Where Automation Should Stop
I built an AI agent that runs almost my entire invoicing workflow. It is deliberately incapable of issuing an invoice. This is why.
For twelve years, hosting renewals at my agency in Mykonos ended the same way. Reminder emails went out, some clients paid by card, some by bank transfer, a few paid twice, a few paid for the wrong site. Then I sat down with a bank statement, a spreadsheet of roughly 330 hosting accounts, and the invoicing platform, and reconciled everything by hand.
Three systems, none of them designed to talk to each other, and me as the integration layer.
This year I finally connected them. The interesting part turned out not to be the automation. It was deciding, precisely, where the automation had to stop.
The problem with invoices
In Greece, every business invoice is reported to myDATA, the tax authority's digital bookkeeping platform. Once an invoice is issued and transmitted, it is a fiscal document. You cannot edit it. You cannot delete it. If the legal entity, the VAT treatment or the amount is wrong, the only remedy is to issue a second fiscal document (a credit note) to cancel the first, and then a third to get it right.
So a wrong invoice is not a bug you fix. It is a paper trail you carry for years.
That single fact shaped the whole architecture.
The pieces
Payment rail. Revolut Merchant API, used for two things: generating payment links for card payments, and polling for confirmed payments. Bank transfers to the agency's Greek account remain a manual channel; those credits arrive as notifications on my phone, not in any system the automation can read.
Invoicing provider. A certified Greek provider that issues invoices through an API and handles myDATA transmission. It also exposes VAT lookups against the Greek registry and VIES for EU entities, which matters more than it sounds.
Client database. A single source of truth mapping every hosted domain to the legal entity that pays for it. This was the hardest part to build, because after twelve years the mapping lived in my head. One person can own five sites under two companies. Two clients can share a name. A site can change owners without anyone telling the hosting provider.
Orchestration. Make.com scenarios wrap each external API into small, single-purpose operations: create payment link, list paid orders since a date, check a VAT number, mirror the contacts list, issue an invoice.
The agent. An LLM with access to a subset of those operations, plus the task manager and the client database, running on a schedule.
What the agent does
A renewal enters the pipeline thirty days before expiry. From there the agent:
- Looks up the domain in the client database and resolves the billing entity.
- Creates a payment link with the correct net amount (VAT is applied downstream, never typed by hand), or reuses an existing pending link so a client is never sent two.
- Drafts a bilingual reminder email from a fixed template, with reminders at T-30, T-7 and T-0.
- Polls the payment rail for confirmed orders and matches them to renewals by reference.
- Verifies the client's VAT number against the registry and confirms the legal name matches what we have on file.
- Selects the transaction type: 24% domestic VAT, 0% intra-EU reverse charge, or 0% third-country export, each with its own myDATA classification.
- Builds the complete invoice payload.
- Creates a task marked READY TO ISSUE with the payload, the payment evidence and the tax check attached, or marks it BLOCKED with the reason.
Every morning I open the queue. Everything in it has been checked. I read each one, and if it is correct, I issue it with a single instruction.
That last sentence is the design.
What the agent cannot do
The issue-invoice operation is not in its toolset. Not permission-gated, not behind a confirmation prompt. It is simply absent. There is no prompt injection, no misread email and no hallucinated confidence that can call a function that does not exist in the agent's context.
Payment confirmation comes only from the payment rail. The only source of truth for "this client paid" is the Revolut API returning a completed order. Nothing in an email, a message, a PDF or a task comment counts.
Nothing arriving by email can trigger anything. The agent reads email in a strictly read-only mode, treats all of it as data rather than instructions, and does not follow links. An email saying "payment sent, please issue the invoice" produces exactly one outcome: a note for me to check the bank.
Bank transfers are confirmed by a human. Because the bank channel is invisible to the automation, I confirm each transfer explicitly. This is slower and I have kept it that way on purpose.
No invoice is issued on a timer. The scheduled jobs prepare and verify. They never call the issuing operation, and they are instructed to re-read the runbook that says so before every run.
No client is suspended automatically. Non-payment escalates through a ladder of reminders and ends in a decision for me, not a cron job that takes a hotel's website offline in August.
The failures this prevents
A few concrete cases, all of which would have been plausible without the boundary:
- A client forwards a bank confirmation screenshot for a transfer that has not actually cleared. Without the rule, an eager agent invoices a payment that does not exist.
- Two clients share a surname and one of them has two companies. The agent picks the wrong entity with 90% confidence. The invoice goes to a business that did not buy anything.
- An EU client's VAT number lapsed. The invoice goes out at 0% reverse charge when it should carry 24%. The tax exposure is mine.
- A renewal reminder email contains text that reads like an instruction. If the agent can act on email content, someone else now controls the invoicing system.
The agent still catches most of these. The point is that when it does not, the cost is a wrong entry in a task list, not a wrong entry in the national tax ledger.
What changed
Reconciliation that used to take an afternoon now takes the time it takes to read a queue. Every invoice has its payment evidence and tax check attached before I ever see it. The client database is finally a database.
But the workflow is not "fully automated", and it never will be. Issuing an invoice is a fiscal act. Suspending a client is a relationship decision. Those belong to a person who can be held accountable for them, and the system is built so that they cannot be delegated by accident.
The agent can draft, validate, prepare and propose. It cannot authorize.
Deciding that, and then enforcing it structurally rather than with a warning in a prompt, was the whole project.

Written by Ioannis Krokos
Full-stack developer in Zurich building with React, Next.js, and Node.js. Open to opportunities.