Elixir Postal

CIHex.pmHex DocsLicense

Elixir bindings for libpostal, powered by a Rust NIF via Rustler.

What is libpostal?

libpostal is a C library for parsing and normalizing street addresses around the world. It uses statistical NLP models trained on OpenStreetMap data covering addresses in over 200 countries and territories. Unlike regex-based parsers, libpostal handles the enormous variety of global address formats -- from "123 Main St" to "1-2-3 Shibuya, Tokyo" to "Flat 4, 22 Acacia Avenue, London."

libpostal provides two core operations:

Postal brings these capabilities to Elixir.

Why Postal?

An Elixir libpostal binding already exists in expostal, but it has been unmaintained since 2017. Postal is a modern replacement with several advantages:

Prerequisites

Installing libpostal

macOS (Homebrew):

brew install libpostal

Ubuntu/Debian (build from source):

git clone https://github.com/openvenues/libpostal
cd libpostal
./bootstrap.sh
./configure
make
sudo make install
sudo ldconfig

See the libpostal README for full instructions.

Installation

Add postal to your dependencies in mix.exs:

def deps do
  [
    {:postal, "~> 0.1.0"}
  ]
end

Then run mix deps.get.

Usage

Parsing addresses

Break a free-text address into structured components:

{:ok, result} = Postal.parse_address("123 Main St, New York, NY 10001")
# => {:ok, %{house_number: "123", road: "main st", city: "new york", state: "ny", postcode: "10001"}}

# Bang variant raises on failure
result = Postal.parse_address!("1 Rue de Rivoli, Paris")
# => %{house_number: "1", road: "rue de rivoli", city: "paris"}

Expanding/normalizing addresses

Expand abbreviations and produce canonical forms (useful for deduplication and matching):

{:ok, expansions} = Postal.expand_address("123 Main St NYC")
# => {:ok, ["123 main street new york city", ...]}

# Pass language hints for better accuracy
{:ok, expansions} = Postal.expand_address("Av. Paulista, 1578", languages: ["pt"])
# => {:ok, ["avenida paulista 1578", ...]}

Configuration

libpostal loads ~2GB of data files into memory on first use. By default this happens lazily on the first call to parse_address/1 or expand_address/2.

To avoid first-call latency, you can initialize eagerly at application boot:

# In your Application module
def start(_type, _args) do
  :ok = Postal.setup()

  children = [
    # ...
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

setup/0 is idempotent -- subsequent calls are no-ops.

Building from source

By default, Postal uses precompiled NIF binaries. To compile the Rust NIF from source (e.g. for development or unsupported platforms), you need the Rust toolchain installed and can set:

POSTAL_BUILD=1 mix deps.compile postal

Documentation

Full API documentation is available on HexDocs.

License

MIT -- see LICENSE for details.