Skip to main content

Your first ten minutes

Mailwoman has two halves, and they become available at different times. The parser 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.' ships inside a package. The geocoder needs reference data that is too large to ship on npm, so it requires a separate download. By the end of this page you will have run both checks, pulled that reference data, and geocoded a US address and a French one. The steps take about ten minutes, plus the time for 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. Install and first parse explains both numbers.
  • About 2.88 GB more disk and a few minutes of download time when you reach Geocode it below. That space holds 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. You pull it once and keep it for every later geocode.
  • mailwoman data pull, the command 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. step runs, ships in 8.7.0. Check npm view mailwoman version first. If it still prints 8.6.0, steps 1 through 4 work, and step 5 needs the next npm release.

1. Install​

npm install mailwoman @mailwoman/neural @mailwoman/neural-weights-en-us

2. Ask it what it has​

Run doctor before you debug anything else. It runs seven checks, and for each missing piece it prints the command that installs it. Point it at a data root. The rest of this page reuses the same data root.

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 extract 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

The output shows four failed checks, and the command still exits 0. This is intended. The exit code reflects only 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 doctor reports a missing 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. and its fix instead of failing. The paths in your own output use your $MAILWOMAN_DATA_ROOT.

3. Parse something​

You can 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. an address 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"
}

The output has components and no coordinates, because the parser 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.' but 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​

The parser works out of the box. It takes free text and returns tagged components: 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 postcode (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 ships inside @mailwoman/neural-weights-en-us, so there is no package to fetch or setting to configure. French support needs one more package (@mailwoman/neural-weights-fr-fr) and no other change.

The geocoder 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. Mailwoman needs a 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. lookup to turn New York into a latitude and longitude. 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. is a file you download once and keep. The transcript below shows the 2026-07-07a build. At the current 2026-08-25b 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 2.88 GB, so your download will be larger. Your doctor output prints the exact one-line command that fetches it. The CLI generates that command from the build it knows about, so prefer it over a command copied from a web 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. It is also optional, and doctor also reports it.

5. Geocode it​

Run the fix line that doctor printed above. Use the same shell so that $MAILWOMAN_DATA_ROOT is still set:

npx mailwoman data pull candidate
▸ pull https://public.mailwoman.ai/mailwoman/gazetteer/2026-07-07a/candidate.db (~1652.9 MB) → /tmp/mw-trial/mailwoman-data/wof/candidate.db
[DEBUG] (mailwoman data): HEAD: https://public.mailwoman.ai/mailwoman/gazetteer/2026-07-07a/candidate.db
[DEBUG] (mailwoman data): 200 (uncached) HEAD: https://public.mailwoman.ai/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)

The download took about four minutes on the connection used for this page. The two [DEBUG] lines show the download client checking the remote file's size before it starts. They do not indicate a problem.

Ignore the export line. The file is saved at <data-root>/db/wof/candidate.db, which is the default path every entry point checks, so the download completes the setup. The CLI still prints the line from the release when the variable was required. This variable has no effect in this setup. Runtime flags describes what the variable is still used for: pointing at a copy stored elsewhere, 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",
"epistemic_status": "observed",
"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"
}
]
}

Without a 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. 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., candidate.db answers at resolution_tier: "admin" or, where a postcodepostcodeThe country-specific postcode (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. locates less than its citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood in the hierarchy., as UK and Dutch unitunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise. postcodespostcodeThe country-specific postcode (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. do, at "postcode". It stores localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood in the hierarchy., postcodepostcodeThe country-specific postcode (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. 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 centroid instead of this building's 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") needs 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), which this page does not cover.

Now geocode a French address without changing anything else:

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",
"epistemic_status": "observed",
"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"
}
]
}

The result has the 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. and the right citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighborhood in the hierarchy., and no flag was needed. This behavior is recent. The file you downloaded became the default backend in 8.7.0. Before that release, it was opt-in behind the variable you were told to ignore above.

You can still run the older backend. --candidate-db none pins it. It needs a full admin database. That database is a separate, locally built file that data pull does not publish. You can read the comparison below, but you cannot reproduce it with the downloads on this page. Same input, same machine:

{
"lat": 33.668553,
"lon": -95.54435,
"countryCode": "US",
"placeID": "wof:101725293"
}

The older backend returns Paris, Texas. The ranking does not cause this. 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 does. --locale defaults to en-US, en-US implies --default-country US, and the older backend applies that filter while the candidate backend omits it. Test each backend with and without the filter separates the two effects (measured 2026-08-04, with 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. and 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.'):

backendcountrycountryThe 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 USno 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.dbParis Township, TX (32.960, −96.838)Paris, France
full admin (none)Paris, TX (33.669, −95.544)Paris, France

Both backends return Paris, France without the US filter, and both return a Texas result with it. This page therefore makes a narrow claim. The default candidate.db applies 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, so the French address geocoded correctly without any configuration. To compare the two backends yourself, use --country-scope locale|none (on parse and geocode) to set the filter the same way for both.

What you have now​

You have a parser and a geocoder, both reading from your own disk. mailwoman parse (or createRuntimePipeline) returns tagged components. mailwoman geocode returns a coordinate once candidate.db is on disk at <data-root>/db/wof/candidate.db. After any change to your install, mailwoman doctor shows again which halves are available.

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.