Getting started
This walks through installing Mailwoman, parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. an address from Node.js, and parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. the same address from the CLI. About five minutes.
Prerequisites
- Node.js ≥24.18.0. Mailwoman's source runs directly under
node(type stripping, no build step for consumers) — earlier versions can't strip types the way the package expects. - ~33 MB download for the English weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values..
@mailwoman/neural-weights-en-usships a 37.6 MB int8 ONNXONNX (Open Neural Network Exchange). An open format for machine learning models that enables interoperability between training frameworks and inference runtimes. Mailwoman ships its trained model as an ONNX file so it can run in Node.js and the browser via onnxruntime. modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' plus the tokenizertokenizerThe component that converts a raw address string into a sequence of numeric token IDs the model can process. Mailwoman's tokenizer is a SentencePiece unigram model trained specifically on postal addresses. and a couple of small lookup files; npm compresses that to a ~33 MB download (~41 MB unpacked). French weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. (@mailwoman/neural-weights-fr-fr) are a separate, similarly-sized package.
Install
npm install mailwoman @mailwoman/neural @mailwoman/neural-weights-en-us
This installs the CLI, the neural runtime, and the English weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values.. Add
@mailwoman/neural-weights-fr-fr for French addresses.
Your first parse (Node.js)
The recommended entry point is createRuntimePipeline from the mailwoman package — it wires up
the full stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. (normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input. → locale gatelocale gateStage 2 of the runtime pipeline: rule-based locale detection from the query shape's script and known-format signals. Returns a LocaleHint with the top candidate and alternatives, surfacing disagreement with an explicit --locale flag. → kind classify → phrase group → tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words. classify)
around whatever classifier and resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. you give it:
import { createRuntimePipeline } from "mailwoman"
import { NeuralAddressClassifier } from "@mailwoman/neural"
const classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
const pipeline = createRuntimePipeline({ classifier })
const result = await pipeline("350 5th Ave, New York, NY 10118")
function printNode(node, depth = 0) {
console.log(`${" ".repeat(depth)}${node.tag}: "${node.value}" (confidence: ${node.confidence.toFixed(2)})`)
for (const child of node.children) printNode(child, depth + 1)
}
for (const root of result.tree.roots) printNode(root)
Output:
region: "NY" (confidence: 0.93)
locality: "New York" (confidence: 0.89)
street: "5th" (confidence: 0.95)
house_number: "350" (confidence: 0.88)
street_suffix: "Ave" (confidence: 0.95)
postcode: "10118" (confidence: 0.92)
result.tree.roots isn't a flat list with one entry per component. It's nested by containment — a
region contains its locality, which contains its street and postcode, and so on — so walk
it with recursion, not a single loop.
The confidence numbers are calibrated probabilities, not vibes: pass the bundled per-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. calibrator and "0.90" means right-about-90%-of-the-time. See Confidence calibration for how to load it.
result also carries result.kind (the query-shape classification, e.g. structured_address),
result.path ("full" or "fast-path"), and result.timing (per-stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). wall-clock in
milliseconds) — see API reference for the full PipelineResult shape.
If you don't need the staged pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. — normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input., locale gatelocale gateStage 2 of the runtime pipeline: rule-based locale detection from the query shape's script and known-format signals. Returns a LocaleHint with the top candidate and alternatives, surfacing disagreement with an explicit --locale flag., phrase grouping, resolution —
NeuralAddressClassifier.parse() on its own runs the neural stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). alone and returns the same
AddressTree shape. See API reference for the signature.
CLI
The same parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. from the command line, once weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. are installed:
mailwoman parse "350 5th Ave, New York, NY 10118"
Output:
{
"region": "NY",
"locality": "New York",
"street": "5th",
"house_number": "350",
"street_suffix": "Ave",
"postcode": "10118"
}
This is the flattened projection (--format json, the default) — one key per component tagcomponent tagOne of the 33 labels in Mailwoman's address schema — street, locality, region, postcode, house_number, unit, po_box, country, venue, intersection, and others. Each parsed span carries exactly one component tag.,
easier to skim than the nested tree. --format tuple emits [tag, value] pairs; --format xml
emits the nested tree as XML. Add --resolve --resolve-db <path-to-wof.sqlite> to look up
components against a Who's On FirstWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. distribution — see
API reference for what that adds and where to get a
distribution.
Caveats
- The resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. you'd reach with
--resolvehere is administrative/postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.-level, not rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few metres — the highest tier of the geocode cascade. Sourced from address-point and situs data.. It returns place centroids (localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.), not delivery-point coordinates. RooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few metres — the highest tier of the geocode cascade. Sourced from address-point and situs data. and interpolated streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-level resolution exist, but through the separate, richermailwoman geocodecommand — see Status for what's covered. - Modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' quality varies by component and localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.. Some tags are strong, some are still developing. Status is the current per-tag picture; don't rely on a specific number quoted here going stale under you.
- Non-Latin scripts still degrade. The tokenizertokenizerThe component that converts a raw address string into a sequence of numeric token IDs the model can process. Mailwoman's tokenizer is a SentencePiece unigram model trained specifically on postal addresses. falls back to byte-level encoding on scripts it has no dedicated subwords for (Cyrillic, CJK, Arabic) — confidence drops and spanspanA contiguous range of characters or tokens in the input string, tagged with an address component type (street, locality, postcode, etc.). Parsed addresses are represented as collections of spans, possibly nested in a tree. boundaries get less reliable. Several Latin-diacriticdiacriticAn accent mark that modifies a letter (é, ñ, ç). Address normalization must fold diacritics for matching without discarding the information a user typed. scripts (French, Czech, Polish, Slovak, Slovenian, the Nordic languages) have dedicated tokenizertokenizerThe component that converts a raw address string into a sequence of numeric token IDs the model can process. Mailwoman's tokenizer is a SentencePiece unigram model trained specifically on postal addresses. coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present.; see Releases & capabilities for which release added which, and which languages Mailwoman handles well for the full picture.
Where to go next
- Status — what ships today, what's behind a flag, what's still experimental
- API reference — full type signatures, configuration, and output shapes
- The knowledge ladder — the staged pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package.'s design principle, stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). by stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone).
- Why a neural parser? — the motivation for a learned parser over hand-written rules