Skip to content
Public template

About

Privacy-first Telegram bot that tracks parcels from 3,000+ carriers via 17TRACK. Add by number or label photo (QR/barcode), get notified on every status change. Encrypted, no logs, self-hostable.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Tracking, @trackshipmentbot

Tracking

A privacy-first Telegram bot that tracks parcels from 3,000+ carriers and tells you the moment they move.

Telegram Python 3.10+ Encrypted No logs 11 languages MIT License

Features · How it works · Privacy · Self-hosting · Configuration · Architecture · Roadmap


Send Tracking a tracking number or just a photo of the label. The bot figures out the carrier, watches the parcel around the clock and messages you when something changes. Your data is encrypted at rest, nothing is logged, and delivered parcels delete themselves.

The code is fully open: read it, check what it stores, or run your own instance in a few minutes.

Features

Tracking

  • 3,000+ carriers worldwide through the 17TRACK API, with the carrier detected automatically
  • Manual carrier override for the rare cases where auto-detection picks the wrong one
  • Status checks every 15 minutes (configurable), plus on-demand refresh
  • Delivered and expired parcels are removed automatically after one final notification. So are parcels that get no tracking data for 30 days, and parcels whose tracking hasn't changed for 90 days (stuck in transit, for example).

Adding a parcel

  • Type the tracking number. Spaces as printed on labels (JJD 0003 9000) are handled.
  • Or send a photo of the QR code or barcode. It's decoded on the server, including tracking links inside QR codes. If a photo has several codes, you pick the right one.
  • Give the parcel a name, or skip
  • Choose how you want updates, per parcel: on every status change, or a status summary every N hours (1 to 168)
  • Clean chat: all the questions and answers from the add flow are deleted when you're done. Only the confirmation stays.

Everyday use

  • Persistent keyboard menu: Add package, My packages, Settings, Help
  • Per-parcel screen with scan history, refresh, rename, notification settings, share, carrier and delete
  • Share a parcel with a link. Whoever opens it gets the parcel on their own list, with their own notifications. The link works for anyone who has it until it expires, so a forwarded link works too.
  • Silent at night: notifications between 22:00 and 07:00 arrive without sound, in each user's own time zone. Users pick a city in Settings, so daylight saving time is handled automatically.
  • 11 languages: English (default), Deutsch, Español, Français, Polski, Português, Türkçe, Русский, Українська, 中文, 日本語
  • Every message carries the bot's banner

How it works

sequenceDiagram
    autonumber
    actor U as User
    participant B as Tracking bot
    participant D as Encrypted DB
    participant T as 17TRACK API

    U->>B: Tracking number or label photo
    B->>B: Decode QR / barcode (in memory)
    B-->>U: Name? Notify how? Every how many hours?
    B->>T: register(number)
    B->>D: store (AES-256-GCM)
    B-->>U: Package added (flow messages deleted)

    loop every 15 minutes
        B->>D: load parcels
        B->>T: gettrackinfo(numbers, batches of 40)
        T-->>B: latest status and events
        alt new event, or N hours passed
            B-->>U: Status update
        end
        alt delivered or expired
            B-->>U: Final update
            B->>D: delete parcel
            B->>T: deletetrack (if nobody else tracks it)
        end
    end
Loading

Privacy and security

Privacy isn't a feature added on top. The storage layer is built around it.

What is stored, and how

Data Stored as
Telegram chat ID HMAC-SHA256 tag (lookup only). The real ID lives only inside the encrypted blob.
Tracking number HMAC-SHA256 tag (lookup and de-duplication), plus inside the encrypted blob
Parcel name, carrier, status, scan history, timestamps, notification settings AES-256-GCM encrypted blob, authenticated and bound to its owner
Settings: language, silent at night, time zone, account start date (for the new-account limit) AES-256-GCM encrypted blob
Share links Only an HMAC of the token is stored. The payload is encrypted, expires after 7 days and is deleted together with its package. The link to the package is an HMAC too, so the database doesn't show who shared what.
Label photos Never stored. Decoded in memory and discarded.
Logs None. Logging and warnings are disabled in code. The only possible output is a configuration error at startup or, if the bot crashes, a single line with the error type (for example fatal: ValueError). Neither contains user data or tracebacks.

