A self-hosted parcel tracker for people and small online shops. Send it a tracking number on Telegram and it follows the parcel with the carrier, tells you when something changes, and draws the route on a map. Turn on the web dashboard and it becomes a shipment control room: every parcel you sent, the ones that need you, and a tracking page for each customer.
- Any carrier. The carrier is detected from the code; 17track covers 2,000+ carriers, with direct sources for DHL and 20 national posts and couriers.
- Smart alerts. Status changes with a route map, a warning when a parcel stops moving, and failed deliveries that are never mistaken for deliveries.
- For sellers. Order numbers, customers, tags and notes; CSV import and export; seller mode; delivery-time statistics per carrier.
- Customer tracking pages under your shop name, showing carrier data and nothing of yours.
- Web dashboard and JSON API, signed in from Telegram with no passwords.
- Private by design. One Docker container, one SQLite file, no third-party analytics, offline geocoding, English and Italian.
You need Docker with Compose, a bot token from @BotFather and your numeric Telegram user ID.
git clone https://github.com/bernalli/parcel-tracker-bot.git
cd parcel-tracker-bot
cp .env.example .env
# Edit .env: TELEGRAM_BOT_TOKEN, OWNER_ID and (recommended) TRACK17_API_KEY.
docker compose up -dSend /start to your bot, then paste a tracking number. Don't know your ID?
Start the bot anyway and send /whoami.
To open the web dashboard, add these to .env, run docker compose up -d
again, and send /web to the bot:
WEB_ENABLED=true
WEB_PUBLIC_URL=http://localhost:8080Releases are also published as signed images on
ghcr.io/bernalli/parcel-tracker-bot (latest or a version such as 0.4.0).
For a public address, HTTPS and backups, read operations.
Everything is reachable from /menu with buttons. You can also just type:
| You send | The bot |
|---|---|
| A tracking number, also with spaces, after a label or inside a carrier link | Starts tracking it and asks for a name |
| Several codes, one per line, as a list or separated by commas | Adds them all |
A .csv file |
Imports the shipments it contains |
/list |
Shows your active parcels |
/web |
Sends a one-time sign-in link for the dashboard |
/export |
Sends all your shipments as CSV |
/forgetme |
Deletes everything stored about you |
/help |
Lists everything else |
Each parcel card has Update now, Events, Map, Rename, Details (order, customer, destination, tags, notes), Share (customer tracking link) and Remove. Notifications can be muted per status in Settings → Notifications.
| Intercontinental route | Out for delivery |
|---|---|
Status updates come with a map of the route, rendered by the bot itself from OpenStreetMap data.
Turn on Seller mode in the settings: delivered parcels are archived automatically with a short notice, instead of the "did you receive it?" question meant for buyers. Then:
- add shipments by pasting codes, uploading your shop's CSV export, or from your shop through the JSON API;
- check Need attention for carrier problems and parcels with no news for a week, and get a Telegram warning when one stalls;
- send customers a tracking link with your shop name on it;
- compare carriers by delivery time and success rate.
The full workflow is in the sellers guide.
The tracking page your customer sees: your shop name, the carrier's data, nothing else.
| Source | Setup | Covers |
|---|---|---|
| 17track API (recommended) | TRACK17_API_KEY |
2,000+ carriers worldwide, and the fallback for every parcel |
| DHL official API | DHL_API_KEY |
DHL Express, DHL Paket / Deutsche Post, DHL eCommerce |
| Built-in web scrapers | none | UPS, USPS, FedEx, Royal Mail, La Poste, Deutsche Post, DPD, GLS, Correos, Correios, Canada Post, Australia Post, PostNL, bpost, Swiss Post, Österreichische Post, Aramex, Evri, Yodel, DHL |
| Detection only | via 17track | Amazon Logistics, China Post, EMS, Japan Post, Singapore Post |
The scrapers are best effort: carriers change their sites and some only render
tracking with JavaScript. When a scraper cannot read a parcel, 17track takes
over. International postal codes (RR…IT, CP…DE, LX…CN) are validated
and show their postal operator straight away. Missing a courier? Write a
plugin in about 50 lines. Details and patterns are in
trackers and api-keys.
.env.example lists every setting with its default. The essentials:
| Variable | Default | Purpose |
|---|---|---|
TELEGRAM_BOT_TOKEN |
required | Bot token from @BotFather |
OWNER_ID |
required | Your Telegram ID; always an admin |
TRACK17_API_KEY |
empty | Universal tracking source and fallback |
MAX_ACTIVE_SHIPMENTS |
20 |
Per user; 0 for no limit |
STALL_ALERT_DAYS |
7 |
Warn about parcels with no news; 0 turns it off |
WEB_ENABLED / WEB_PUBLIC_URL |
false / http://localhost:8080 |
Web dashboard and the base of the links the bot sends |
DEFAULT_LANGUAGE |
en |
en or it; each user can switch |
DATA_RETENTION_DAYS |
180 |
Delete archived parcels after N days |
- Only tracking numbers leave your server, to the carrier or 17track. Names, customers, addresses and notes stay in your SQLite file.
- Geocoding is offline (GeoNames); map tiles are fetched by the server, never by a visitor's browser.
- The container runs as a non-root user with a read-only filesystem, no capabilities and resource limits.
- The dashboard uses single-use sign-in links, hashed sessions and tokens, CSRF protection and a strict Content-Security-Policy.
/forgetmeerases a user; archived parcels expire afterDATA_RETENTION_DAYS.
Found a vulnerability? Please report it privately: see SECURITY.
- Sellers guide — running a shop's shipments with the bot
- Web dashboard and JSON API
- Operations — deployment, reverse proxy, backups, upgrades
- Tracking sources and API keys · Carriers
- Writing a plugin
- Architecture · Observability
- Translations · Troubleshooting
- Changelog
Bug reports, couriers, translations and documentation fixes are welcome. Read
CONTRIBUTING for the development setup, and try the
dashboard on demo data with python scripts/demo_web.py. This project follows
the code of conduct.
MIT — see LICENSE. Copyright © 2026 Samuele Martinalli and contributors. Map data © OpenStreetMap contributors, © CARTO; place names from GeoNames (CC BY 4.0), see NOTICE.