P-03 · BUILD / PROJECT · 2026 SHIPPED

AML/CFT training platform

In-house annual AML/CFT and proliferation-financing training, with a quiz the client can't cheat.

STACK: Next.js 16DrizzleCloudflare D1R2Workers

Why I built it instead of buying it

Annual AML/CFT training is an obligation, and the market answer is an off-the-shelf e-learning subscription. Those courses are written for everyone, which means they are written for nobody: generic typologies, generic red flags, a quiz you can click through while doing something else. I was paying a subscription for content I then had to supplement anyway.

So I wrote the course against MAS notices myself and built the platform to deliver it. The training content stays internal. The delivery system is the part I can talk about.

How it works

The course is 33 guided slides covering money laundering, terrorism financing and proliferation financing, with formative checks along the way so people find out they’ve drifted before the assessment does it for them.

The assessment is server-authoritative. Questions and answers are graded on the server; the client is never sent the key. There are two parallel forms, A and B, and attempts alternate between them, so a second sitting is not a memory test of the first. Retries are unlimited, the pass mark is nine out of ten, and each learner is locked to one live attempt at a time.

The part I like most is what happens on a fail. Rather than “try again”, the platform maps wrong answers back to learning objectives and tells you which objectives to revisit. A failed attempt becomes a reading list.

A failed attempt, mapped back to the objectives to revisit

On a pass it generates a certificate with pdf-lib: serial-numbered, with a QR code that resolves to a verification page. When an auditor asks who completed training and when, the answer is a record with a check attached rather than a spreadsheet row.

A pass, on the second attempt's alternate form

The stack, and one small script

Next.js 16 with Drizzle on Cloudflare: D1 for data, R2 for artefacts, Workers at the edge. Sign-in is SSO through Cloudflare Access, either a one-time PIN or Entra ID, so there is no password store to look after.

There’s also a script in the repository whose entire purpose is to fail the build if quiz answers ever appear in the client bundle. Server-authoritative grading is only true until somebody refactors it accidentally.

Where it stands

Shipped and running. It replaced the subscription, and the training now says exactly what I want it to say.