ESF Plus

A European Social Fund Plus programme in Baden-Württemberg, run through L-Bank, needed its funded organisations to send documents to a public authority securely.

I was its only engineer for three years, employed by ISG Institut. I did the architecture, the build, the security model and the operations. Requirements gathering with L-Bank was my employer’s process, and I built against it.

Status on 30 September 2026
Stage
In production
Handed over
2025
Uploads
22,000+ processed, from 718 organisations
Outcome
My employer commercialised it for €500,000
Security
No reported security incident
The sign-in page. A panel headed Wichtige Informationen explains where credentials come from and who issues them; the sign-in form sits to its right.
Sign-in, with the access rules stated on the page itself.
The account administration table, headed 718 accounts in total, showing its first three rows. The organisation, contact-person and both e-mail columns are covered by one black redaction bar.
One account per organisation. Names and addresses redacted.

The constraint

Someone from the funding bank has to be able to ask, in 2029, who uploaded which document in 2024 and who looked at it. That question comes years after the software is finished. The answer has to be recorded from the first upload onwards.

How it is built

The audit record is written before the document is stored, so an upload that fails halfway still leaves a trace. Extraction runs on the server, so a malformed upload never becomes the browser’s problem. Role and ownership checks live in one layer that every request passes through. PHPUnit tests are written around the permission model and the extraction path.

Trade-offs

The audit trail came first

I built the audit trail before the upload feature that needed it. The usual order is to add logging later. The cost was weeks with nothing to demonstrate, while I was asked when the upload screen would work. Because of that order, every upload in the programme has an append-only record, written before the document.

Livewire for the interface

Livewire kept me to one language and one mental model, which is why one person could finish it. The interface is chattier over the network, and on a bad connection that shows. I would keep it and measure earlier.

Built with

Server
Laravel and Livewire, on PHP
Database
MySQL