Python service for delivering Discourse webhook notifications to Telegram through a local Telegram Bot API sidecar.
- Secrets are not committed.
- Real environment files are ignored by git.
- Test and production values use separate local env files.
- VPS deployment uses a Compose overlay for
/var/tools.
notification-service/- Python app: FastAPI ingestion, Redis Stream drain, pending-message reaper.docker-compose.yml- local/dev compose for this repo only.deploy/docker-compose.notification-service.test.yml- test overlay compose for/var/toolson the VPS.deploy/docker-compose.notification-service.prod.yml- production overlay compose for/var/toolson the VPS.deploy/docker-compose.notification-service.yml- legacy single-instance overlay kept for compatibility.deploy/deploy-notification-test.sh- pulls the repo and deploys the test instance.deploy/deploy-notification-prod.sh- pulls the repo and deploys the production instance.deploy/.env.notification.test.example- test env template.deploy/.env.notification.prod.example- production env template.
Assumed VPS layout:
/var/tools/docker-compose.yml
/var/tools/notification-sender-service-discourse/
/var/tools/notification-sender-service-discourse-test/
/var/tools/notification-sender-service-discourse-prod/
The base checkout stores Git metadata. Test and production are separate Git worktrees:
cd /var/tools
git clone <repo-url> notification-sender-service-discourse
cd notification-sender-service-discourse
git fetch origin
git pull --ff-only
git show-ref --verify --quiet refs/remotes/origin/staging || git push origin main:staging
git checkout --detach
git worktree add ../notification-sender-service-discourse-test staging
git worktree add ../notification-sender-service-discourse-prod mainCreate a local test env file:
cp notification-sender-service-discourse-test/deploy/.env.notification.test.example \
notification-sender-service-discourse-test/deploy/.env.notification.test
chmod 600 notification-sender-service-discourse-test/deploy/.env.notification.testFill real values in:
nano notification-sender-service-discourse-test/deploy/.env.notification.testValidate the merged compose before starting anything:
docker compose \
--env-file .env \
--env-file notification-sender-service-discourse-test/deploy/.env.notification.test \
-f docker-compose.yml \
-f notification-sender-service-discourse-test/deploy/docker-compose.notification-service.test.yml \
configStart only the test services:
notification-sender-service-discourse-test/deploy/deploy-notification-test.shRun only notification-redis-test and notification-service-test during test rollout.
Test mode uses deploy/.env.notification.test and points to the test Discourse forum/table/bot.
Expose it through Nginx Proxy Manager as:
tgsender-test.example.ru -> notification-service-test:8067
Production mode uses deploy/.env.notification.prod and must be created only after test mode is verified:
cp notification-sender-service-discourse-prod/deploy/.env.notification.prod.example \
notification-sender-service-discourse-prod/deploy/.env.notification.prod
chmod 600 notification-sender-service-discourse-prod/deploy/.env.notification.prodExpose production through Nginx Proxy Manager as:
tgsender.example.ru -> notification-service-prod:8067
Production deploy command:
notification-sender-service-discourse-prod/deploy/deploy-notification-prod.shKeep test and production running as separate compose services. Do not switch one container between test and production env files.
If executable bits are not preserved after cloning, run scripts through bash:
bash notification-sender-service-discourse-test/deploy/deploy-notification-test.sh
bash notification-sender-service-discourse-prod/deploy/deploy-notification-prod.shThe test deploy script must run from the staging worktree. The production deploy script must run from the main worktree.
notification-redis-testandnotification-redis-prodare separate Redis instances.notification-service-testandnotification-service-prodhave no published host ports by default.- Nginx Proxy Manager should reach them through
proxy_networkby service name and port8067. - The service reaches Telegram via
telegram-bot-api:8081on the existing/var/toolsdefault network. - The service reaches Supabase via
supabase-kong:8000onsupabase_default. - The service enriches notifications through Discourse API using
DISCOURSE_API_KEYandDISCOURSE_API_USERNAME. - Account linking endpoints are protected by
ACCOUNT_LINK_API_TOKEN. - n8n should call
POST /telegram/link-tokento create a short-lived forum link for/settings. - The Discourse plugin should call
POST /telegram/account-linkto finalize the token withdiscourse_user_id,discourse_username,email, andlinked_at. - Set
NOTIFICATION_LOG_PAYLOAD_DATA=trueto lognotification.dataduring template debugging.
Local checks that do not need external services:
python -m compileall notification-service
cd notification-service
python -m unittest discover -s tests -vCompose validation:
docker compose --env-file .env.example configOn the VPS, use the merged compose validation command from the deployment section.
- Invalid HMAC signature returns
401. - Invalid account-link Bearer token returns
401. - Account-link tokens are stored in Redis with
ACCOUNT_LINK_TOKEN_TTL_SECONDSTTL. - Expired account-link tokens return
410; conflicting active links return409. - Expected internal failures after successful signature validation return
200 {"ok": true}to avoid a Discourse retry storm. - Deduplication is atomic Redis Lua:
SET NX EX+XADD. - New-topic deduplication is semantic: notification types
9,17, and36collapse to onenew_topic:{topic_id}:{user_id}key. - Drain enriches events from Discourse API cache-first and falls back to a minimal HTML message if enrichment fails.
XACKhappens only after Telegram accepts the message or after the retry limit is exhausted.- Dead letters are JSON logs in stdout for Promtail/Loki, not DB rows.