@visamundi/visamundi-vision
v0.1.0
Published
The read-only Gmail email-reading engine behind Visamundi Vision: scans an inbox for travel bookings (flights, hotels, trains, car rentals) and extracts structured trip data. Bring your own LLM.
Maintainers
Readme
👁️ Visamundi Vision
The email-reading engine behind Visamundi Vision — open-sourced so you can audit exactly what we read.
Read-only. No writing, no deleting, no storage. Bring your own LLM.
Why this repo exists
Visamundi Vision scans your inbox to find flights, hotels, trains and car rentals, then tells you which visas and travel formalities you need. That requires access to your email, and access to your email demands trust.
So we don't ask you to trust us. This repository is the actual code that reads and interprets your emails — the same package our production app installs from npm. You can read every line, run it yourself, and confirm three promises:
- We only ever read. The single Gmail scope requested is
gmail.readonly. There is no code path that sends, replies, labels, archives, or deletes anything. - We don't keep your email. This library extracts a handful of trip fields (dates, destination, booking reference) and returns them to the caller. It writes nothing to disk and phones no home server.
- The AI backend is yours to choose. The trip extractor talks to any OpenAI-compatible model you configure. There is no hidden third party baked in.
What stays closed is everything that has nothing to do with reading your mailbox: visa rules, the dashboard, accounts, billing, our database.
What's in / what's out
| In this package (auditable) | Kept in the private app |
| --- | --- |
| Gmail search + fetch (gmail.readonly) | OAuth login & token storage |
| Candidate triage & heuristic pre-filter | User accounts, database, RLS |
| LLM trip extraction (provider you supply) | Visa & formality rules |
| Confidence scoring, trip stitching | Scan scheduling / orchestration |
| Airport / IATA / city normalization | Web dashboard & emails |
Install
npm install @visamundi/visamundi-vision
# or: pnpm add @visamundi/visamundi-visionQuick start
Give it a Gmail access token, get structured trips back. Token acquisition (OAuth) is your responsibility — this library never sees your client secret.
import { scanEmailsForTrips, GMAIL_SCOPES } from "@visamundi/visamundi-vision"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
// 1. The ONLY scope this library needs:
console.log(GMAIL_SCOPES) // ["https://www.googleapis.com/auth/gmail.readonly"]
// 2. Bring your own OpenAI-compatible model (OpenAI, Mistral, a local model…)
const provider = createOpenAICompatible({
name: "my-llm",
apiKey: process.env.MY_LLM_API_KEY!,
baseURL: "https://api.openai.com/v1",
})
// 3. Scan. Omit `llm` entirely to run the regex-only fallback with no AI at all.
const trips = await scanEmailsForTrips(accessToken, {
lastScanDate: null,
llm: { provider, extractorModel: "gpt-4o-mini" },
})
console.log(trips)
// [{ bookingType: "flight", destination: "Tokyo, Japan", startDate: "2026-09-12", confirmationNumber: "ABC123", ... }]API
scanEmailsForTrips(accessToken, options?)
| Param | Type | Description |
| --- | --- | --- |
| accessToken | string | A Google OAuth access token carrying the gmail.readonly scope. |
| options.lastScanDate | Date \| null | Only look at mail newer than this. null = default window (last 3 months). |
| options.llm | { provider, classifierModel?, extractorModel? } | OpenAI-compatible provider + model ids. Omit to disable AI and use the deterministic regex extractor. |
| options.getProcessedEmailIds | (ids: string[]) => Promise<Set<string>> | Optional dedup hook: return the subset of message IDs you've already handled so they're skipped. Default: process everything. |
Returns Promise<DetectedTripData[]>.
Also exported: GMAIL_SCOPES, stitchTrips, normalizeDestination, computeHeuristicScore, computeFinalScore, the lower-level Gmail helpers, and all pipeline types.
How it works
Gmail (read-only)
│ messages.list ── date-bounded, promotions/social/forums excluded
▼
Candidate metadata (sender · subject · snippet)
│ triage ── LLM (or known-sender shortcut) keeps likely bookings
▼
Heuristic pre-filter ── PNR/IATA/flight-number/date regex scoring
│ messages.get ── full body fetched ONLY for retained candidates
▼
LLM extraction ── your model pulls structured trip fields
│
▼
Score → stitch → normalize destination → DetectedTripData[]Every network call to Gmail is a read (messages.list, messages.get). Grep the source — you will not find messages.send, .modify, .trash, or .delete.
Extracted fields
Per detected trip: booking type (flight / hotel / train / car), destination (normalized to city + IATA), start/end dates, confirmation number, carrier/provider, and an extraction-confidence score. Nothing else is read or retained.
Audit it yourself
- Confirm the scope:
GMAIL_SCOPESinsrc/constants.ts— read-only, full stop. - Confirm no writes: search the repo for
send|modify|trash|deleteagainst the Gmail client. Onlylist/getexist. - Confirm no exfiltration: the only outbound calls are to Google (read) and to the LLM provider you configured.
- Run the demo in
example/against your own account.
Relationship to the product
This package is published from this repo and consumed as a pinned dependency by the closed-source Visamundi Vision app. The version our app runs is the version you see here — verifiable via the lockfile and the npm registry.
License
MIT © Visamundi
