A background worker that monitors public Apple TestFlight invitation links for any app. It sends an alert when a previously closed or full beta becomes available. Email through Resend and Telegram are independently optional; with neither configured, the worker logs status changes.
The checker does not reserve slots or enrol testers. Apple can change its pages, and availability can disappear before you open an invitation.
Requires Node.js 24 or later and npm.
git clone https://github.com/DangerMouseUK/testflightchecker.git
cd testflightchecker
npm ciCopy .env.example to .env (Copy-Item .env.example .env in PowerShell, or cp .env.example .env in a POSIX shell). Replace the fictional invitation code with a real public TestFlight link.
INVITE_URLS=https://testflight.apple.com/join/ABCDEFGH|https://testflight.apple.com/join/IJKLMNOP
POLL_INTERVAL_MS=60000
POLL_JITTER_PERCENTAGE=10ABCDEFGH and IJKLMNOP are fictional examples. Duplicate invitations are checked once. Only HTTPS links on testflight.apple.com with an eight-character alphanumeric /join/ code are accepted. Query strings, fragments, and a trailing slash are removed.
npm run build
npm startFor development, use npm run dev. Production runs use npm run start:prod and JSON logging; local interactive terminals use readable logs.
Existing shell environment variables take precedence over .env.
| Variable | Default | Purpose |
|---|---|---|
INVITE_URLS |
Required | Pipe-separated public TestFlight invitation links. |
POLL_INTERVAL_MS |
60000 |
Positive integer milliseconds between completed polling cycles, up to the Node timer limit. |
POLL_JITTER_PERCENTAGE |
10 |
Random timing variation from 0 to 100 percent. The final delay is at least half the interval and at least one second. |
PORT |
8080 |
HTTP health server port, from 1 to 65535. |
NODE_ENV |
Unset | Set to production for production logging. |
RESEND_API_KEY |
Blank | Optional email credential. |
ALERT_SENDER |
Blank | Sender authorised by Resend, for example sender@example.com. |
ALERT_RECIPIENT |
Blank | Comma-separated email recipients, for example recipient@example.com. |
TELEGRAM_BOT_TOKEN |
Blank | Optional Telegram bot credential. |
TELEGRAM_CHAT_ID |
Blank | Target Telegram user or group chat. |
Invalid polling values generate a warning and use the defaults. Leave all settings for an unused notification channel blank. Email requires its three settings together; Telegram requires both. Partial channel configuration stops startup and names the missing settings.
For email, create a Resend API key and configure an authorised sender. For Telegram, create a bot through BotFather, start a conversation with it or add it to a group, and configure that chat's ID. Keep both services' credentials private.
- The first recognised check establishes a baseline without an availability alert. Restarting resets this in-memory baseline.
- The full and not-accepting messages both count as unavailable. Apple's joining instructions or a recognised "View in TestFlight" join control count as available.
- Unrecognised HTML, redirects, HTTP errors, and network failures remain unknown. They do not overwrite the last recognised state or trigger availability alerts.
- Unavailable-to-available transitions alert every enabled channel. Available-to-unavailable transitions send Telegram updates when enabled.
- Email attempts delivery up to three times with one- and four-second retry delays. Telegram sends once. Each network request has a 15-second timeout.
- Channel failures are logged independently. The recognised state advances after delivery attempts, so an unchanged status does not repeatedly alert after a delivery failure.
- Polling is sequential and does not overlap. There is no persistent storage or automatic enrolment.
When Telegram is configured, startup sends a confirmation message. Logs omit notification addresses, chat IDs, tokens, raw provider payloads, and error stacks.
GET /healthz returns liveness, uptime, monitored invitation count, memory use, and the timestamp of the last cycle in which every invitation had a recognised status. That timestamp starts as null. Liveness remains healthy during upstream failures; it does not guarantee current slot availability or successful notification delivery.
docker build -t testflightchecker .
docker run --rm --env-file .env -p 8080:8080 testflightcheckerThe multi-stage Node.js 24 image compiles TypeScript, installs production dependencies, and runs as the Node user. Private configuration and Git history are excluded from the build context; tests are not included in the image.
spec.yaml is a DigitalOcean App Platform template. Connect your own repository in the hosting dashboard, replace its fictional invitation, and set any enabled notification channel's configuration in the platform's secret settings. Publishing this repository does not deploy a running service.
npm test -- --runInBand
npm run build
npm audit --omit=devTests use fictional configuration and mocked notification requests. Never commit real configuration, logs, credentials, or private diagnostic output. .env.example is the only tracked environment template.
If a key or token has appeared in a repository, revoke it in Resend or through BotFather and create a replacement before enabling alerts. Deleting files or resetting Git history cannot invalidate a credential or remove copies held elsewhere. See GitHub's sensitive-data guidance.
Package metadata declares the ISC licence.