Intelligent Legal Correspondence
Universidad Nacional de Colombia · 2026 - At present
The Campus Vice Chancellor's Office signs resolutions, travel allowances and commissions that arrive by email from every department on campus. This application turns that inbox into a traceable workflow: an agent ingests the mail, an AI summarizes and classifies it, the secretary reviews and assigns it, the Vice Chancellor decides, and the approved document comes back signed, archived and delivered to whoever asked for it.
Architecture and Tech Stack
Core Architecture
- Framework: Next.js 16 (App Router), React 19, TypeScript
- Database and auth: Supabase (
@supabase/ssr) with row-level security on every table and private document storage - AI: Vercel AI SDK with
@ai-sdk/mistral, modelmistral-medium-2508 - Documents:
pdf-parsefor text with a Mistral OCR fallback,pdf-libto stamp the signature,pdfjs-distto render the page for placing it - Google:
googleapisfor the Gmail API (intake) and the Drive API (archive), each with its own scoped credentials - UI: shadcn/ui (
base-nova) + Tailwind CSS 4, always in Spanish through a label map - Tests: Vitest over the signing, rollback, activity and prompt logic
Layered Architecture
Request Flow
Key Features
Features at a Glance
Automatic Gmail intake
Messages are pulled straight from the Vice Chancellor's mailbox — no sender filter, so nothing has to be forwarded by hand — and any PDF or Word attachment is picked up. Word files are converted, and scanned documents fall back to Mistral OCR.
Executive summary, topic and suggested action
For each request the AI writes an executive summary, classifies the topic into one of six real categories (resolucion, viaticos_comisiones, presupuesto, permiso, personal, otro) and suggests an action: approve, report or forward. It abstains to otro when unsure — a wrong confident label costs more than an honest "unclassified".
Sign-off checklist
A heuristic detects which sign-offs a document carries (Legal, Administrative Directorate, Budget), reading both the email and the "Vo.Bo." stamps inside the PDF text. A missing sign-off is surfaced as an observation, never as a hard block — the secretary decides.
Role-based inbox
One inbox, two views. The Vice Chancellor decides (approve, report, forward or deny with a mandatory reason); the secretary reviews, adjusts sign-offs and observations, assigns, signs, dispatches and can reopen an already-processed request to correct it — keeping the Vice Chancellor's note.
Placeable signature
The secretary loads a signature PNG, drags and resizes it over the rendered PDF page (signature-placer.tsx), and it is stamped server-side with pdf-lib. The signed file is archived to Drive, the original is deleted from the bucket, and the document is emailed to the requester detected from the CC.
Technical Highlights
The classifier was measured, not assumed
The action prompt was rewritten from the secretary's real corrections and evaluated against a labelled corpus: accuracy went from about 82% to about 88%, with no regression on aprobar. remitir remains the hard class — deciding which department a document should go to depends on routing knowledge the text simply doesn't contain.
The signature is never stored
It is loaded in the browser, kept in memory, and sent only at the moment of signing. There is no signature file on the server and no signature column in the database — nothing to leak if either is ever compromised.
Access is closed by construction
Login is Google OAuth restricted to the university domain, enforced both in the request to the provider and re-validated server-side afterwards — a client-side-only domain check is not a check. Signing in is not enough either: an account has to be provisioned in advance, and without a profile there is no access at all. Row-level security is on for every table, and every server action verifies the caller's role before performing a privileged operation.
An email corpus that keeps improving the model
A labelled corpus pairs the classifier's own output with the correction a human actually made. That is what makes it possible to measure a prompt change instead of guessing at it — and what turned "the new prompt feels better" into 82% → 88%.
English code, Spanish domain
Tables, columns and identifiers are in English; enum values are the domain's own Spanish terms (pendiente, por_firmar, firmado, informado, remitido, resuelto, negado), and the UI is always Spanish through src/lib/labels.ts. Nobody hardcodes a label, and nobody has to translate a status in their head.
Project Structure
src/ ├── app/ │ ├── actions/requests/ # signing, decisions, annexes, queries, metadata │ ├── api/ # scheduled intake, Google authorization, document delivery │ ├── bandeja/ # the inbox: list, dialogs, signature placer │ └── manual/ # in-app manual per role ├── lib/ # activity log, read state, labels, doc naming ├── utils/ │ ├── ai.ts, ai-prompts.ts # runMistral + pure prompts/parsers │ ├── classify.ts, summary.ts, date-audit.ts │ └── gmail-ingest.ts # intake + AI enrichment └── types/database.ts # generated Supabase types
Impact and Scalability
- Intake, review, drafting, signature and delivery live in one flow instead of scattered mailboxes and shared folders.
request_eventskeeps an audit trail of who did what and when — including denials, whose reason stays internal.- Classification is at ~88% accuracy and measurable against a labelled corpus, so the next prompt change can be proven rather than argued.
- Known pending work: resolution numbering, the delivery email template, right-of-petition deadlines, the definitive institutional Drive, and Adobe-certified signing (deferred).
Notes
Built with Next.js 16, Supabase, the Vercel AI SDK over Mistral, the Gmail and Drive APIs, pdf-lib and shadcn/ui. The repository is private — it belongs to the Centro de Prototipado at the National University of Colombia.