Building a Self-Hosted Bulk Phone Number Verifier with libphonenumber and Regulator Block Checks
A messy phone column is a familiar problem: mixed country codes, extensions, landlines sitting in an SMS list, and numbers that were never allocated in the first place. I wanted a self-hosted desk that cleaned those lists the way a bulk email verifier cleans addresses — and that stayed honest about what it can and cannot prove offline.
Bulk Phone Number Verifier (the README also calls it PhoneVerify) is a Next.js dashboard and backend for that job. It classifies each number with Google libphonenumber, checks regulator number-block data for the United States, the United Kingdom and Germany, and exports cleaned CSV lists. Live "currently active on the network" status is a separate, optional provider. The repository is MIT licensed.
Everything below comes from the public README. I am not adding customer results, conversion rates or claims the repo does not make.
What it verifies — and what it does not
The project classifies each number in three layers:
- Google libphonenumber — E.164 normalize, valid or possible, country, and line type (mobile, landline, VoIP, and the other types the library reports).
- Regulator number-block databases — US (NANPA), UK (Ofcom), and Germany (BNetzA / numbering-plan NDCs).
- A live lookup interface — a pluggable HLR / carrier "currently active" check, which is not used until you add a provider.
Exports can be all rows, structurally valid rows, or SMS candidates (mobile / fixed-or-mobile).
The important distinction is in the README's honest-capability table. Format, E.164, country and line type are available offline. Regulator block allocation for US, UK and German feeds is also available offline, and it means the block is assigned to a carrier — not that the line is live. Currently active on the network needs an HLR query. Until a Live Lookup provider is configured, the UI and exports label results as structurally valid or block allocated, never "live active."
That is the same idea as email verification hygiene: drop garbage before you spend on messages, without pretending an offline tool can see a subscriber's live status.
Who it is for
The README names four audiences: SMS and outreach teams who want to drop invalid and unallocated-block numbers before campaigns; CRM and lead ops teams who need messy phone columns normalized to E.164 and line type; agencies and freelancers who want to offer list cleanup without claiming offline "live active" magic; and developers who want to self-host a Next.js and Prisma pipeline with a clear HLR plug-in point.
Primary markets covered deeply are the United States, the United Kingdom and Germany, with global format validation via libphonenumber everywhere else.
Features
From the repository:
- Bulk CSV / TXT upload, with a column mapper and paste
- Async jobs with progress and classification stats
- A single-number check page
- Filters for structurally valid, SMS candidates, unallocated, invalid and unknown
- CSV exports (all, clean, or SMS candidates)
- A number-block coverage dashboard, plus NANPA / Ofcom / BNetzA sync hooks
- A lookup cache with a configurable TTL
- A
LiveLookupProviderstub, documented as ready for later wiring to Twilio Lookup, CM.com, CheckMobi or SIGTRAN - A demo user seed for local evaluation
An official HTTP HLR API is already integrated as an optional provider: hlr-lookups.com. Without API keys, the app still runs the structural and regulator-block cleanup. No paid API is required for that default path; processing then stays on the machine or server you run.
Tech stack
| Layer | Technology |
|---|---|
| UI | Next.js App Router, React 19, Tailwind CSS 4 |
| Auth | Auth.js (NextAuth v5) credentials |
| DB | Prisma + SQLite (default) or PostgreSQL |
| Queue | In-process / DB poller, or BullMQ + Redis |
| Validation | google-libphonenumber |
| Blocks | NANPA / Ofcom / BNetzA importers + seed data |
The README badges the app as Next.js 16 and TypeScript 5. Node.js 20 or newer and npm 10 or newer are the documented prerequisites. Optional infrastructure is in docker-compose.yml (Postgres and Redis).
Quick start
git clone https://github.com/mafzalkalwardev/bulk-phone-number-verifier.git
cd bulk-phone-number-verifier
cp .env.example .env
npm install
npx prisma migrate dev
npm run seed
npm run dev
Open http://localhost:3000. The README documents a local demo login of demo@phoneverify.local / demo1234. That account is for local evaluation after npm run seed; it is not a hosted service login.
An optional background worker is started with npm run worker.
Environment and regulator sync
Copy .env.example to .env and set at least:
| Variable | Purpose |
|---|---|
DATABASE_URL |
file:./dev.db or a Postgres URL |
AUTH_SECRET |
Auth.js secret |
NEXTAUTH_URL |
for example http://localhost:3000
|
REDIS_URL |
optional — enables BullMQ |
LOOKUP_CACHE_TTL_DAYS |
cache TTL (default 30) |
NANPA_UTILIZED_URL / OFCOM_CSV_URL / BNETZA_CSV_URL
|
optional live regulator feeds |
In the dashboard, open Coverage to run syncs, or POST authenticated JSON to /api/admin/sync with { "source": "nanpa" | "ofcom" | "bnetza" | "seed" | "all", "text": "..." }.
The public sources the README points at are:
Regulator numbering data remains subject to the terms of NANPA, Ofcom, BNetzA and any upstream providers you connect. Users are responsible for lawful use of phone lists and for telecom rules in their jurisdiction.
Optional live HLR
If you want live active or absent status, the integrated provider is hlr-lookups.com. The README's steps are: create an account, copy keys from the provider's API settings, put HLR_API_KEY, HLR_API_SECRET and HLR_BULK_ENABLED=true in .env, then restart npm run dev. The Coverage page should show Live HLR: Configured. Single checks and bulk jobs then return CONNECTED, ABSENT or INVALID_MSISDN, mapped to active, absent or invalid.
Set HLR_BULK_ENABLED=false if you only want live checks on the single-number page, which the README notes saves credits. Leave the keys unset and the app stays on structural and regulator-block cleanup.
FAQ
What does Bulk Phone Number Verifier do?
It cleans bulk phone lists with format validation and regulator block checks, then exports CSV results from a web dashboard. The source is github.com/mafzalkalwardev/bulk-phone-number-verifier.
Who built it?
Muhammad Afzal Kalwar (@mafzalkalwardev). Portfolio: mafzalkalwardev.github.io.
Is this an HLR lookup tool?
It is a bulk structural and regulator-block cleaner with an HLR interface. Live active or dead status needs a configured provider.
Can it tell if a phone number is currently active?
Not without a Live HLR provider. Offline mode answers "structurally valid / block allocated," not live network status.
Can I use it for US, UK and Germany SMS lists?
Yes — those markets have the deepest block coverage in this project, with global format support everywhere else.
Does uploading a list send numbers to a third-party API?
No paid APIs are required for the default path. Processing is self-hosted on your machine or server. Live HLR is optional and only runs when you add provider keys.
What stack does it use?
Next.js, React, TypeScript, Prisma, Auth.js, google-libphonenumber, and optional Redis/BullMQ. SQLite is the local default; Postgres is supported.
Is it free and open source?
Yes — MIT licensed. See LICENSE.
How do I try it locally?
Clone the repo, copy .env.example to .env, run npm install, npx prisma migrate dev, npm run seed and npm run dev, then open http://localhost:3000. A demo user is seeded for local evaluation.
Source: https://github.com/mafzalkalwardev/bulk-phone-number-verifier
Top comments (0)