Automation & workflows

Your repetitive tasks handed to reliable workflows, with AI where judgment is needed, and alerts when something goes wrong.

Not everything needs an agent. A large share of back-office work is solved by a well-designed workflow: I connect your tools, automate the repetitive chains (sorting, data entry, follow-ups, reporting) and only add AI where it brings judgment. All of it supervised: a workflow that fails should alert you, not carry on silently.

Key facts

0

manual rekeying on automated processes

7 days

for a first useful workflow in production

100%

of failures visible: alerts, logs, statuses

Automation & workflows

What I build

01

Business process automation

Email triage and dispatch, data entry, follow-ups, reporting: repetitive chains executed by workflows plugged into your tools.

n8n · Workflows · Back-office

02

AI-augmented workflows

AI inserted at the right step of the workflow to classify, extract, summarize or draft. The rest stays deterministic, and therefore predictable.

LLM · Classification · Extraction

03

Custom integrations

CRM, ERP, email, spreadsheets, internal tools: I connect them through APIs and webhooks, including when no off-the-shelf connector exists.

API · Webhooks · CRM · ERP

The promise

Repetitive work runs on its own, under control.

Demonstration

A supplier invoice stopped before the ledger

A building engineering firm receives supplier invoices by email, in as many formats as it has suppliers, then rekeys them by hand into its accounting tool. Someone checks the purchase order from memory, and a line billed on top ends up paid.

Invented caseThis case is not an engagement. It is invented end to end, and the code below is the real code that would run it.

  1. HumanThe morning request

    This morning's supplier invoices are in the accounting mailbox. I want the amounts pulled out, checked against the purchase order, and only what fails to match on my desk.

  2. Tool callDocument extraction

    {
      "tool": "document.extract",
      "source": {
        "channel": "email",
        "mailbox": "accounting",
        "message_id": "AAMkAGI3f21b4c9a",
        "attachment": "august-invoice.pdf",
        "received_at": "2026-09-03T07:41:12+02:00"
      },
      "expected_fields": [
        "invoice_number",
        "date",
        "currency",
        "amount_ex_tax",
        "amount_inc_tax",
        "order_reference",
        "lines"
      ],
      "minimum_score_per_field": 0.85
    }

    No per-supplier template. The document layout is not an assumption the system makes, it is an input it has to take as it comes.

  3. Tool responseExtracted fields

    {
      "status": "extracted",
      "supplier_trade": "aluminium joinery manufacturer",
      "invoice_number": "F-2026-3184",
      "date": "2026-08-28",
      "currency": "EUR",
      "amount_ex_tax": 4968.40,
      "amount_inc_tax": 5962.08,
      "order_reference": "BC-2026-0771",
      "lines": [
        {
          "description": "Aluminium frame, series 60",
          "quantity": 12,
          "unit_price_ex_tax": 386.20,
          "total_ex_tax": 4634.40
        },
        {
          "description": "On-site delivery charge",
          "quantity": 1,
          "unit_price_ex_tax": 334.00,
          "total_ex_tax": 334.00
        }
      ],
      "scores": {
        "invoice_number": 0.98,
        "amount_ex_tax": 0.97,
        "order_reference": 0.93
      },
      "fields_below_threshold": []
    }
  4. The agentReading the difference

    The invoice carries the purchase order reference, read above the minimum score. So I read the order from the ERP by that reference rather than searching by supplier. It holds the twelve frames, not the delivery charge billed on top. I do not settle that difference myself: I pass both amounts to reconcile_invoice_with_order, which applies a written, tested tolerance.

  5. Tool callCalling the reconciliation

    {
      "tool": "reconcile_invoice_with_order",
      "invoice": {
        "number": "F-2026-3184",
        "currency": "EUR",
        "amount_ex_tax": 4968.40
      },
      "purchase_order": {
        "reference": "BC-2026-0771",
        "read_by": "erp.orders.read",
        "status": "open",
        "currency": "EUR",
        "amount_ex_tax": 4634.40
      }
    }

    The model does not judge the difference and does not supply the threshold. It calls a deterministic function whose tolerance owes nothing to how the invoice is worded.

  6. AnswerBack to accounting

    Invoice F-2026-3184 is not posted. It goes to human review: the difference with purchase order BC-2026-0771 is 334.00 EUR, against a tolerance of 15.00 EUR. The line at fault is named: an on-site delivery charge that is not on the order. Whoever opens the task sees both amounts, the offending line, and two ways out: accept the difference and post it, or send the invoice back to the supplier.

    The run stops on an invoice that was never posted. That is the expected behavior: the automation knows what it cannot settle.

The codereconcile_invoice_with_order.pypython

The reconciliation rule

"""Reconcile a supplier invoice with its purchase order."""

from dataclasses import dataclass
from decimal import Decimal

ABSOLUTE_TOLERANCE = Decimal("15.00")
RELATIVE_TOLERANCE = Decimal("0.01")
RELATIVE_REJECT_THRESHOLD = Decimal("0.25")

POST_TO_LEDGER = "post_to_ledger"
HUMAN_REVIEW = "human_review"
REJECT = "reject"


@dataclass(frozen=True)
class Decision:
    status: str
    difference: Decimal
    tolerance: Decimal
    reason: str


def _amount(source, field):
    value = source.get(field)
    if value is None:
        raise ValueError("missing amount: " + field)
    return Decimal(str(value))


def reconcile_invoice_with_order(invoice, purchase_order):
    if invoice.get("currency") != purchase_order.get("currency"):
        return Decision(REJECT, Decimal("0"), Decimal("0"), "currency mismatch")

    invoice_amount = _amount(invoice, "amount_ex_tax")
    order_amount = _amount(purchase_order, "amount_ex_tax")
    difference = abs(invoice_amount - order_amount)

    # The stricter of the two tolerances wins, otherwise a large purchase order
    # buys the right to a large difference.
    tolerance = min(ABSOLUTE_TOLERANCE, order_amount * RELATIVE_TOLERANCE)

    if difference > order_amount * RELATIVE_REJECT_THRESHOLD:
        return Decision(REJECT, difference, tolerance, "difference beyond a keying error")

    if difference > tolerance:
        return Decision(HUMAN_REVIEW, difference, tolerance, "difference above tolerance")

    return Decision(POST_TO_LEDGER, difference, tolerance, "difference within tolerance")

An automation that is never allowed to stop does not remove the rekeying: it moves it to correcting ledger entries, later and at a higher price.

Automation & workflows

Before / after

Automation & workflows

The stack

n8n

Make

Zapier

Python

TypeScript

API

Webhooks

Claude

Automation & workflows

Straight answers

01

Which processes should be automated first?

High-volume, low-judgment repetitive tasks: email triage, data entry, follow-ups, reporting, syncing between tools. A quick audit identifies the ones with the biggest payoff, often within days.

02

n8n, Make, Zapier or custom code?

n8n for most cases: self-hostable, versionable, extensible with code. Make and Zapier for simple, quick needs. Custom code when the logic outgrows what a visual tool can maintain cleanly. I choose based on your case, not my habits.

03

What happens when a workflow fails?

It tells you. Alerts, logs, a status for every run, the ability to retry: I design how it fails before I design how it works. A workflow that fails silently costs more than the manual task it replaced.

04

Do my automations need AI?

Not always. When the rule is clear, a deterministic workflow is enough and costs less. AI comes in where judgment is needed: classifying an ambiguous email, extracting data from a document, drafting a reply.

Read on this topic

Contact

Ready to go from demo to production?

Reply within 24 hours · first conversation free, no strings attached.