Your first ten minutes
Mailwoman arrives in two halves, and they become available at different times. 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. works as soon
as npm install finishes, because the 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.' travels with the package. GeocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. does not, because
the reference data it resolves against is far too large to ship on npm. By the end of this page
you'll have run both checks, pulled that reference data, and geocoded a US address and a French one.
About ten minutes, not counting the one-time 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. download.
Prerequisites
- Node.js 24.18.0 or later, and roughly 750 MB of free disk for the install. The full reasoning is in Install and first parse.
- About 1.65 GB more disk and a few minutes on your connection once you reach Geocode it below — the 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. database, pulled once and kept for every geocode after.
mailwoman data pull, the command that 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. step runs, ships in8.7.0. Checknpm view mailwoman versionfirst: if it still prints8.6.0for you, steps 1 through 4 already work, and step 5 is the one still on its way to npm.
1. Install
npm install mailwoman @mailwoman/neural @mailwoman/neural-weights-en-us
2. Ask it what it has
doctor is the diagnostic to run before you debug anything else. It checks seven things and, for each
gap, prints the one command that closes it. Point it at a data root — the rest of this page reuses
the same one.
export MAILWOMAN_DATA_ROOT=/tmp/mw-trial/mailwoman-data
npx mailwoman doctor
On a fresh install with no address data on disk, that reads:
mailwoman doctor
✓ Model weights (en-us): package:@mailwoman/neural-weights-en-us · model.onnx 39.4 MB, tokenizer.model 1.6 MB
✓ Node runtime: node v26.2.0 (engines: >=24.18.0)
✓ ONNX runtime: onnxruntime-node loadable
✗ Data root: /tmp/mw-trial/mailwoman-data ($MAILWOMAN_DATA_ROOT) does not exist (optional)
fix: mkdir -p /tmp/mw-trial/mailwoman-data (or set $MAILWOMAN_DATA_ROOT to an existing dir)
✗ Admin gazetteer: no candidate.db or WOF shard found (probed 5 paths) (optional)
fix: mailwoman data pull candidate
✗ POI layer: /tmp/mw-trial/mailwoman-data/poi/poi.db not found (optional)
fix: mailwoman gazetteer build poi (or: mailwoman data pull poi)
✗ Locale overlay (fr-fr): @mailwoman/neural-weights-fr-fr not installed (optional — only needed for fr-fr parsing) (optional)
fix: npm install @mailwoman/neural-weights-fr-fr
PASS — core checks ok (weights + runtime); parse is ready
Four red crosses and it still exits 0. That is the contract, not a bug: the exit code tracks the
two core checks, 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. and runtime. Everything marked (optional) is a data layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. that 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.
does not need, so a gap there is reported and costed rather than treated as a failure. The paths in
your own output will name $MAILWOMAN_DATA_ROOT, which is wherever you point it.
3. Parse something
The green half is ready now. Follow
Install and first parse for a ten-line script that turns
apt 4b 350 5th ave new york ny 10118 into a labeled tree, or check it from the shell:
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. Components, and no coordinates, is exactly the shape of a parser that has its 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.' and not its 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..
4. Know which half you have
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. works out of the box. Free text in, tagged components out — house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales., streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels., unitunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise.,
localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood 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., and the rest. The 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.' file travels inside
@mailwoman/neural-weights-en-us, so there is nothing further to fetch and nothing to configure.
French is one more package (@mailwoman/neural-weights-fr-fr) and no other change.
GeocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. needs 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. database. Turning New York into a
latitude/longitude means looking it up, and the 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. that answers is a file you download once
and keep. At the 2026-07-07a build the candidate 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., candidate.db, is 1.65 GB. Your doctor
output prints the exact command that fetches it — one line, and it is generated from the build the CLI
you installed knows about, so prefer it over any command copied from a page. Run it next.
Point-of-interest search ("coffee near Honolulu") is a third layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. with its own database, also
optional, also reported by doctor.
5. Geocode it
Run the fix line doctor printed above, in the same shell so $MAILWOMAN_DATA_ROOT is still set:
npx mailwoman data pull candidate
▸ pull https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db (~1652.9 MB) → /tmp/mw-trial/mailwoman-data/wof/candidate.db
[DEBUG] (mailwoman data): HEAD: https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db
[DEBUG] (mailwoman data): 200 (uncached) HEAD: https://public.sister.software/mailwoman/gazetteer/2026-07-07a/candidate.db
export MAILWOMAN_CANDIDATE_DB=/tmp/mw-trial/mailwoman-data/wof/candidate.db
✓ candidate: gazetteer/2026-07-07a/candidate.db: content-length verified (1652.9 MB) → /tmp/mw-trial/mailwoman-data/wof/candidate.db
PASS (1/1 checks)
Took about four minutes on this connection. The two [DEBUG] lines are the download client checking
the remote file's size before it starts; harmless.
Ignore the export line. The file landed at <data-root>/wof/candidate.db, and that path is where
every entry point looks when nothing points it elsewhere, so the download is the whole setup. The
line is a leftover from when the variable was required; setting it changes nothing here. What the
variable is still for is on Runtime flags —
a copy that lives somewhere else, or none to pin the older backend.
Geocode a US address:
npx mailwoman geocode "350 5th Ave, New York, NY 10118"
{
"input": "350 5th Ave, New York, NY 10118",
"lat": 40.694457,
"lon": -73.93045,
"resolution_tier": "admin",
"uncertainty_m": null,
"locality": "New York",
"region": "NY",
"postcode": "10118",
"house_number": "350",
"street": "5th Ave",
"venue": null,
"dependent_locality": null,
"countryCode": "US",
"hierarchy": [
{
"tag": "locality",
"value": "New York",
"name": "New York",
"lat": 40.694457,
"lon": -73.93045,
"placeID": "wof:85977539"
},
{
"tag": "region",
"value": "NY",
"name": "New York",
"lat": 42.921227,
"lon": -75.596537,
"placeID": "wof:85688543"
}
],
"candidates": [
{
"name": "New York",
"tag": "locality",
"lat": 40.694457,
"lon": -73.93045,
"countryCode": "US",
"placeID": "wof:85977539"
}
]
}
resolution_tier: "admin" is what candidate.db alone can answer: it carries localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood in the hierarchy. and regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.
centroids, so the coordinate is New York's, not this specific rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few meters — the highest tier of the geocode cascade. Sourced from address-point and situs data.. 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 precisionprecisionOf the spans the model labeled as a given tag, the fraction it got right. High precision means few false positives. Paired with recall to compute F1.
(resolution_tier: "address_point" or "interpolated") is a further, per-countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. download —
mailwoman data pull us or mailwoman data pull fr — outside what this page covers.
Now a French address, no other change:
npx mailwoman geocode "12 Rue de Rivoli, 75001 Paris"
{
"input": "12 Rue de Rivoli, 75001 Paris",
"lat": 48.856599,
"lon": 2.342841,
"resolution_tier": "admin",
"uncertainty_m": null,
"locality": "Paris",
"region": null,
"postcode": "75001",
"house_number": "12",
"street": "Rue de Rivoli",
"venue": null,
"dependent_locality": null,
"countryCode": "FR",
"hierarchy": [
{
"tag": "locality",
"value": "Paris",
"name": "Paris",
"lat": 48.856599,
"lon": 2.342841,
"placeID": "wof:1159322569"
}
],
"candidates": [
{
"name": "Paris",
"tag": "locality",
"lat": 48.856599,
"lon": 2.342841,
"countryCode": "FR",
"placeID": "wof:1159322569"
},
{
"name": "Les Paris",
"tag": "locality",
"lat": 45.63972,
"lon": 5.73742,
"countryCode": "FR",
"placeID": "wof:1327276209"
}
]
}
Right countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head., right citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood in the hierarchy., and no flag asked for either. That is worth a paragraph, because it is
recent: the file you downloaded is the default backend as of 8.7.0, and before that it was
opt-in behind the variable you were told to ignore above.
You can still run the older path. --candidate-db none pins it, and it needs a full admin database —
a separate, locally-built file data pull does not publish, so this is a comparison you read here
rather than reproduce. Same input, same machine:
{
"lat": 33.668553,
"lon": -95.54435,
"countryCode": "US",
"placeID": "wof:101725293"
}
Paris, Texas. The tempting reading is that the older backend ranks worse, and it is wrong. What
produced Texas is the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. filter, not the ranking: --locale defaults to en-US, en-US
implies --default-country US, and that filter is applied on the older backend and not on this one.
Cross the two levers and the confound comes apart (measured 2026-08-04, this 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., this 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.'):
| backend | countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. filter US | no countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. filter |
|---|---|---|
| candidate.db | Paris Township, TX (32.960, −96.838) | Paris, France |
full admin (none) | Paris, TX (33.669, −95.544) | Paris, France |
Both backends are right when nothing filters them to the United States, and both are wrong when
something does. So the claim this page makes is the narrow one: candidate.db is the default,
and the default does not apply a countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head. filter, which is why a French address geocoded correctly
without you configuring anything. --country-scope locale|none (on parse and geocode) is the
switch that holds the filter fixed if you ever need to compare the two backends yourself.
What you have now
A parser and a geocoder, both running against your own disk. mailwoman parse (or
createRuntimePipeline) gives you tagged components; mailwoman geocode gives you a coordinate, once
candidate.db is on disk at <data-root>/wof/candidate.db. You also have the command that will
answer the "which half do I have" question again after any change to your install.
Next
- What ships today — versions, footprints, the localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. tiers, and what is not supported.
- What Mailwoman is — why the two halves are separate in the first place.