I built Statementory to review UCAS personal statements with AI. Score out of 100, line-by-line annotations, rewrites, improvement plan. The kind of feedback a good teacher gives you, available at 2am the night before the deadline.
Here's what I learned building it solo.
Everything flows from one constraint: the AI takes 5 minutes
Claude processing a full personal statement and generating structured feedback takes 5 to 10 minutes. Vercel serverless functions timeout at 10 to 60 seconds depending on your plan. So the obvious approach — hit the AI API directly from a route handler — was never going to work.
The solution was separating the job from the request. The API route triggers a background job and returns immediately. The job runs on a separate worker for as long as it needs. The user gets an email when it's done.
This is the architecture decision that everything else depends on. If you're building anything with a slow AI call, solve this first.
The bug that ate my first customers' reviews
The Stripe webhook fires, triggers the background job, done. Simple enough.
Except I was awaiting an email send before triggering the job. Resend was occasionally slow. When it took too long, the serverless function timed out before the job ever fired. Payment went through. Review never generated.
The fix: the job fires first, unconditionally. Everything else — emails, logging, analytics — is best effort after that.
I had to write a recovery script that cross-checked Stripe payments against generated reviews and re-triggered the missing ones. That was a fun Sunday.
Lesson: in a webhook handler, the only thing that matters is triggering the downstream job. Everything else waits.
Custom auth over a library
I tried an auth library first. The App Router support felt half-baked for what I needed and I was fighting the abstraction more than it was helping me.
Custom magic-link auth ended up being about 200 lines. User enters email, gets a link, clicks it, gets a session cookie. No OAuth complexity, no provider dependencies, works exactly how I want.
The tradeoff is maintenance. If I ever need OAuth I'm building it myself. For now it's fine.
The PDF problem nobody warns you about
The review is delivered as an HTML email and a downloadable PDF. The PDF is generated by rendering the HTML in a headless browser.
What nobody tells you: certain unicode characters don't render in headless browsers. I had icons in the report that showed as empty squares in the PDF. Took longer to debug than it should have. The fix was replacing unicode characters with pure CSS shapes.
Test your PDFs on the actual generation environment, not your local machine. Fonts and character support differ.
What I'd tell myself at the start
Three things:
First, solve the slow AI call problem before you write anything else. It touches everything.
Second, pin your third party API versions as constants and import them everywhere. A version mismatch across multiple files is a miserable thing to debug.
Third, GDPR compliance is an architecture decision, not a feature you add at the end. Cascade deletes, data anonymisation, unsubscribe tokens — if you're building for European users, design for deletion from day one.
Statementory is live at statementory.com Happy to answer questions in the comments.



















