Free, open-source shipment tracking for Indian and international couriers. Built with Next.js + TypeScript.
Currently supports:
- Blue Dart (India)
More carriers coming — contributions welcome.
Most courier tracking lives behind paid SaaS aggregators. ShipTrack is a tiny, self-hostable alternative: bring your own carrier credentials and run it on Vercel, Cloudflare, or your own box.
git clone https://github.com/aswin/shiptrack
cd shiptrack
npm install
cp .env.example .env.local
# fill in carrier credentials
npm run devOpen http://localhost:3000.
GET /api/carriers
GET /api/track/{carrier}/{trackingNumber}
Example:
curl http://localhost:3000/api/track/bluedart/1234567890Response shape: see src/carriers/types.ts.
No credentials required. The carrier scrapes the public tracking page at
https://www.bluedart.com/trackdartresultthirdparty, which renders all
shipment data server-side.
Blue Dart's commercial tracking API is gated behind customer credentials
issued by a Blue Dart account manager; a DHL Developer Portal app on its
own is not sufficient. If you have such credentials and want to switch to
the official API, the prior version of src/carriers/bluedart.ts in git
history shows the request shape.
Polling every 15–30 minutes per shipment is plenty.
- Create
src/carriers/<name>.tsexporting aCarrier(seetypes.ts). - Register it in
src/carriers/registry.ts. - Document any required env vars in
.env.exampleand this README.
That's it — the API route and UI pick it up automatically.
The site is read-only for visitors — anyone can paste a tracking number and see the current status. Scheduled alerts are gated behind an ADMIN_TOKEN, so only the operator can register a watch. Public signup + per-user accounts will come later.
When a watch is active, a scheduled worker polls every 15 minutes, diffs the latest event, and emails the configured address via Resend on any change. Every alert email has a one-click unsubscribe link.
Notifier registry (src/notifiers/) is pluggable — email (Resend) is implemented; webhook / sms / slack / telegram are registered stubs for later.
# Add
curl -X POST https://your-app.workers.dev/api/watches \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"carrier": "bluedart",
"trackingNumber": "1234567890",
"label": "Mom’s package"
}'
# Cancel — open the unsubscribe link from any alert email, OR:
wrangler d1 execute shiptrack --remote \
--command "UPDATE watches SET status='cancelled' WHERE id='<watch-id>'"# 1. Create the D1 database
npx wrangler d1 create shiptrack
# Copy the `database_id` into BOTH wrangler.jsonc and wrangler.poller.jsonc
# 2. Apply schema (local + remote)
npm run db:migrate
npm run db:migrate:remote
# 3. Set secrets on the web worker
for k in ADMIN_TOKEN TOKEN_SECRET RESEND_API_KEY RESEND_FROM APP_URL; do
npx wrangler secret put "$k"
done
# 4. Set the relevant secrets on the poller (no ADMIN_TOKEN needed there)
for k in TOKEN_SECRET RESEND_API_KEY RESEND_FROM APP_URL; do
npx wrangler secret put "$k" -c wrangler.poller.jsonc
done
# 5. Deploy both
npm run deployADMIN_TOKEN and TOKEN_SECRET should each be a random 32-byte hex string (openssl rand -hex 32). RESEND_FROM must be a verified sender in your Resend account.
This repo is configured for @opennextjs/cloudflare.
npm install
npx wrangler login
npm run deployLocal Workers preview (runs the actual worker bundle):
npm run previewBoth workers are also built by Cloudflare Workers Builds on every push. Each worker has two build configurations, and the deploy is the trigger's job, not the build command's:
| branch | build command | deploy command |
|---|---|---|
main |
npm run deploy:web |
npx wrangler deploy |
| any other | npm run deploy:web |
npx wrangler versions upload (preview only) |
deploy:web therefore only builds — despite the name, which is fixed by the
existing trigger config. Do not make it deploy: it runs on every branch, so it
would ship unreviewed branch code straight to production. npm run deploy is
the manual full deploy (web + poller).
MIT — see LICENSE.