Intelligent Legal Correspondence

Universidad Nacional de Colombia · 2026 - At present

Next.jsAI SDKGmail APISupabase

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, model mistral-medium-2508
  • Documents: pdf-parse for text with a Mistral OCR fallback, pdf-lib to stamp the signature, pdfjs-dist to render the page for placing it
  • Google: googleapis for 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_events keeps 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.


© 2026 Felipe Giraldo