AI-powered note transcription → structured task extraction → automated client delivery. A production-scale, polyglot SaaS application that eliminates the manual overhead between a contractor's meeting notes and their client's inbox.
Next.js 15 App Router. System of record: owns auth (Auth.js), database (Prisma + Postgres), public API, client email delivery. The only service that writes the DB.
TypeScriptPython FastAPI + arq worker. Stateless: transcribes audio via Whisper, extracts structured tasks via LLM, POSTs results back via HMAC-signed callback. No DB access.
Python 3.11Cross-service contract: Zod schemas (TypeScript) mirrored as Pydantic models (Python). The seam between services is typed in both languages — no drifting interfaces.
Shared ContractClient uploads audio. Web app stores blob to S3/MinIO, creates Note row (PENDING), enqueues job to Redis. Returns 202 Accepted immediately — zero blocking.
arq worker dequeues. Pulls audio from object storage. Runs through Transcriber interface (OpenAI Whisper or faster-whisper — pluggable by config, no rewrite).
LLM call with JSON schema constraint — model must return { title, summary, items[] }. Output validated by Pydantic before accepted. Retries on malformed output.
AI service POSTs result to /api/internal/callback. Body signed with HMAC-SHA256, verified in constant time. Untrusted callers get 401, result discarded.
Web app writes tasks to Postgres, marks Note COMPLETE. Sends formatted email via Resend. Generates share link for client portal access.
Every pipeline run writes a Job row: type, status, attempts, error, timestamps. Failures are a DB state — never a silent void.
Transcription takes 10–40s. Holding an HTTP request open that long would break serverless timeouts and make the UI feel broken. Upload returns 202 immediately; a Redis-backed worker does the slow work.
Trade-off: operational cost of a queue + eventually-consistent status modelTypeScript for the product (types from DB to UI). Python for the AI pipeline (Whisper, model SDKs, audio libs). Each language where it's strongest; separate scaling and failure domains.
Trade-off: two runtimes, one internal HTTP hopOnly apps/web touches Postgres. The AI service returns results over a callback; the web app persists them. One migration owner, unambiguous schema authority, simpler auditing.
Trade-off: one extra HTTP callback vs a direct DB writeThe AI worker signs the request body with HMAC-SHA256. The web app verifies in constant time before trusting the payload. Internal services are never trusted blindly — not even on a private network.
Trade-off: shared-secret auth vs mTLS (right level of security for V1)Freeform prompts return unparseable prose. Instead, extraction uses JSON-schema constrained output so the model must return a typed structure. Validated by Pydantic before accepted.
Trade-off: occasional retry on malformed output vs deterministic storageTranscription and extraction providers change on price/quality. The Transcriber and Extractor interfaces let you swap Whisper for Deepgram or change LLM vendors via config — no rewrite required.
Trade-off: small indirection layer up front; pays back on first vendor change