The encryption key (DATA_KEY) is kept outside the database. Deleted rows are overwritten inside the database file (PRAGMA secure_delete). That doesn't cover copies the operating system or an SSD may keep on its own, so run the bot on an encrypted disk if that matters to you. Database files are created readable only by the bot's user.

Threat model

Scenario Protected?
Someone gets a copy of the database file or a backup Yes. They see opaque hashes and ciphertext. What still shows: how many users and parcels there are, how many parcels each anonymous user has, whether two users track the same number, how many share links exist per parcel and when they expire, and how many parcels each anonymous user added today (the daily counters, which can't be linked to parcels and are deleted the next day). Parcel IDs are random, so the order in which parcels were added isn't stored.
A curious operator browses the database Yes. Same as above. There's no admin panel and no "view users" feature.
Full compromise of the running server (database and .env) No. The bot needs the key to poll parcels unattended, so an attacker with the key can decrypt.
17TRACK Partly. Receives tracking numbers, and nothing else, in order to track them.
Telegram Partly. Chats with bots are not end-to-end encrypted, so Telegram's servers carry the messages.

That last part is what any bot that sends notifications in the background has to deal with. If you don't want to trust an operator, host your own instance. That's what this repository is for.

Abuse protection

  • Label photos are limited to 10 MB and 24 megapixels, downscaled before decoding and processed off the main loop, two at a time, with a short cooldown per user.
  • New packages are limited per user and per day for the whole bot (DAILY_ADDS_PER_USER, DAILY_ADDS_TOTAL), so the shared 17TRACK quota can't be burned in minutes.
  • Accounts younger than 3 days can add only 3 packages a day, and all new accounts together can use at most half of the daily total. A handful of fresh accounts can't lock everyone else out.
  • Each chat can send at most 8 messages per 10 seconds. Anything above that is ignored, so one spammer can't slow the bot down for everyone.
  • Manual refreshes and carrier changes share a per-user cooldown. A carrier can't be changed for a number that someone else tracks too, because the change applies to everyone.
  • Up to 3 active share links per parcel. Creating a new one replaces the oldest.
  • The per-user daily counter is stored under its own anonymous hash, so it survives /deletedata for the rest of the day but can't be linked to anyone's parcels.

User controls

  • Delete any parcel instantly. When no one tracks a number anymore, it's also removed from 17TRACK.
  • /deletedata or Settings > Delete my data erases everything about you immediately
  • Blocking the bot erases your data immediately. If the bot happens to be offline for more than a day at that moment, Telegram drops the event. Your parcels are then erased at the next attempt to message you. If you had no parcels, your encrypted settings stay until you use /deletedata.
  • The bot only works in private chats. If someone adds it to a group, it leaves.

Self-hosting

Requirements

  • Python 3.10+ (developed on 3.14)
  • A Telegram bot token from @BotFather
  • A 17TRACK API key from api.17track.net (Settings > Security > Access Key). The free plan includes 200 tracking numbers.

Install

git clone https://github.com/Soyboy/tracking-telegram-bot.git ~/tracking-telegram-bot
cd ~/tracking-telegram-bot
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
chmod 600 .env

Generate an encryption key and put it in .env together with your tokens:

python3 -c "import secrets,base64;print(base64.b64encode(secrets.token_bytes(32)).decode())"
DATA_KEY=<generated key>
TELEGRAM_TOKEN=123456:ABC...
TRACK17_KEY=<your 17track key>

Important

Back up DATA_KEY separately from the database. Without it the stored data can't be recovered. If it sits next to the database backup, the encryption protects nothing.

Run

.venv/bin/python bot.py

On startup the bot sets its own name, command list and English description in Telegram (turn this off with SET_PROFILE=0). To finish the profile, send logo.png to @BotFather with /setuserpic.

On first start the bot stores an encrypted check value. If it's later started with a different DATA_KEY, it refuses to start instead of silently showing everyone an empty list.

Diagnostics

The bot is silent by design and keeps no logs. If it starts and answers /start, it works.

If something seems off (no notifications, for example), start it with DIAG=1. It then prints one line per problem to stderr, with the error type and the 17TRACK endpoint only, for example 17track gettrackinfo: HTTP 401 when the API key is wrong. It never prints tracking numbers, names, chat IDs or tokens. Turn it off again when you're done.

Note

Don't re-enable Python logging (the logging.disable(...) line in bot.py) instead.

Warning

With logging enabled, httpx logs every request URL at INFO level, and Telegram request URLs contain your bot token. Never enable logging in production or share such logs.

Make it your own

The bundled banner.png shows @trackshipmentbot. If you run your own bot, replace banner.png and logo.png with your own images. To use a different name, change BOT_NAME in bot.py and the word "Tracking" in locales/*.json.

Run as a service (systemd)

For a server, run the bot as a system service under its own user. The code then belongs to root and can't be changed by the bot, and the only place it can write to is /var/lib/tracking-bot.

sudo useradd --system --no-create-home --shell /usr/sbin/nologin tracking
sudo git clone https://github.com/Soyboy/tracking-telegram-bot.git /opt/tracking-telegram-bot
cd /opt/tracking-telegram-bot
sudo python3 -m venv .venv
sudo .venv/bin/pip install -r requirements.txt
sudo cp .env.example .env              # then fill it in
sudo chown root:tracking .env
sudo chmod 640 .env
sudo cp tracking-bot.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now tracking-bot

The unit stores the database in /var/lib/tracking-bot (created automatically, readable only by the tracking user) and overrides DB_PATH for that. It's sandboxed: read-only system and code, no access to home directories, a private /tmp and no devices, only network and local sockets, only the system calls a normal service needs, no capabilities, and a 512 MB memory cap. It also runs in its own user namespace, blocks privileged and resource-related system calls, and can't create memory that is both writable and executable. systemd-analyze security tracking-bot rates it at about 1.1 on systemd's 0 to 10 exposure scale (lower is better).

Note

These file-system protections need a system service. When systemd runs a unit as a regular user (systemctl --user), most of them are silently ignored.

Configuration

All settings are environment variables, usually set in .env.

Variable Default Description
TELEGRAM_TOKEN required Bot token from @BotFather
TRACK17_KEY required 17TRACK API access key
DATA_KEY required 32-byte base64 key for database encryption
CHECK_INTERVAL_MIN 15 How often all parcels are checked, in minutes
MAX_PACKAGES_PER_USER 10 Parcel limit per user, to protect the shared 17TRACK quota
CHECK_COOLDOWN_SEC 120 Cooldown between manual refreshes per user
SHARE_DAYS 7 How long share links stay valid
DAILY_ADDS_PER_USER 10 New packages a single user can add per day
DAILY_ADDS_TOTAL 50 New tracking numbers the whole bot can register per day
STALE_DAYS 30 Parcels with no tracking data for this many days are removed
INACTIVE_DAYS 90 Parcels whose tracking hasn't changed for this many days are removed
NEW_ACCOUNT_DAYS 3 How long an account counts as new
NEW_ACCOUNT_DAILY_ADDS 3 Daily package limit for new accounts
BOT_TZ UTC Default time zone for silent at night (22:00 to 07:00), used until a user picks their own in Settings
SET_PROFILE 1 Set the bot's name, commands and English description in Telegram on startup. Use 0 to manage them yourself in BotFather.
DIAG 0 Troubleshooting only: 1 prints error types to stderr. See Diagnostics.
SUPPORT_CONTACT empty Optional contact shown in Help, e.g. @yourname
DB_PATH data/tracking.db SQLite database file. Its folder is created on startup.
BANNER_PATH banner.png Image attached to every message

Commands

Command Description
/start Main menu (also used by share links)
/add <number> Track a parcel. You can also just send the number or a photo.
/list Your parcels
/refresh Check all your parcels now
/settings Language, silent at night, time zone, delete data
/language Change language
/deletedata Delete all your data
/help How it works

Languages

Every user starts in English and can switch in Settings > Language. After switching, their command menu follows the chosen language too.

All UI text lives in locales/, one JSON file per language with identical keys. To add a language:

  1. Copy locales/en.json to locales/<code>.json and translate the values. Keep {placeholders}, HTML tags and /commands as they are.
  2. Add ("<code>", "<flag> <native name>") to LANG_NAMES in bot.py.
  3. Keep every btn_* label unique within the file, because keyboard taps are matched by label.

Carriers

Carrier detection is automatic for 3,000+ carriers. The manual override list (CARRIERS in bot.py) currently includes:

4PX, Asendia USA, Azerbaijan Post, BRT, Correos, DHL Express, DHL Netherlands, FedEx, GLS Europe, GLS Italy, Hermes Germany, iMile, InPost Italy, Japan Post, Liccardi Trasporti, Pitney Bowes, Poste Italiane, PostNord, SDA, SpeedPAK (Orange Connex), SunYou, TNT Italy, UPS, UPU, Winit

To add one, look up its numeric key in 17TRACK's carrier list and add a (key, "Name") tuple.

Architecture

Stack: python-telegram-bot (async, job queue), httpx, SQLite, cryptography (AES-GCM), zxing-cpp and Pillow (QR and barcode decoding)

Design notes

  • Single process, single file. The bot runs on long polling, so it needs no web server, webhooks or open ports.
  • Batched polling. Parcels are fetched from 17TRACK in batches of 40. A number tracked by several users is registered and fetched only once.
  • Change detection. Each parcel keeps an encrypted fingerprint of its latest status and event, so a notification goes out only when it changes, or when the user's N-hour interval is due.
  • Keyboard-driven UI. Screens use a reply keyboard, and taps are mapped back to actions per screen. Previous menu screens are cleaned up so the chat stays readable.
  • Self-healing. Numbers that 17TRACK has dropped are re-registered automatically, in batches of 40, within the daily limit. A failed re-registration doesn't stop the check. Users who block the bot or whose chat no longer exists are purged.
  • Schema versioning. The database carries a schema version (PRAGMA user_version), and upgrades run automatically on startup.
  • Responsive under load. Updates from different users are handled in parallel, while each chat is processed in order. A manual refresh during a scheduled check answers right away instead of waiting. Outgoing messages go through a rate limiter, so Telegram's flood limits don't drop notifications.
  • No lost edits. Polling runs one at a time, and both polling and carrier changes are merged into the latest stored version of each parcel, so a rename or settings change made in the meantime isn't overwritten.

Database schema (everything sensitive is inside data)

packages (id = random, owner = HMAC(chat_id), number_tag = HMAC(number), data = AES-GCM blob)
users    (owner = HMAC(chat_id), data = AES-GCM blob)
shares   (token_tag = HMAC(token), data = AES-GCM blob, expires_at, ref = HMAC(owner, package))
meta     (key, value)   banner file_id, key check value and today's anonymous add counters

Project layout

.
├── bot.py                 the whole bot
├── locales/               UI translations (11 JSON files)
├── banner.png             attached to every message
├── logo.png               bot profile picture
├── tests/                 unit tests
├── tracking-bot.service   systemd service
├── requirements.txt
├── .env.example
└── LICENSE

Limitations

  • Shared 17TRACK quota. On a public instance all users share the operator's key: the free plan covers 200 numbers in total, and registrations aren't refunded when a number is deleted. The daily limits slow abuse down but don't remove it.
  • Update latency. 17TRACK refreshes carrier data on its own schedule, so updates can arrive later than on the carrier's own website.
  • Buttons after a restart. Menu state lives only in memory. After a restart, tapping an old button shows a fresh main menu.
  • No end-to-end secrecy from the operator. See the threat model.

Roadmap

  • Bring your own 17TRACK key: each user adds their own API key, so there's no shared quota (see TODO.md)
  • 17TRACK webhooks as an alternative to polling
  • More carriers in the manual override list
  • Native-speaker review of all translations

Tests

.venv/bin/python -m unittest discover -s tests

The tests cover number parsing, 17TRACK response parsing, caption limits, encryption (including a check that nothing readable ends up in the database file), QR decoding, the polling logic (notifications, delivered and stale parcels, N-hour summaries), share links, daily limits and per-user silent hours. 17TRACK and Telegram are replaced by fakes. They don't need a Telegram token or a 17TRACK key.

Contributing

Issues and pull requests are welcome. Before opening a PR:

  • Keep the privacy guarantees intact. Don't add logging, analytics, or plaintext storage of user data.
  • Add new UI strings to all files in locales/, with the same keys in the same order.
  • Test the full add flow (text, photo, name, both notification modes) against a test bot.

License

Released under the MIT License. Copyright (c) 2026 Soyboy.

About

Privacy-first Telegram bot that tracks parcels from 3,000+ carriers via 17TRACK. Add by number or label photo (QR/barcode), get notified on every status change. Encrypted, no logs, self-hostable.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages