Library / SDK
wangwangit/SubsTracker avatar
wangwangit/SubsTracker

SubsTracker: a Cloudflare Workers subscription expiry reminder you deploy yourself

基于Cloudflare Workers的轻量级订阅管理系统,帮助您轻松跟踪各类订阅服务的到期时间,并通过Telegram发送及时提醒。

3,237 stars2,579 forksJavaScriptMIT

At a glance

What is it?
SubsTracker runs on Cloudflare Workers with KV storage and pushes renewal reminders through ten notification channels. It suits one person tracking domains, memberships and bills, and it is a poor fit for teams or approval workflows.
Who is it for?
Adopt SubsTracker if you are one person tracking domains, memberships or recurring bills and you already have a Cloudflare account, because the whole system is a Worker plus one KV namespace and the default 7/3/1/day reminder preset covers most renewal cycles. Do not adopt it for multi-user collaboration or approval flows, which the README lists as out of scope.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What SubsTracker actually solves, and for whom

Recurring charges fail quietly. A domain lapses, a membership auto-renews at a higher tier, a SIM top-up card goes dormant. Calendar apps handle one-off events badly and spreadsheets do not send anything. SubsTracker is a small web application that stores subscriptions, computes how many days remain against a configured timezone, and fires a message to a channel you already use when a rule matches.

The README is explicit about the audience: personal self-hosting, and reminders for domains, memberships and bills. It is equally explicit about the opposite case. Multi-user collaboration and complex enterprise approval flows are named as unsuitable. That is a useful boundary rather than a marketing hedge, because the whole permission model is a single admin account with a username and password stored in KV. There are no roles, no per-user data separation and no audit trail tied to a person. If two people need to share the same list, they share the same login.

The project is version 3.0.0, MIT licensed, written in JavaScript, and the last push to the master branch was on 2026-07-12. The repository is not archived. It is built on Hono, and the only runtime dependency declared in package.json is hono, which keeps the deployable artifact small.

The hourly cron, the KV namespace and the reminder rule semantics

The architecture is a single Worker with two entry points. According to the repository layout, src/index.js holds the fetch and scheduled handlers, src/app.js wires up Hono, and the rest splits into core (time, lunar calendar, currency, JWT), data (KV access and migrations), services (scheduler and notification channels), api (routes and handlers) and views (the admin HTML). Static assets live in public/, and tests use Vitest with workerd.

The scheduler runs on the cron expression 0 * * * *, so it wakes roughly once an hour. The README explains that the trigger is UTC but the decision uses the timezone from your configuration. Each run asks three questions in order: is the current hour inside the allowed send window, does any enabled subscription match a reminder rule exactly, and has this subscription and rule already fired today. Only then does it call the enabled channels and write entries to the notification history and the scheduling log.

The rule semantics trip people up, and the README calls this out directly. A rule of "7 days before expiry" fires on the single day when the remaining count is exactly 7. It does not fire every day from day 7 down to day 1. To get a 7/6/5 pattern you need separate rules. The default preset is 7 days, 3 days, 1 day and the expiry day itself, which is why most users never notice the distinction until they change the preset.

Deduplication is per day and per rule, so the same reminder cannot repeat within a day even if the cron runs multiple times inside the allowed window. Two subscription modes exist: a recurring subscription extends from the current expiry date, while a reset subscription recalculates a full period from the payment date. The README gives a concrete contrast: a membership expiring on 6/15 renewed on 6/3 lands around 7/15 under the recurring mode, whereas a top-up card counts 180 days from the recharge date under reset.

Deploying SubsTracker with deploy:safe

The README recommends the command line path. Clone the repository, install dependencies, export a Cloudflare API token, then run the safe deploy script. The script is a two-step composition defined in package.json: npm run setup, which runs scripts/setup-kv.cjs to create or bind the KV namespace named SUBSCRIPTIONS_KV, followed by npm run deploy, which calls npx wrangler deploy.

bash
git clone https://github.com/wangwangit/SubsTracker.git
cd SubsTracker
npm install
export CLOUDFLARE_API_TOKEN=你的token
npm run deploy:safe

On success the terminal prints a workers.dev URL of the form https://subscription-manager.<your-subdomain>.workers.dev. The README also documents a GitHub Actions route: fork the repository, add CLOUDFLARE_API_TOKEN (and optionally CLOUDFLARE_ACCOUNT_ID) under Settings, then push to master or main, or run the Deploy workflow manually.

The first login uses admin and password. The README states plainly that you should change this immediately in the system configuration page, because a publicly reachable Worker with the default credentials will be taken over. If you lose the password later, the recovery path is the Cloudflare dashboard: open Workers & Pages, go to KV, open SUBSCRIPTIONS_KV, edit the JSON under the key config, and change ADMIN_PASSWORD.

After logging in, four settings matter before anything else. Set the timezone, with Asia/Shanghai named for mainland China. Set the allowed send hours, where a value of 08 means only the 08:00 hour sends, 08, 20 allows two windows, and an empty value or * allows every hour. Enable at least one channel and press its test button until a message arrives. Then add a test subscription with a near expiry date and the 7/3/1/day preset to confirm the pipeline end to end.

The allowed send hours are the most common source of false alarms

Most "it stopped working" reports trace back to the send window rather than to a broken scheduler. The README lists the symptom in its own troubleshooting table: the task history says the current hour is not in the allowed list. If you configured 08 and check the log at 19:00, the entry is a normal skip. Nothing failed. The Worker woke, evaluated the window, and declined to send.

A second confusing case is a run inside the window that reports sentCount=0. That means no rule matched today, which is the exact-day design working as intended. A third is a failed record, and that one is a real error: a wrong token, an expired credential or a network rejection. The notification history at /admin/notify-logs keeps the failure detail, and the scheduling log records why each run hit, deduplicated or skipped.

The configuration page shows a live preview of whether the current moment would send, but the README warns that the preview only agrees with the server after you save. There is also a /debug endpoint behind the login that reports timezone and notification window diagnostics. The practical consequence is that you should test with the window open, then narrow it. If you want more than one reminder per day, write multiple hours such as 08, 12, 20 rather than adding rules.

Notification channels, backups and the third-party API

Ten channels ship with the project: Telegram, NotifyX, Webhook, WeCom, Resend email, Bark, Gotify, Server酱, PushPlus and ntfy. Each needs its own credential. Telegram wants a bot token and chat ID, with an optional Topic ID that maps to message_thread_id for forum groups. Bark wants a device key and optionally a self-hosted server. ntfy defaults to https://ntfy.sh and needs a topic, with the phone app subscribed to the same topic. Webhook accepts any HTTP address plus an optional template, which is the escape hatch for anything not on the list.

Backup lives at the bottom of the system configuration page. Export downloads a JSON file, and the README notes it can exclude secrets by default. Import offers two modes: merge overwrites matching subscription IDs and keeps everything else, while overwrite clears first and loads the whole file. The README flags overwrite as dangerous and tells you to export current data before using it. Migrating a Cloudflare account or upgrading a major version is the case where this matters.

For external systems, the configuration page can generate a third-party API token. The README's example posts to a tokenized URL.

bash
curl -X POST "https://你的域名.workers.dev/api/notify/你的令牌" \
  -H "Content-Type: application/json" \
  -d '{"title":"标题","content":"正文"}'

The same token can be passed as Authorization: Bearer 你的令牌 instead of appearing in the path. This turns SubsTracker into a generic push relay for other tools, not just a subscription tracker.

Where SubsTracker is the wrong tool

The single-admin model is the hard limit. Every user shares one login, one subscription list and one set of channel credentials. There is no concept of assigning a subscription to a person, no approval step before a renewal, and no per-user notification routing. A finance team that needs a second pair of eyes on a renewal decision will not find it here.

The hourly granularity is the second constraint. The cron fires once an hour, so the finest resolution you get is the hour, and only within your allowed window. If you need a reminder at 09:15 exactly, this is not the scheduler for that. The README frames the window as a feature, since it prevents overnight messages, but it is also a ceiling on precision.

The exact-day rule design is a third trap. Anyone who reads "7 days before" as "starting 7 days before" will configure one rule, receive one message, and conclude the system is broken. The README anticipates this in its FAQ, but it remains a design choice that puts the burden on the user to enumerate every day they care about.

Finally, the observability is deliberately thin. There is a notification log, a scheduling log and a debug page, all behind the admin login. There is no metrics export, no alerting on the alerting system itself. If the cron silently stops, the first signal is a missed renewal, not a dashboard.

How SubsTracker differs from a calendar reminder or a hosted tracker

