A job application tracker that tailors resumes and cover letters with Claude, with a credit limit that holds under concurrent requests and LaTeX and DOCX export.
Problem
Applying well is slow. Every role wants its own resume and cover letter, and tracking ends up in a spreadsheet. Offerstitch keeps every application in one pipeline and uses Claude to tailor the documents to each job description. The hard part wasn't the AI call. It was making a usage limit hold when one user fires several requests at once.
My Contribution
I built it solo: product, architecture, code and prompts.
- A Kanban pipeline across eight statuses with drag-and-drop, a table view, soft delete with restore, and a tracker for funnel, response rate, weekly goals and applications that have gone quiet
- Seven Claude features, each priced in credits before the click: tailored resume, cover letter, job description analysis, interview prep, eight outreach message types, quick import from a pasted posting, and LaTeX export
- A credit allowance enforced by one atomic conditional update: charged up front, refunded on failure, with per-user rate limiting in front
- A private demo account per visitor, seeded with sample data, served from AI fixtures and deleted after 2 hours
- Admin approval for new users, editable plans, per-user credit overrides, a cost-against-revenue margin view and an audit log
- 206 Vitest tests against a real MongoDB. Typecheck, lint, a docs check, tests and build run in CI on every push and pull request
Architecture
Next.js 16 App Router and TypeScript, MongoDB via Mongoose, Clerk for identity, deployed on Vercel. Every query goes through a repository layer, and an ESLint no-restricted-imports rule makes importing a Mongoose model from anywhere else a build error. That keeps userId scoping and the soft-delete filter in one place.
The obvious way to enforce a usage limit is to read usage, compare it to the limit, call the model and record the cost. That's check-then-act with a multi-second window: concurrent requests all read the same stale total and all pass. When limits were still in dollars, a test sent 20 parallel requests at a $5.00 budget and spent $14.99. Now the check and the charge are a single conditional update that only matches when the whole price fits. A test runs 30 parallel 6-credit requests against a 100-credit allowance and grants exactly 16.
Limits are in credits rather than a flat generation count because a tailored resume costs roughly five times a short outreach message to run, so prices are weighted 6 to 2. The real USD cost of every call is still recorded, which is what the admin margin view is built from.
The demo started as one shared account, so each visitor saw what the last one typed. Now every visitor gets their own, guarded by Cloudflare Turnstile, per-IP and global rate limits, a cap on live demos and a kill switch. Export runs two pipelines: LaTeX from templates compiled into the source and cached on the document, and DOCX poured into an admin-uploaded template, keeping its styles and run formatting, with a pixel-width budget so content doesn't overflow and a generic fallback when no template exists.
Outcomes
Live at offerstitch.vercel.app with a one-click private demo. The full loop works: import a posting, analyse it, generate tailored documents, export them and track the application. Eight ADRs record why the main decisions went the way they did.
Learnings
A limit that is checked but not reserved only holds for serial traffic. The overspend only showed up once a test fired parallel requests, so the quota and rate-limit tests now run against a real MongoDB instead of a mock. Second lesson: the repo once documented a test suite and CI that didn't exist. check:docs now fails CI if the docs cite a script that isn't there.