Personal MAX → Telegram bridge for your school and community chats
Your chats route themselves. Silently.
MAX is a mandatory messenger in Russian schools and public institutions. It has no official Telegram client and no open API. Maxgram solves this by running an unofficial WebSocket userbot that mirrors every MAX chat into a Telegram Forum Supergroup topic — and routes replies back.
Result: read and answer all MAX conversations from Telegram, without installing MAX.
Русская версия / Russian version →
MAX (personal account) Telegram Forum Supergroup
┌───────────────────┐ ┌──────────────────────────┐
│ DM: Contact │────────►│ 📁 Contact Name │
│ Group: School │────────►│ 📁 School Group │
│ Group: Sports │────────►│ 📁 Sports Club │
│ ... │ │ 📁 ... │
└───────────────────┘ └──────────────────────────┘
▲ │
└──────── Reply in topic ────────┘
Each MAX chat (DM or group) becomes a separate Telegram topic, created automatically on first message. Replying in a topic sends the message back to MAX.
- Unofficial WebSocket API — reverse-engineered
pymaxuserbot with a custom reconnect loop that fixes an OOM bug in the upstream library (reconnect=False+ outerwhile True) - Idempotent message deduplication —
max_msg_idis written to SQLite before forwarding to Telegram, making the system safe to restart at any point without duplicates - Privacy-first design — no message text or media is ever stored; SQLite only holds routing metadata (chat bindings, message ID map, delivery log)
- Production-deployed — running on Hetzner Cloud behind Docker Compose with UFW, fail2ban, non-root container, and SSH-key-only access
- Ansible-driven ops — regular deploy, backup, recovery, fresh-VM bootstrap, and hardening are all codified as idempotent playbooks under
infra/ansible/; the manual runbook is kept only as fallback - Supervisor runtime shell — PID1 is now a supervisor that keeps the container
Up, restarts the bridge worker with backoff, and persists health state even when MAX/TG integration degrades - Resilient delivery — Telegram API calls retry with exponential backoff; temporary TG→MAX transport failures retry automatically; failed outbound deliveries are written to SQLite with attempt counts; MAX watchdog alerts on offline > 60s; retryable MAX video downloads are persisted until delivered;
/statusgives live health snapshot on demand - Persistent health model —
health_state.json,health_events.jsonl,alert_outbox.jsonl, andhealth_heartbeat.jsonmake degraded-vs-dead runtime states explicit
- Automatic topic creation for every new MAX chat
- Bidirectional messaging — replies in Telegram → delivered to MAX (including reply-to-message)
- Media forwarding in both directions: photos, video, audio, voice, documents
- MAX video downloads prefer real
MP4_*streams overEXTERNALplayer pages and use an adaptive CDN user-agent (CHROMEvs mobile Safari) - Retryable MAX video failures are queued in SQLite and sent later to the same Telegram topic without storing signed URLs or tokens
- MAX downloader validates
Content-Type+ file signature and rejects HTML/text fallbacks for expected media - MAX
CHANNEL/forward wrappers are unwrapped into the real forwarded text and media instead of a generic system placeholder - Unknown MAX message shapes are forwarded with diagnostic metadata (
type,link_*, counts, raw field names) so new formats can be fixed from the next occurrence - MAX attachment aliases (
IMAGE,VOICE,DOCUMENT,DOC) are normalized consistently across dispatch and download stages - MAX
VOICEattachments are delivered as native Telegram voice notes (send_voicebubbles) - Control events are rendered in human-friendly text (including
joinbylinkjoin notifications) - Sender name prefix in group chats:
[First Last] message text - Own messages (sent directly in MAX) mirrored to Telegram with
[Вы]prefix - DM topics named after the contact (resolved from MAX profile, cache + live API)
- Per-chat modes:
active/readonly/disabled - Deduplication — no duplicate messages on reconnect
- Stable reconnect — no OOM, no SSL storm
/statuscommand — uptime, message stats, top active chats; works in forum group and personal DM with bot/chatscommand — list of bridged chats with topic id, mode, and inbound/outbound counters- Periodic 4-hour status report — automatic delivery stats sent to owner DM, with optional fanout to a dedicated forum topic
- MAX offline watchdog — alert if MAX unreachable > 60 seconds
- Reconnect gap warning — after recovery, owner gets a reminder about possible missed messages during downtime
- Telegram API retry with exponential backoff (3 attempts, respects
Retry-After) - MAX outbound retry for temporary transport/session failures (3 attempts, short backoff)
- Failed TG→MAX deliveries are persisted in
delivery_logwith error reason and attempt count - Startup self-check in production — after boot, the bot notification includes
pytestresult summary - System alerts survive temporary Telegram outages via persistent outbox + retry
- Docker healthcheck is tied to supervisor heartbeat, not external MAX/TG availability
MAX WebSocket ──► MAX Adapter ──► Bridge Core ──► TG Adapter ──► Telegram
(pymax) (routing) (aiogram) (Topics API)
│
SQLite DB
(bindings, dedup,
delivery log)
One Python service with two layers: a long-lived supervisor plus a restartable bridge worker. No external queues or services. SQLite and persisted health files are the only state stores.
Details: docs/architecture.md
| Layer | Technology |
|---|---|
| MAX userbot | pymax / maxapi-python |
| Telegram bot | aiogram 3.x |
| Database | SQLite + aiosqlite |
| Config | YAML + python-dotenv |
| Runtime | Python 3.13+, asyncio |
| Deployment | Docker Compose / Hetzner Cloud |
| Ops automation | Ansible (infra/ansible/) |
Bridge is running in production on Hetzner Cloud.
- Runtime: Docker Compose (non-root container,
cap_drop: ALL,restart: always) - State: SQLite + MAX session in a bind-mounted
data/directory - Health: Docker
HEALTHCHECKuses supervisor heartbeat freshness instead of checking external integrations - Access: SSH key only, restricted by IP via UFW
- Security:
fail2ban,unattended-upgrades, no public HTTP ports - Boot signal: startup notification in Telegram owner DM includes runtime/host info plus startup
pytestsummary
Requirements: Python 3.13+, Telegram bot (@BotFather), Forum Supergroup with Topics enabled, MAX account.
# 1. Dependencies
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 2. Config + secrets
cp .env.example .env
cp .env.secrets.example .env.secrets
# Fill in secrets in .env.secrets:
# TG_BOT_TOKEN, TG_OWNER_ID, TG_FORUM_GROUP_ID, MAX_PHONE
# 3. (optional) Local chat bindings
cp config.local.yaml.example config.local.yaml
# 4. First run — MAX authorization via SMS code
.venv/bin/python -m src.main
# 5. Background run
nohup .venv/bin/python -m src.main >> data/bridge.log 2>&1 &Via Docker:
docker compose -f deploy/docker-compose.yml up -dmaxgram/
├── src/
│ ├── main.py ← supervisor entry point + worker bootstrap
│ ├── adapters/
│ │ ├── max_adapter.py ← MAX userbot: connect, recv, send, reconnect
│ │ └── tg_adapter.py ← Telegram bot: topics, send, receive, ops alerts
│ ├── bridge/
│ │ └── core.py ← all routing logic
│ ├── config/loader.py
│ ├── runtime/
│ │ ├── health.py ← persisted health snapshot/events/outbox/heartbeat
│ │ ├── supervisor.py ← worker restart loop + alert integration
│ │ └── healthcheck.py ← Docker healthcheck entry point
│ └── db/
│ ├── models.py ← SQLite schema (3 tables)
│ └── repository.py
│
├── docs/
│ ├── architecture.md
│ ├── roadmap.md
│ ├── decisions/ ← ADR-001…004
│ └── runbooks/
│
├── deploy/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── docker-compose.prod.yml
│
├── infra/
│ └── ansible/ ← deploy / backup / recover / bootstrap / hardening
│
├── tests/ ← pytest regression suite
└── scripts/smoke_check.py
- Messages during downtime are lost — pymax has no history replay API
- Unofficial userbot — potential ToS violation with MAX
- Bot commands (
/status,/chats,/reauth) are owner-only ops_topic_idis optional; without it, ops alerts go only to owner DM
MIT — see LICENSE
See README-ru.md for full Russian documentation.