API reference
Entry points
createRuntimePipeline(opts?)
The recommended entry point. Returns a one-call 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. function that runs all six stagesstageOne 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)..
import { createRuntimePipeline } from "mailwoman"
function createRuntimePipeline(opts?: {
classifier?: AddressClassifier // Stage 3 — typically NeuralAddressClassifier
resolver?: Resolver // Stage 6 — typically createWOFResolver(...)
detectLocale?: LocaleDetector // Stage 2 override (caller-trust stub by default)
classifyKind?: KindClassifier // Stage 2.5 override (rule-based by default)
groupPhrases?: PhraseGrouper // Stage 2.7 override (rule-based by default)
}): (raw: string, runOpts?: PipelineOpts) => Promise<PipelineResult>
NeuralAddressClassifier
The neural 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.' wrapper. Loads 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. 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. + SentencePieceSentencePieceA language-independent subword tokenizer that splits text into pieces using a unigram language model. Mailwoman uses a SentencePiece tokenizer with a 48,000-token vocabulary and byte-fallback, trained on address data rather than general text. 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..
import { NeuralAddressClassifier } from "@mailwoman/neural"
// From npm weights package
const classifier = await NeuralAddressClassifier.loadFromWeights({ locale: "en-US" })
// From explicit file paths
const classifier = await NeuralAddressClassifier.loadFromWeights({
modelPath: "./model.onnx",
tokenizerPath: "./tokenizer.model",
})
// Parse a single address
const tree: AddressTree = await classifier.parse("350 5th Ave, New York, NY 10118")
// Parse with logits exposed (for downstream joint-reconcile)
const { tree, logits, pieces } = await classifier.parseWithLogits("350 5th Ave, New York, NY 10118")
createWOFResolver(backend)
Wraps a place-lookup backend as a Resolver. The backend is any PlaceLookup — on the
server that's a WOFSqlitePlaceLookup over 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. SQLite distribution.
import { createWOFResolver, type ResolverBackend } from "@mailwoman/resolver"
import { WOFSqlitePlaceLookup } from "@mailwoman/resolver-wof-sqlite"
const lookup = new WOFSqlitePlaceLookup({ databasePath: "./wof.sqlite" })
// `PlaceLookup` and `ResolverBackend` are structurally adjacent but not identical, so the
// backend is passed through a cast — the same one every internal call site uses today.
const resolver = createWOFResolver(lookup as unknown as ResolverBackend)
// Resolve a parsed tree
const resolvedTree: AddressTree = await resolver.resolveTree(tree)
// Opt-in resolver enrichments (both off by default → byte-stable):
// hierarchyCompletion — recover the dropped locality of a dual-role place (Berlin, Milano)
// includeAncestors — stamp each resolved node's containment chain onto metadata.ancestors
// See concepts/dual-role-places.md.
const enriched = await resolver.resolveTree(tree, { hierarchyCompletion: true, includeAncestors: true })
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. is the second half of a deliberate division of labor: 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. knowledge also flows into the 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. itself as soft anchoranchor inferenceA technique where structured knowledge (postcode locations, gazetteer place names) is injected into the model as soft input features — not as deterministic overrides. The model still decides the final labels, but the anchor signal biases it toward correct admin tags. featuresfeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them. (postcode anchoranchor inferenceA technique where structured knowledge (postcode locations, gazetteer place names) is injected into the model as soft input features — not as deterministic overrides. The model still decides the final labels, but the anchor signal biases it toward correct admin tags., 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-tag clues) — the lexicon informs 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.', it never overrides it. See What Mailwoman is for the architecture and Closed-vocab fields: model-first for why "informs, never overrides" is essential.
Output types
AddressTree
The result of 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.. A flat list of root nodes with parent-child nesting.
interface AddressTree {
raw: string
roots: AddressNode[]
}
AddressNode
A single parsed component.
interface AddressNode {
tag: ComponentTag // "locality", "street", "house_number", "postcode", etc.
value: string // The raw text span, e.g. "New York"
span: Span // { start: number, end: number } character offsets
confidence: number // 0..1, mean per-token softmax probability
source: "rule" | "neural" // Which classifier produced this node
children: AddressNode[] // Nested components (e.g. street contains street_prefix)
alternatives?: AddressAlternative[] // Resolver candidates when --candidates is set
interpretations?: Interpretation[] // Extra roles this one span plays — a city-state is region AND locality (#413)
}
A node usually plays one role (its tag). A dual-role placedual-role placeA place that is two placetypes at once — Berlin as both a city and a state, Washington DC as city and district. Resolved using the coincident-roles relation plus hierarchy completion. plays several on one 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.: Berlin is both a regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. and a localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy.. The extra roles ride in interpretations (each a { tag, placeId, lat, lon, … }), and the flat projections surface every role — decodeAsJSON emits both region and locality, decodeAsXML lists roles="region locality". See concepts/dual-role-places.md.
AddressAlternative
A 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. candidate for an ambiguous component.
interface AddressAlternative {
placeId: number // WOF ID
name: string // Canonical name
placetype: string // "locality", "region", "country", etc.
lat: number
lon: number
parentId?: number // WOF parent_id for hierarchy traversal
score: number // Match × population ranking score
}
PipelineResult
The full output from createRuntimePipeline.
interface PipelineResult {
input: string
normalized: NormalizedInputLite // NFC-normalized text + offset map
queryShape: QueryShapeLite // Script class, postcode format hits, token classes
locale: LocaleHint // Detected or caller-provided locale
kind: QueryKindResult // postcode_only | structured_address | intersection | ...
phraseProposals: PhraseProposal[] // Stage 2.7 span boundary proposals
tree: AddressTree // Parsed + (optionally) resolved output
timing: Record<string, number> // Per-stage wall-clock in ms
path: "fast-path" | "full" // Whether the pipeline took the fast-path shortcut
}
Pipeline options
interface PipelineOpts {
locale?: string // BCP-47 tag, e.g. "en-US". Default: caller-trust stub
signal?: AbortSignal // Cancel the pipeline between stages
resolveOpts?: {
candidatesPerLookup?: number // How many alternatives to surface (default: 1)
}
jointReconcile?: boolean // Joint decoding. Default: false (argmax) — retired as default in #566
forceJointReconcile?: boolean // DEPRECATED alias of jointReconcile
}
Joint reconcile requires a classifier with parseWithLogits support (the NeuralAddressClassifier has this) and the phrase grouperphrase grouperStage 2.7 of the runtime pipeline: proposes coherent input units (street phrase, locality phrase, postcode, etc.) with structural kind hypotheses. Decouples boundary discovery from type classification so the classifier answers 'what type?' not 'where?'. It runs beam searchbeam searchA decoding search that keeps the top-k partial candidates at each position. Used in Stage-5 reconciliation to explore the (phrase, tag, resolver) space with controlled pruning. over phrase proposals × classifier top-K × 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. candidates with WOFWOF (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. concordanceconcordanceA joint-decode signal that rewards a parse whose spans form a consistent Who's On First parent-child chain and vetoes contradictory ones — a hard veto for conflicts, a log-space bonus for full agreement. scoring. It is opt-in (default false) — argmax is the default decode. Reconcile was retired as the default in #566: graded against the assembled 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. it broke the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. + house_number geocode precondition on 77–84% of clean US addresses while fixing 0%. The code and A/B harnesses remain; set jointReconcile: true to opt back in (forceJointReconcile is the deprecated alias).
CLI
mailwoman parse [options] <address string>
| Flag | Description |
|---|---|
--locale <tag> | BCP-47 localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. (default: en-US) |
--format json|tuple|xml | Output format (default: json) |
--resolve | Run 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. against WOFWOF (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. SQLite |
--resolve-db <path> | Path to WOFWOF (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. SQLite distribution |
--candidates <n> | Surface up to n alternative resolutions per node |
--debug | Emit full PipelineResult as JSON |
--no-neural | Skip neural classifierneural 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.' (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. + queryShape + 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. only) |
--benchmark <n> | Run 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. n times and emit 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). p50/p95/p99 |
--policy <c>=<m> | Per-component policy override (repeatable). e.g. --policy postcode=rule_only |
Policy modes
| Mode | Behavior |
|---|---|
rule_only | Use only the rule classifier for this component |
neural_only | Use only the neural classifierneural 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.' |
both | Run both, solver picks the best |
neural_preferred | Prefer neural, fall back to rule if confidence < threshold |
rule_preferred | Prefer rule, fall back to neural if confidence < threshold |
Browser imports
// Neural classifier (WASM-based onnxruntime-web)
import { loadNeuralClassifierFromURLs } from "@mailwoman/neural-web"
// WOF resolver backend (WASM-based sqlite-wasm)
import { loadSlimWOFDatabase, WOFWasmPlaceLookup } from "@mailwoman/resolver-wof-wasm"
// Map rendering (MapLibre + Protomaps)
import { StyleSpecificationComposer } from "@mailwoman/cartographer/base"
The browser path mirrors the Node API. classifier.parse() and classifier.parseWithLogits() have the same signatures. Build 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. the same way as on the server, swapping the SQLite lookup for the WASM one:
import { createWOFResolver, type ResolverBackend } from "@mailwoman/resolver"
const { db } = await loadSlimWOFDatabase({ source: "/wof-slim.sqlite" })
const lookup = new WOFWasmPlaceLookup({ db })
const resolver = createWOFResolver(lookup as unknown as ResolverBackend)
The demo at /demo is the reference browser integration.
Component tags
The full ComponentTag union (source of truth: core/types/component.ts):
| Tag | Description | Example |
|---|---|---|
country | Sovereign stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. | USA |
region | First-level admin | OR |
locality | CitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., town | Portland |
dependent_locality | Sub-localitydependent localityA sub-locality (neighbourhood or borough) hierarchically inside a larger locality — e.g. Brooklyn within New York City. Provides finer geographic specification below the primary locality. | Brooklyn |
postcode | Postal code | 97215 |
subregion | County-level admin | Multnomah County |
house_number | Building number | 6220 |
street | StreetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. name | Salmon St |
street_prefix | Directional prefix | SE |
street_suffix | StreetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. type | Street |
venue | Named placevenueA named, non-address place — a business, building, park, or stadium. Mailwoman's free-text point-of-interest component, added as a Tier 2 fine label. | Mt Tabor Park |
unit | Apartment/suite | Apt 4B |
intersection_a | First crossing streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. | 5th Ave |
intersection_b | Second crossing streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. | 42nd St |
attention | Care-of line | c/o Jane Doe |
po_box | Post office boxPO boxA numbered mailbox at a post office used as a delivery address instead of a physical street location. Mailwoman tags it as the po_box component; structurally the same family as a subpremise. | PO Box 1234 |
cedex | FR postal routing | CEDEX 08 |
The current neural 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.' (v0.4.0) emits 10 of these: 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., regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., dependent_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., subregion, cedexCEDEX (Courrier d'Entreprise à Distribution Exceptionnelle). A French postal routing for high-volume business mail: a CEDEX code delivers directly from a sorting centre, bypassing the local post office. A common negative-space format Mailwoman must parse., venuevenueA named, non-address place — a business, building, park, or stadium. Mailwoman's free-text point-of-interest component, added as a Tier 2 fine label., streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels., house_number. The remaining tags are rule-classifier-only.
Drop-in server specifications (OpenAPI 3.1)
The three HTTP drop-ins — plus the native /v1/* surface mailwoman serve ships — each expose a machine-readable OpenAPI 3.1 specification: the endpoints, query parametersparameterA 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., response shapes, the schema.org jsonld variants, and (for the drop-ins) the documented divergencesdivergenceA training failure where loss descends through warmup, plateaus low for a while, then climbs catastrophically back to its starting magnitude — the model unlearns everything despite no obvious component failure. from each upstream. Point Swagger UI, Redoc, a client generator, or a contract test at them.
| Drop-in | Endpoints | Specification |
|---|---|---|
@mailwoman/photon | /api, /reverse | photon.json |
@mailwoman/nominatim | /search, /reverse, /lookup, /status | nominatim.json |
@mailwoman/libpostal | /parse, /expand | libpostal.json |
mailwoman serve (native /v1/* surface) | /v1/parse, /v1/geocode, /v1/batch, /v1/resolve, /v1/format, /v1/reload, /health, /metrics | mailwoman.json |
The source of truth is the route table, not a checked-in file. Every surface builds its OpenAPI 3.1 document from the same @hono/zod-openapi route definitions that serve the requests. There are two ways to get the document, both derived from that identical route table, and nothing to keep in sync by hand:
- At runtime, from a running server —
GET /openapi.json. - Ahead of time, from each surface's own CLI —
mailwoman-photon openapi,mailwoman-nominatim openapi,mailwoman-libpostal openapi,mailwoman openapi— which prints the document to stdout (or--out <path>) without booting the real engine or opening a data root. This is what stamps the four JSON files linked above: the docs site'sprebuildscript runs all four emitters intostatic/openapi/before every build, so those copies are a build product, not a file to hand-edit.
To lint a running server's document with Redocly CLI, fetch it to a temp file first (Redocly's lint doesn't read stdin) — or skip the server and lint the CLI's own output directly:
# From a running server:
npx @mailwoman/nominatim serve --port 8081 --candidate-db <path> &
curl -s http://127.0.0.1:8081/openapi.json -o /tmp/nominatim-openapi.json
npx --yes @redocly/cli@latest lint /tmp/nominatim-openapi.json
# photon and libpostal serve the same way — GET /openapi.json — swap the package and port
# Or straight from the CLI (no server, no data root needed):
npx @mailwoman/nominatim openapi --out /tmp/nominatim-openapi.json
npx --yes @redocly/cli@latest lint /tmp/nominatim-openapi.json
Client libraries
mailwoman-client bundles a typed client per language for all four surfaces above — the three HTTP
drop-ins plus the native mailwoman serve API:
| Language | Package | Generator |
|---|---|---|
| Python | mailwoman-client (PyPI) | openapi-python-client |
| Rust | mailwoman-client (crates.io) | progenitor |
Same one-directional discipline as the specs themselves: route tables → emitted OpenAPI documents →
generated clients, nothing hand-maintained downstream of the spec. mailwoman clients generate
(mailwoman/tools/generate-clients.ts) emits all eight documents (each surface, 3.1 + the
progenitor-diet 3.0.3 — openapiv3, which progenitor depends on, only understands 3.0.x), runs
openapi-python-client once per surface to assemble a Python package
(mailwoman_client.{photon,nominatim,libpostal,mailwoman}), vendors the 3.0 documents into a Rust
crate whose four modules each expand progenitor::generate_api! at compile time, then verifies
both actually build: uv build the Python package + import-check the wheel in an ephemeral
environment, cargo check --examples the crate. Nothing generated is committed — regenerate any time
the specs change.
Not yet published. Both packages build and verify cleanly on every mailwoman clients generate
run, but neither has shipped to a registry yet — that's gated behind an operator step (provisioning a
PyPI account and a crates.io account, each with a publish 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.) documented in the repo's
RELEASING.md. Once provisioned, the same generation 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. runs inside a gated CI job: it always
builds the artifacts on a release dispatch, and only pushes to PyPI/crates.io when the operator
explicitly opts in for that run. Until then, build them yourself:
mailwoman clients generate
# clients-build/python/dist/mailwoman_client-<version>-py3-none-any.whl
# clients-build/rust/ (cargo check --examples already passed; cargo package/publish when ready)
Python usage
Forward-geocode against the hosted Photon trial endpoint (https://photon.sister.software, no local
server needed):
from mailwoman_client import PhotonClient
from mailwoman_client.photon.api.geocoding import search
client = PhotonClient.hosted() # or PhotonClient(base_url="http://127.0.0.1:2322") to self-host
result = search.sync(client=client, q="berlin", limit=3)
for feature in result.features:
lon, lat = feature.geometry.coordinates
print(feature.properties.name, lat, lon)
PhotonClient, NominatimClient, LibpostalClient, and MailwomanClient all default to their
surface's local serve port, so they work against a self-hosted server out of the box; pass
base_url= to point elsewhere. Every generated endpoint module also exposes an asyncio coroutine
alongside sync.
Rust usage
use mailwoman_client::photon::types::PhotonResponse;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = mailwoman_client::photon_hosted(); // https://photon.sister.software
let response = client
.search(None, None, None, None, Some(3), None, None, Some("berlin"))
.await?;
if let PhotonResponse::PhotonFeatureCollection(fc) = response.into_inner() {
for feature in &fc.features {
println!("{:?}", feature.properties);
}
}
Ok(())
}
photon_local() / nominatim_local() / libpostal_local() / mailwoman_local() point at each
surface's local serve port; construct a module client directly (mailwoman_client::nominatim::Client::new("http://…"))
for anything else. The transport is reqwest over rustls, so the build host needs a C compiler +
CMake (rustls' default crypto provider, aws-lc-rs, builds a small C library) but not system OpenSSL.
Both languages are AGPL-3.0-only OR LicenseRef-Commercial, matching the rest of the repo. The
mailwoman_client/mailwoman-client naming, module layout, and license carry forward from an earlier
prototype branch (feat/api-clients, superseded and left unmerged) — the difference is this 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.
sources every spec from the emitters above instead of a checked-in openapi.yaml, and adds the native
mailwoman surface as a fourth module.