A calendar app solves the storage problem and the display problem but not the credential problem. It lives in someone else's account, it cannot post to a Telegram bot token you control, and its reminder rules are tied to the calendar's own model. SubsTracker keeps the data in a KV namespace inside your own Cloudflare account, and the notification path is a set of tokens you own and can rotate.

Against a hosted subscription tracker, the difference is the deployment model rather than the feature list. A hosted service owns the database and the scheduler; you get an account and a monthly bill. SubsTracker gives you a Worker URL and a KV namespace, and the operating cost is whatever Cloudflare charges for Workers and KV usage on your plan. The trade is that you own the uptime. If the Worker fails to deploy after an upgrade, no one else notices.

Against a plain cron job plus a shell script, SubsTracker adds a UI, a lunar calendar option, multi-currency expense tracking with a fallback when the exchange rate API fails, payment history and the ten-channel fan-out. Those are the parts you would otherwise rewrite. The script would be smaller and would have no KV migration step on upgrade.

Upgrades, licence and what to verify before you rely on it

Upgrading is three commands: git pull, npm install, npm run deploy:safe. The README states that the first visit after an upgrade runs the KV structure migration automatically, and it recommends exporting a backup first. There is no documented rollback procedure, so the export is the only recovery path the README offers.

Development follows the same shape. npm install, npm test for unit and integration tests, npm run lint which is tsc --noEmit against jsconfig.json, and npx wrangler dev --config wrangler.dev.toml --local to run locally at http://127.0.0.1:8787 with the default admin and password. The README asks contributors to include tests for business logic changes.

The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. This is a description of the licence text, not legal advice; if you redistribute the project inside a product, read the LICENSE file in the repository root yourself.

One deployment error is worth knowing in advance. Authentication error [code: 10000] during deploy means the API token lacks permission or Wrangler's cache is stale; the README suggests checking token permissions and deleting .wrangler/ before retrying. Separately, beacon.min.js or cloudflareinsights errors in the browser console come from Cloudflare's own analytics script, not from SubsTracker's code.

The security section lists four habits: change the default credentials, never commit API or bot tokens to Git, treat a backup that includes sensitive configuration as a password, and rotate a leaked Cloudflare API token in the dashboard.

Editorial conclusion

Adopt SubsTracker if you are one person tracking domains, memberships or recurring bills and you already have a Cloudflare account, because the whole system is a Worker plus one KV namespace and the default 7/3/1/day reminder preset covers most renewal cycles. Do not adopt it for multi-user collaboration or approval flows, which the README lists as out of scope. Before trusting it with real renewals, change the admin password, set the timezone to Asia/Shanghai if you are in China, run the built-in channel test until it succeeds, and confirm the notification window in the system configuration preview matches what you expect, since a value of 08 means the scheduler sends during that hour only.

Frequently asked questions

Why did SubsTracker not send me a notification?

Check in order: whether a channel is enabled and passes its test, whether the current hour is inside the allowed send window (a value of 08 means only that hour), whether the timezone is set correctly, whether the subscription and its rules are enabled, and whether today is exactly the day a rule matches. The notification history shows whether a send failed or was skipped.

What does the task history entry about the allowed send hour mean in SubsTracker?

It means the scheduled task ran but the current hour is not in your allowed list, so it skipped. If you configured only 08, a run at 19:00 is a normal skip rather than a failure.

Why does SubsTracker only notify on one day when I set a rule for 7 days before expiry?

The rule fires only on the day when the remaining count is exactly 7. It does not repeat from day 7 down to day 1. Add separate rules or use the 7/3/1/expiry-day preset to cover more days.

How do I back up SubsTracker or move it to another Cloudflare account?

Use export backup at the bottom of the system configuration page to download a JSON file, then import it in the new environment. Merge overwrites matching subscription IDs and keeps the rest; overwrite clears existing data first, so export again before using it.

What is the difference between recurring and reset subscriptions in SubsTracker?

A recurring subscription extends from the current expiry date, so a membership expiring 6/15 and renewed 6/3 lands around 7/15. A reset subscription recalculates a full period from the payment date, which suits top-up cards counted from the recharge day.

Can another system send notifications through SubsTracker?

Yes. Generate a third-party API token in the system configuration, then POST JSON with title and content to /api/notify/ followed by the token, or pass the token as an Authorization Bearer header.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. wangwangit/SubsTracker on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wangwangit-substracker.svg)](https://hysenlabs.com/projects/wangwangit-substracker)