Automated Telegram backup with Docker. Performs incremental backups of messages and media on a configurable schedule.
This project is developed with AI assistance (Claude Code).
- Incremental backups — Only downloads new messages since last backup
- Scheduled execution — Configurable cron schedule (default: every 6 hours)
- Real-time listener — Catch edits, deletions, and new messages instantly between backups
- Album support — Groups photos/videos sent together as albums
- Service messages — Tracks group photo changes, title changes, user joins/leaves
- Forwarded message info — Shows original sender name for forwarded messages
- Channel signatures — Displays post author when channels have signatures enabled
- Media deduplication — Symlinks identical files to save disk space
- Avatars always fresh — Profile photos updated on every backup run
- Photos, videos, documents, stickers, GIFs
- Voice messages and audio files with in-browser player
- Polls with vote counts and results
- Configurable size limits and selective download
- Telegram-like dark UI — Feels like the real app
- Mobile-friendly — Responsive design with iOS/Android optimizations
- Integrated lightbox — View photos and videos without leaving the page
- Keyboard navigation — Arrow keys to browse media, Esc to close
- Real-time updates — WebSocket sync shows new messages instantly
- Push notifications — Get notified even when browser is closed
- Chat search — Find messages by text content
- JSON export — Download chat history with date range filters
- Multi-user access control — Master account + DB-backed viewer accounts with per-user chat whitelists
- Admin panel — Create, edit, delete viewer accounts with fine-grained chat permissions
- Audit logging — Track all login attempts, admin actions, and API access
- Authenticated media — Media files require login and respect per-user permissions
- Mass deletion protection — Rate limiting prevents accidental data loss
- Runs as non-root — Docker best practices
- SQLite (default) — Zero config, single file
- PostgreSQL — For larger deployments with real-time LISTEN/NOTIFY
See docs/CHANGELOG.md for complete version history.
Have a feature request? Open an issue!
Two separate Docker images are available (v4.0+):
| Image | Purpose | Size |
|---|---|---|
drumsergio/telegram-archive |
Backup scheduler (requires Telegram credentials) | ~300MB |
drumsergio/telegram-archive-viewer |
Web viewer only (no Telegram client) | ~150MB |
📦 Upgrading from v3.x? See Upgrading from v3.x to v4.0 for migration instructions.
- Go to https://my.telegram.org/apps
- Log in with your phone number
- Create a new application (any name/platform)
- Note your API ID (numbers) and API Hash (letters+numbers)
# Clone the repository
git clone https://github.com/GeiserX/Telegram-Archive
cd Telegram-Archive
# Create data directories
mkdir -p data/session data/backups
chmod -R 755 data/
# Configure environment
cp .env.example .envEdit .env with your credentials:
TELEGRAM_API_ID=12345678 # Your API ID
TELEGRAM_API_HASH=abcdef123456 # Your API Hash
TELEGRAM_PHONE=+1234567890 # Your phone (with country code)
VIEWER_USERNAME=admin # Required for web access
VIEWER_PASSWORD=change-this # Required for web accessOptional: enable a SOCKS5 proxy for all Telegram connections (useful in regions where Telegram is blocked or behind corporate firewalls)
TELEGRAM_PROXY_TYPE=socks5
TELEGRAM_PROXY_ADDR=127.0.0.1
TELEGRAM_PROXY_PORT=1080
TELEGRAM_PROXY_USERNAME=
TELEGRAM_PROXY_PASSWORD=
TELEGRAM_PROXY_RDNS=falseOption A: Using the provided scripts (recommended for fresh installs)
# Run authentication
./init_auth.sh # Linux/Mac
# init_auth.bat # WindowsOption B: Direct Docker command (for existing deployments or re-authentication)
If your session expires or you need to re-authenticate an existing container:
# Generic command - adjust volume paths and credentials
docker run -it --rm \
-e TELEGRAM_API_ID=YOUR_API_ID \
-e TELEGRAM_API_HASH=YOUR_API_HASH \
-e TELEGRAM_PHONE=+YOUR_PHONE_NUMBER \
-e SESSION_NAME=telegram_backup \
-v /path/to/your/session:/data/session \
drumsergio/telegram-archive:7.7.0 \
python -m src authExample for docker compose deployment:
# If using docker compose with a session volume
docker run -it --rm \
--env-file .env \
-v ./data:/data \
drumsergio/telegram-archive:7.7.0 \
python -m src auth
# Then restart the backup container
docker compose restart telegram-backupWhat happens during authentication:
- The script connects to Telegram's servers
- Telegram sends a verification code to your Telegram app (check "Telegram" chat)
- Enter the code when prompted
- If you have 2FA enabled, enter your password when prompted
- Session is saved to the mounted volume for future use
docker compose up -dView your backup at http://localhost:8000
The default compose binds the viewer to 127.0.0.1. Put it behind a reverse proxy only after setting VIEWER_USERNAME and VIEWER_PASSWORD. To deliberately run without auth for a local-only viewer, set ALLOW_ANONYMOUS_VIEWER=true — this grants read-only access only; writes still require the master account.
| Problem | Solution |
|---|---|
Permission denied |
Run chmod -R 755 data/ |
init_auth.sh: command not found |
Run chmod +x init_auth.sh first |
| Viewer shows no data | Both containers need same database path - see Database Configuration |
Failed to authorize |
Re-run ./init_auth.sh |
The standalone viewer image (drumsergio/telegram-archive-viewer) lets you browse backups without running the backup scheduler.
# Example: Viewer-only deployment
services:
telegram-viewer:
image: drumsergio/telegram-archive-viewer:7.7.0
ports:
- "127.0.0.1:8000:8000"
environment:
BACKUP_PATH: /data/backups
DATABASE_DIR: /data/db
VIEWER_USERNAME: admin
VIEWER_PASSWORD: your-secure-password
VIEWER_TIMEZONE: Europe/Madrid
volumes:
# SQLite needs write access for WAL files, sessions, audit logs, and thumbnails.
# Use :ro only when the database is PostgreSQL and media is mounted separately.
- /path/to/data:/dataBrowse your backups at http://localhost:8000
All settings are configured via environment variables. Set them in your .env file or as environment: entries in docker-compose.yml. See .env.example for a ready-to-use template.
ENABLE_LISTENERis a master switch. When set tofalse(the default), allLISTEN_*andMASS_OPERATION_*variables have no effect. You only need to configure those when you setENABLE_LISTENER=true.
The Scope column shows whether each variable applies to the backup scheduler (B), the web viewer (V), or both (B/V).
| Variable | Default | Scope | Description |
|---|---|---|---|
| Telegram Credentials | |||
TELEGRAM_API_ID |
required | B | API ID from my.telegram.org |
TELEGRAM_API_HASH |
required | B | API Hash from my.telegram.org |
TELEGRAM_PHONE |
required | B | Phone number with country code (e.g., +1234567890) |
TELEGRAM_PROXY_TYPE |
- | B | Optional proxy type for all Telegram clients. Currently supports socks5 |
TELEGRAM_PROXY_ADDR |
- | B | SOCKS5 proxy host or IP address |
TELEGRAM_PROXY_PORT |
- | B | SOCKS5 proxy port |
TELEGRAM_PROXY_USERNAME |
- | B | Optional SOCKS5 username |
TELEGRAM_PROXY_PASSWORD |
- | B | Optional SOCKS5 password |
TELEGRAM_PROXY_RDNS |
false |
B | Use remote DNS resolution through the SOCKS5 proxy |
| Backup Schedule & Storage | |||
SCHEDULE |
0 */6 * * * |
B | Cron expression for backup frequency |
BACKUP_PATH |
/data/backups |
B/V | Base path for backup data and media |
DOWNLOAD_MEDIA |
true |
B | Download media files (photos, videos, documents) |
MAX_MEDIA_SIZE_MB |
100 |
B | Skip media files larger than this (MB) |
MEDIA_MAX_FILENAME_BYTES |
143 |
B | Usable filename byte budget for downloaded media. Raise to 255 on plain ext4/xfs; keep 143 for Synology/eCryptfs encrypted shares |
MEDIA_MAX_DOWNLOAD_ATTEMPTS |
5 |
B | Stop retrying a file's download after this many failed attempts. Re-requesting the download resets the counter |
PARALLEL_DOWNLOAD_ENABLED |
false |
B | Fetch large files over several connections to lift the single-stream speed cap (see below) |
PARALLEL_DOWNLOAD_MIN_SIZE_MB |
20 |
B | Only files at least this large use the parallel path (min 1) |
PARALLEL_DOWNLOAD_CONNECTIONS |
4 |
B | Concurrent connections per file (clamped 2–8) |
PARALLEL_DOWNLOAD_PART_SIZE_KB |
512 |
B | Chunk size per request; one of 4/8/16/32/64/128/256/512 |
BATCH_SIZE |
100 |
B | Messages processed per database batch |
CHECKPOINT_INTERVAL |
1 |
B | Save backup progress every N batch inserts (lower = safer resume after crash) |
DATABASE_TIMEOUT |
60.0 |
B/V | Database operation timeout in seconds |
SESSION_NAME |
telegram_backup |
B | Telethon session file name |
DEDUPLICATE_MEDIA |
true |
B | Symlink identical media files across chats to save disk space |
SYNC_DELETIONS_EDITS |
false |
B | Batch-check ALL messages for edits/deletions each run (expensive!) |
VERIFY_MEDIA |
false |
B | Re-download missing or corrupted media files |
STATS_CALCULATION_HOUR |
3 |
B | Hour (0-23) to recalculate backup statistics daily |
PRIORITY_CHAT_IDS |
- | B | Comma-separated chat IDs to process first in all operations |
SKIP_MEDIA_CHAT_IDS |
- | B | Skip media downloads for specific chats (messages still backed up with text) |
SKIP_MEDIA_DELETE_EXISTING |
true |
B | Delete existing media files and DB records for chats in skip list to reclaim storage |
SKIP_TOPIC_IDS |
- | B | Skip specific topics in forum supergroups (format: chat_id:topic_id,...) |
LOG_LEVEL |
INFO |
B/V | Logging verbosity: DEBUG, INFO, WARNING/WARN, ERROR |
| Chat Filtering | See Chat Filtering below | ||
CHAT_IDS |
- | B | Whitelist mode: backup ONLY these chats (ignores all other filters) |
CHAT_TYPES |
private,groups,channels |
B | Type-based mode: comma-separated chat types to backup |
GLOBAL_EXCLUDE_CHAT_IDS |
- | B | Exclude specific chats (any type) |
GLOBAL_INCLUDE_CHAT_IDS |
- | B | Force-include specific chats (any type) |
PRIVATE_EXCLUDE_CHAT_IDS |
- | B | Exclude specific private chats |
PRIVATE_INCLUDE_CHAT_IDS |
- | B | Force-include specific private chats |
GROUPS_EXCLUDE_CHAT_IDS |
- | B | Exclude specific groups |
GROUPS_INCLUDE_CHAT_IDS |
- | B | Force-include specific groups |
CHANNELS_EXCLUDE_CHAT_IDS |
- | B | Exclude specific channels |
CHANNELS_INCLUDE_CHAT_IDS |
- | B | Force-include specific channels |
| Real-time Listener | See Real-time Listener below | ||
ENABLE_LISTENER |
false |
B | Master switch — enables all LISTEN_* features below |
LISTEN_EDITS |
true |
B | Apply text edits in real-time |
LISTEN_DELETIONS |
false |
B | Process deletion events from Telegram. Opt-in only |
DELETION_MODE |
hard |
B | When deletions are processed: hard removes archived messages (legacy), soft keeps messages and marks them deleted |
LISTEN_NEW_MESSAGES |
true |
B | Save new messages in real-time between scheduled backups |
LISTEN_NEW_MESSAGES_MEDIA |
false |
B | Also download media immediately (vs. next scheduled backup) |
LISTEN_CHAT_ACTIONS |
true |
B | Track chat photo, title, and member changes |
MASS_OPERATION_THRESHOLD |
10 |
B | Max operations per chat before rate limiting triggers |
MASS_OPERATION_WINDOW_SECONDS |
30 |
B | Sliding window for counting operations (seconds) |
MASS_OPERATION_BUFFER_DELAY |
2.0 |
B | Deprecated compatibility setting; operations are rate-limited, not buffered |
| Database | See Database Configuration below | ||
DATABASE_URL |
- | B/V | Full database URL (highest priority, overrides all below) |
DB_TYPE |
sqlite |
B/V | Database engine: sqlite or postgresql |
DB_PATH |
$BACKUP_PATH/telegram_backup.db |
B/V | Path to SQLite database file |
DATABASE_PATH |
- | B/V | Full path to SQLite file (v2 compatible alias for DB_PATH) |
DATABASE_DIR |
- | B/V | Directory containing telegram_backup.db (v2 compatible) |
POSTGRES_HOST |
localhost |
B/V | PostgreSQL host |
POSTGRES_PORT |
5432 |
B/V | PostgreSQL port |
POSTGRES_USER |
telegram |
B/V | PostgreSQL username |
POSTGRES_PASSWORD |
- | B/V | PostgreSQL password (required when using PostgreSQL) |
POSTGRES_DB |
telegram_backup |
B/V | PostgreSQL database name |
| Viewer & Authentication | |||
VIEWER_USERNAME |
- | V | Master web viewer username |
VIEWER_PASSWORD |
- | V | Master web viewer password |
ALLOW_ANONYMOUS_VIEWER |
false |
V | Explicitly allow unauthenticated local viewer mode. Grants read-only access — browsing/search work, but settings, viewer/token management, and deletions still require the master account |
AUTH_SESSION_DAYS |
30 |
V | Days before re-authentication is required |
AUTH_PROXY_HEADER |
- | V | Header carrying the authenticated username from a trusted reverse proxy (Authelia, Authentik, Keycloak), e.g. Remote-User. See warning below |
AUTH_PROXY_ADMIN_USERS |
- | V | Comma-separated usernames from AUTH_PROXY_HEADER that get the admin (master) role |
AUTH_PROXY_DEFAULT_ACCESS |
none |
V | Default chat access for auto-created proxy users: none or all |
DISPLAY_CHAT_IDS |
- | V | Restrict viewer to specific chats (comma-separated IDs) |
TRUST_PROXY_HEADERS |
false |
V | Trust X-Forwarded-For / X-Real-IP only when your reverse proxy overwrites them |
INTERNAL_PUSH_SECRET |
- | B/V | Shared secret for SQLite backup-to-viewer realtime push over Docker/private networks |
VIEWER_HOST |
localhost |
B | Viewer host for SQLite realtime push from backup/listener |
VIEWER_PORT |
8080 |
B | Viewer port for SQLite realtime push from backup/listener |
VIEWER_TIMEZONE |
Europe/Madrid |
V | Timezone for displayed timestamps (tz database names) |
SHOW_STATS |
true |
V | Show backup statistics dropdown in viewer header |
| Security | |||
CORS_ORIGINS |
* |
V | Allowed CORS origins, comma-separated (e.g., https://my.domain.com). Credentials auto-disabled when * |
SECURE_COOKIES |
auto |
V | Secure flag on auth cookies. Auto-detects from request protocol (X-Forwarded-Proto / scheme). Override with true or false |
| Notifications | |||
PUSH_NOTIFICATIONS |
basic |
V | off = disabled, basic = in-browser only, full = Web Push (works with browser closed) |
VAPID_PRIVATE_KEY |
auto-generated | V | Custom VAPID private key for Web Push |
VAPID_PUBLIC_KEY |
auto-generated | V | Custom VAPID public key for Web Push |
VAPID_CONTACT |
mailto:admin@example.com |
V | Contact email included in Web Push requests |
⚠️ AUTH_PROXY_HEADERrequires a trusted reverse proxy. Your proxy MUST strip or overwrite this header on every inbound request before it reaches the viewer. If it merely passes the header through, any client can set it themselves and impersonate any user — including an admin — with a single request header. This is a full authentication bypass, not a hardening nicety. Only enable this if you've verified your proxy config strips client-supplied values for the header you choose.
There are two modes for selecting which chats to backup:
Mode 1 — Whitelist (simple): set CHAT_IDS to backup only those specific chats. All other filtering variables are ignored.
CHAT_IDS=-1001234567890,-1009876543210 # Only these 2 chats, nothing elseMode 2 — Type-based (default): use CHAT_TYPES to backup all chats of certain types, then fine-tune with include/exclude lists:
# Backup all private chats and groups (no channels)
CHAT_TYPES=private,groups
# Backup all channels except one
CHAT_TYPES=channels
CHANNELS_EXCLUDE_CHAT_IDS=-1001234567890
# Backup groups plus one specific channel
CHAT_TYPES=groups,channels
CHANNELS_INCLUDE_CHAT_IDS=-1001234567890Include variables are allow-lists, not additive overrides.
GLOBAL_INCLUDE_CHAT_IDSlimits all selected types to those IDs; type-specific include variables limit only that type. For the simplest exclusive selection, useCHAT_IDS.
Chat ID format — Telegram uses "marked" IDs:
- Users: positive numbers (
123456789) - Basic groups: negative (
-123456789) - Supergroups/Channels: negative with
-100prefix (-1001234567890)
Find a chat's ID by forwarding a message to @userinfobot.
Topic filtering — For forum-enabled supergroups, you can exclude specific topics without excluding the entire chat using SKIP_TOPIC_IDS:
# Skip topics 42 and 1337 in one chat, and topic 7 in another
SKIP_TOPIC_IDS=-1001234567890:42,-1001234567890:1337,-1009876543210:7Note: The topic-creating service message (1 per topic) may still be backed up since it lacks
reply_tometadata. This does not affect user-generated content.
The scheduled backup only captures new messages. To also track edits and deletions between backups, enable the real-time listener:
ENABLE_LISTENER: "true" # Master switch — required
LISTEN_EDITS: "true" # Track text edits (safe, default: true)
LISTEN_DELETIONS: "false" # Ignore Telegram deletions entirely
DELETION_MODE: "hard" # hard=legacy remove, soft=keep and show "deleted"
LISTEN_NEW_MESSAGES: "true" # Save new messages instantly (default: true)How it works: stays connected to Telegram between scheduled backups, captures changes as they happen, and automatically reconnects if disconnected.
Backup protection: LISTEN_DELETIONS=false is the safe default. Set LISTEN_DELETIONS=true only if you want to process deletion events. With the default DELETION_MODE=hard, deletions mirror Telegram and remove archived messages. Set DELETION_MODE=soft to keep the original archived message and show deleted in the message metadata. Soft-deleted messages are retained in the archive — they remain counted in chat statistics and continue to appear in search and exports, flagged as deleted.
Alternative — batch sync: set SYNC_DELETIONS_EDITS=true to check ALL backed-up messages on each scheduled run. This is expensive and slow, and uses the same DELETION_MODE behavior for deleted messages.
When the listener is enabled and LISTEN_DELETIONS=true, a sliding-window rate limiter limits mass deletion damage:
- Operations under the threshold are applied immediately
- A sliding window tracks operations per chat over
MASS_OPERATION_WINDOW_SECONDS - When
MASS_OPERATION_THRESHOLDis exceeded, remaining operations are blocked for that window
Example: someone deletes 50 messages in 10 seconds with default settings (threshold=10, window=30s) — the first 10 are applied according to DELETION_MODE, remaining 40 are blocked. For zero deletion handling, set LISTEN_DELETIONS=false.
A single Telegram connection caps download throughput at roughly 10 MB/s. With
PARALLEL_DOWNLOAD_ENABLED=true, large files are split into chunks fetched
concurrently over several connections to the file's datacenter and reassembled
on disk, lifting that cap on fast links.
- Default OFF. Enable only if download speed is your bottleneck — most setups are fine on a single stream.
- Large files only. Files below
PARALLEL_DOWNLOAD_MIN_SIZE_MB(default 20 MB) and all photos stay single-stream; chunking overhead isn't worth it for them. - Conservative by design.
PARALLEL_DOWNLOAD_CONNECTIONSis clamped to 2–8 (default 4). Telegram throttles hard past ~20 total connections, so keep this low. Higher values also raise the cost of a rate limit: aFloodWaiton any one connection cancels its siblings and restarts the whole file under the shared retry budget, so under throttling a higher connection count can mean slower overall throughput. If you see frequent flood waits, lower this back toward 4. - FloodWait-aware. Rate limits flow through the same retry budget as normal downloads — no separate backoff scheme.
- Verified reassembly. Each chunk is written at its exact offset and the full byte range is checked for complete, non-overlapping coverage before the file is finalized. Any chunk failure cancels the rest, removes the partial file, and falls back transparently to a single stream.
- Bounded memory. Peak extra memory ≈
CONNECTIONS × PART_SIZE_KB(e.g. 4 × 512 KB ≈ 2 MB), since each connection buffers one chunk in flight.
PARALLEL_DOWNLOAD_PART_SIZE_KB must be one of 4/8/16/32/64/128/256/512 (a 4 KiB
multiple that divides 1 MiB, per Telegram's getFile constraints); invalid values
snap down to the nearest valid size. This feature applies to the scheduled backup
path only, not the real-time listener.
Telegram Archive supports SQLite (default, zero-config) and PostgreSQL (better for large deployments with real-time LISTEN/NOTIFY).
Viewer shows no data? Both backup and viewer containers must access the same database. Ensure
DB_TYPEandDB_PATH(orDATABASE_URL) match in both services.
SQLite path resolution (highest priority first): DATABASE_URL → DATABASE_PATH → DATABASE_DIR → DB_PATH → $BACKUP_PATH/telegram_backup.db
Using PostgreSQL:
- Uncomment the
postgresservice indocker-compose.yml - Set
DB_TYPE=postgresqlandPOSTGRES_PASSWORDin your.env, or use a fullDATABASE_URL - Uncomment
depends_onin both backup and viewer services - Run
docker compose up -d
If you're using the default docker-compose.yml with images from Docker Hub:
# Pull latest images and recreate containers
docker compose pull
docker compose up -dOr in one command:
docker compose up -d --pull alwaysNote: Running
git pullonly updates source code, not Docker images. You must usedocker compose pullto get new container versions.
If you've modified the code or prefer building locally:
git pull
docker build -t drumsergio/telegram-archive:latest .
docker build -t drumsergio/telegram-archive-viewer:latest -f Dockerfile.viewer .
docker compose up -dFor production stability, pin to specific versions instead of latest:
services:
telegram-backup:
image: drumsergio/telegram-archive:7.7.0 # Pin to a reviewed releaseCheck Releases for available versions.
For major version upgrades with breaking changes and migration scripts, see docs/CHANGELOG.md.
Install the package in editable mode to get the telegram-archive command:
# Install in editable mode
pip install -e .
# Now telegram-archive is available system-wide
telegram-archive --help
telegram-archive --data-dir ./data list-chats
telegram-archive --data-dir ./data stats
telegram-archive --data-dir ./data backup
# Export to JSON
telegram-archive --data-dir ./data export -o backup.json -s 2024-01-01 -e 2024-12-31For development without installing, use the telegram-archive executable script:
# Show all available commands
./telegram-archive --help
# Use custom data directory (instead of /data)
./telegram-archive --data-dir ./data list-chats
./telegram-archive --data-dir ./data stats
./telegram-archive --data-dir ./data backup
# Or symlink to PATH for easier access
sudo ln -s $(pwd)/telegram-archive /usr/local/bin/telegram-archive
telegram-archive --data-dir ./data list-chatsAll commands use the unified python -m src interface inside containers:
# Show all available commands
docker compose exec telegram-backup python -m src --help
# View statistics
docker compose exec telegram-backup python -m src stats
# List chats
docker compose exec telegram-backup python -m src list-chats
# Export to JSON
docker compose exec telegram-backup python -m src export -o backup.json
# Export date range
docker compose exec telegram-backup python -m src export -o backup.json -s 2024-01-01 -e 2024-12-31
# Manual backup run (one-time)
docker compose exec telegram-backup python -m src backup
# Re-authenticate (if session expires)
docker compose exec -it telegram-backup python -m src authdata/
├── session/
│ └── telegram_backup.session
└── backups/
├── telegram_backup.db
└── media/
└── {chat_id}/
└── {files}
| Problem | Solution |
|---|---|
| "Failed to authorize" | Run ./init_auth.sh again |
| "Permission denied" | chmod -R 755 data/ |
| Media files missing/corrupted | Set VERIFY_MEDIA=true to re-download them |
| Backup interrupted | Set VERIFY_MEDIA=true once to recover missing files |
| Re-run touches every media file in a git-annex / DataLad backup | See git-annex / DataLad layouts below |
| "duplicate key value violates unique constraint reactions_pkey" | See Reactions Sequence Fix below |
When the media tree is committed to git-annex (or DataLad), files appear
as symlinks pointing into the repository's annex object store. The
backup process treats an existing symlink as authoritative and never
overwrites it on re-run -- but content-hash deduplication only
recognizes existing _shared/ blobs when their symlink targets are
reachable from the running process. If you mount only the working tree
into a container, the annex object store sits outside the mount and is
invisible to the backup.
For fully idempotent re-runs against an annex-managed archive, ensure the annex object store is reachable -- typically by mounting the repository root (not just the per-session subdirectory) and pointing the data path at the session subdirectory inside it.
If you see this error during backup:
duplicate key value violates unique constraint "reactions_pkey"
DETAIL: Key (id)=(XXXX) already exists
Cause: The PostgreSQL sequence for reactions.id got out of sync with the actual data. This commonly occurs after database restores or migrations.
Solutions:
-
Upgrade to v4.1.2+ (recommended) - The code automatically detects and recovers from this issue.
-
Manual fix - Run this SQL command:
docker exec -i <postgres-container> psql -U telegram -d telegram_backup -c \ "SELECT setval('reactions_id_seq', COALESCE((SELECT MAX(id) FROM reactions), 0) + 1, false);"
Or use the provided script:
curl -O https://raw.githubusercontent.com/GeiserX/Telegram-Archive/main/scripts/fix_reactions_sequence.sql docker exec -i <postgres-container> psql -U telegram -d telegram_backup < fix_reactions_sequence.sql
- Secret chats not supported (API limitation)
- Edit history not tracked (only latest version stored; enable
ENABLE_LISTENER=trueto track edits in real-time) - Deleted messages before first backup cannot be recovered
| Project | Type | Description |
|---|---|---|
| telegram-archive-mcp | MCP Server | Query archived messages from AI assistants |
| n8n-nodes-telegram-archive | n8n Node | Workflow automation for Telegram Archive |
- paperless-telegram-bot — Manage Paperless-NGX documents through Telegram
- AskePub — Telegram bot for ePub annotation with GPT-4
- telegram-delay-channel-cloner — Relay messages between channels with configurable delay
- jellyfin-telegram-channel-sync — Sync Jellyfin access with Telegram channel membership
- telegram-slskd-local-bot — Automated music discovery and download via Telegram
This project is made possible by generous supporters: Calvin
GPL-3.0. See LICENSE for details.
Built with Telethon.


