Where would you like to start?
Build with it
Three packages and ten lines
Run npm install, load the English model, and parse your first address. Parsing works the moment the install finishes — the model travels with the package, so there is nothing to configure and no key to obtain.
"apt 4b 350 5th ave new york ny 10118" → unit·house·street·city·postcodeWhat it costs
Free, or a flat license
AGPL-3.0 and free to run in production. A commercial license releases you from the source-sharing condition for a flat $250 a month per company. There are no seats to count and no per-address fees, because the software runs on your machines and reports nothing back.
2M addresses/month · $250 · 20M addresses/month · $250See it work
The whole engine, in a browser tab
The demo is the product, not a mock of it: the same model and the same resolver, downloaded once and run client-side. Type an address and watch it get labeled and placed. Nothing you type is transmitted anywhere.
type an address → components, coordinate, and the source it came fromMake the case for it
A flat number, not a meter
Metered geocoding prices every lookup, so the bill moves with your address volume — the input your team controls least. A flat license fixes the number finance budgets against, whatever the volume does this quarter.
metered: $/request · flat: $250/mo, any volumeSee the proof
Every number ships with its command
Published panels against the French national address register and a Belgian sample, plus the reading guide for the ways a geocoding benchmark reads better than it is. Script, inputs and result file, published beside every score.
resolve rate ≠ accuracy · panel ≠ populationWorked examples, on real public data
Coverage reconciliation
The provider registry meets the Universal Service Fund
Three public datasets — a national provider registry, an FCC funding file, a state licensing list — that share no identifier. Resolved onto one map by matching the geocoded place, not the key none of them carry. Every dot is a real entity that turned up in more than one of them.
Data provenance
We keep the receipt on every coordinate
Every point Mailwoman resolves to remembers which open dataset it came from. Here's New York: the federal National Address Database statewide, OpenAddresses (the city's own data) in New York City. Most geocoders sand that provenance off. We keep it on the point.
Neural address parser
A 6-layer encoder over a 73,143-piece SentencePiece vocabulary, quantized to int8 and executed on ONNX Runtime. Emits 33 BIO labels over 16 component tags — country, region, locality, postcode, street, house number, unit, venue and the rest — each with a confidence score.
Gazetteer-backed resolver
Parsed components resolve to Who's On First place IDs and WGS-84 coordinates over pre-indexed SQLite. It uses only node:sqlite, without SpatiaLite or a native build step. The gazetteer is a 1.65 GB download you keep, not a service you call.
Node and the browser
Node 24.18 or later, and the same pipeline in a browser tab: the classifier on onnxruntime-web, the resolver on WASM SQLite over a byte-ranged gazetteer. Earth is that build, not a hosted API behind a text box.
Drop-in and agent surfaces
Nominatim-, Photon- and libpostal-compatible servers answer on the shapes your client code already speaks, so a migration can be a hostname change. @mailwoman/mcp exposes parse, geocode and POI search to any MCP-compatible agent over stdio.
Quick start
Library
npm install mailwoman @mailwoman/neural @mailwoman/neural-weights-en-us
import { createRuntimePipeline } from "mailwoman"
import { NeuralAddressClassifier } from "@mailwoman/neural"
const classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
const parse = createRuntimePipeline({ classifier })
const result = await parse("apt 4b 350 5th ave new york ny 10118")
// result.tree.roots — nested by geographic containment:
// region "NY" › locality "New York" › street "5TH"
// › unit "Apt 4B" · house_number "350" · street_suffix "Ave"
// …and postcode "10118" under the locality
CLI
npx mailwoman parse "350 5th Ave, New York, NY 10118"
{
"region": "NY",
"locality": "New York",
"street": "5th",
"house_number": "350",
"street_suffix": "Ave",
"postcode": "10118"
}
No coordinates in that output, because geocoding needs the gazetteer. Your first ten minutes covers what you get out of the box and what still needs a download.

