Skip to main content

Open source · No API key · Runs in your browser

Still assembling a geocoder from spare parts?

Mailwoman parses, geocodes, and resolves messy records to real places — so you can stop stitching tools together and get back to the problem you set out to solve.

Free to run yourself, forever — AGPL-3.0 open source, with a commercial license if your legal team wants one.

"1600 Pennsylvania Ave NW"38.8977, -77.0365rooftop

How it compares

Good tools sit in every column here — several of them taught us the trade. The difference is what you stand up before the first query, and what each answer carries with it.

MailwomanNominatimPhotonPeliasHosted APIs
Before your first querynpm installPostgreSQL + planet importNominatim import + OpenSearchElasticsearch cluster + importscreate an account
API keynonenonenonenonerequired
Monthly feenonenone (self-host)none (self-host)none (self-host)metered
Runs in the browser, offline
Calibrated confidence per componentheuristicvaries

Already running one of these? Mailwoman ships drop-in Nominatim-, Photon-, and libpostal-compatible APIs, so your client code can stay put. The full capability matrix — including the rows where the other tools win — is in How Mailwoman compares, with switching guides for each.

Neural address parser

ONNX-runtime sequence classifier over a SentencePiece tokenizer. Emits BIO-labeled components (country / region / locality / postcode / street / venue / …). Trained on a corpus stitched from TIGER, NAD, BAN, OpenAddresses + curated rows. Ships per-locale weight bundles as separate npm packages.

WOF-backed resolver

Parsed components are resolved to Who's On First place IDs + WGS-84 coordinates via FTS5 + R*Tree over pre-indexed SQLite shards. Pure node:sqlite, no SpatiaLite, no native build deps. Multi-shard ATTACH routes postcode queries to the postalcode shard automatically.

Pure-TypeScript runtime

Mailwoman runs on Node 22+ and, as the live demo shows, entirely in the browser — the classifier on onnxruntime-web, the resolver on sql.js-httpvfs over a byte-ranged gazetteer. The same pipeline client-side, no API server.

Spatial tools for agents

A poi_query kind and a sealed poi.db resolve category searches ("coffee near Honolulu") the way address queries resolve. @mailwoman/mcp exposes the same parse / geocode / POI-search toolset to any MCP-compatible agent over stdio. On npm since 7.2.1.

Quick start

Library

npm install mailwoman @mailwoman/neural @mailwoman/neural-weights-en-us
import { createRuntimePipeline } from "mailwoman"
import { NeuralAddressClassifier } from "@mailwoman/neural"
import { createWOFResolver, type ResolverBackend } from "@mailwoman/resolver"
import { WOFSqlitePlaceLookup } from "@mailwoman/resolver-wof-sqlite"

const classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
const lookup = new WOFSqlitePlaceLookup({ databasePath: "./wof.sqlite" })
const parse = createRuntimePipeline({ classifier, resolver: createWOFResolver(lookup as unknown as ResolverBackend) })

const result = await parse("1600 Pennsylvania Ave NW, Washington DC")
// → house_number "1600" · street "Pennsylvania Ave NW" · locality "Washington" · region "DC"
// …and with the resolver attached, each resolved node carries coordinates + its source:
// locality "Washington" → lat 38.90, lon -77.04 · src resolver:locality

CLI

# parse + resolve in one shot
MAILWOMAN_WOF_DB=/path/to/wof.db npx mailwoman parse \
--neural --resolve --format xml \
"Springfield, Illinois"
<address raw="Springfield, Illinois">
<region src="resolver:region:85688697" lat="40.27" lon="-89.19">Illinois
<locality src="resolver:locality:85940429"
lat="39.80" lon="-89.65"
place="wof:85940429">Springfield</locality>
</region>
</address>