Offline recovery and transcript export for the native Telegram for macOS app.
Built for recovery situations where the only remaining copy of a Telegram
conversation is the encrypted local cache on a Mac. The tool decrypts the local
db_sqlite, understands Telegram's Postbox storage well enough to find peers
and messages, then writes a readable transcript in HTML, Markdown, or CSV.
This targets the native Telegram for macOS app from
macos.telegram.org or the Homebrew telegram
cask. It does not target the cross-platform Telegram Desktop/Qt app, iOS
backups, Android backups, the Mac App Store version, or Telegram's cloud export
format.
Quick links: π Quick Start Β· π How It Works Β· π§ͺ Usage Β· π§° CLI Reference Β· π©Ί Troubleshooting Β· π Credits
- π Overview
- β¨ Capabilities
- π How It Works
- β Requirements
- π Quick Start
- π§ͺ Usage
- π§° CLI Reference
- π Output Formats
- πΊοΈ Key Paths
- π Safety and Privacy
- π©Ί Troubleshooting
β οΈ Limitations- β FAQ
- π Versioning
- π§Ή Quality Checks
- ποΈ Project Structure
- π Credits
- π€ Contributing
- π License
Telegram for macOS stores local message data in an encrypted SQLite database. When a chat has been deleted from Telegram's cloud state, the local cache can be the last useful source of evidence, provided it still exists on disk and has not been overwritten or synced away.
telegram-message-exporter provides a focused recovery path:
- decrypt the local SQLCipher database using
.tempkeyEncrypted - inspect the resulting plaintext SQLite copy
- list likely peers and contacts
- export one chat or all decoded messages to a clean transcript
The tool is intentionally offline. It does not call Telegram APIs, restore messages back into Telegram, or upload recovered data anywhere.
- Stop using Telegram on the Mac as soon as possible.
- Keep the Mac offline if you are trying to preserve a recently deleted chat.
- Copy the Telegram key and database to a separate working directory before experimenting.
- Install the tool in a virtual environment.
- Decrypt to
plaintext.db. - Run
list-peersto find the chat you care about. - Export HTML first for reading, then CSV if you need analysis in a spreadsheet.
- Store
plaintext.dband transcript files securely; they contain private message content.
- Offline decryption: derives SQLCipher keys from Telegram's local
.tempkeyEncryptedfile - Passcode support: accepts
--passcodeorTG_LOCAL_PASSCODEwhen Telegram Passcode Lock is enabled - Postbox parsing: handles the native macOS app's key/value Postbox tables
- Peer discovery: searches local peer records so exports can use names instead of raw IDs where possible
- Targeted exports: filters by peer ID, contact name, message limit, start date, and end date
- Readable output: writes styled HTML, Markdown, or CSV with timestamps, speakers, directions, and link handling
- Diagnostics: samples tables and rows when Telegram storage changes or a cache does not match the expected shape
The normal recovery flow has two phases: decrypt the database, then export from the plaintext copy.
flowchart TD
A[Native Telegram for macOS data] --> B[.tempkeyEncrypted]
A --> C[account-*/postbox/db/db_sqlite]
B --> D[telegram-exporter decrypt]
C --> D
D --> E[plaintext.db]
E --> F[list-peers]
E --> G[diagnose]
F --> H[export --peer-id or --contact]
G --> H
H --> I[HTML transcript]
H --> J[Markdown transcript]
H --> K[CSV dataset]
The CLI never needs Telegram network access. Everything comes from local files that already exist on the Mac.
sequenceDiagram
participant User
participant CLI as telegram-exporter
participant Key as .tempkeyEncrypted
participant DB as db_sqlite
participant Plain as plaintext.db
participant Out as transcript file
User->>CLI: decrypt --key --db --out plaintext.db
CLI->>Key: derive local key candidates
CLI->>DB: open encrypted SQLCipher database
CLI->>Plain: write plaintext SQLite copy
User->>CLI: list-peers --db plaintext.db
CLI->>Plain: inspect peer records
User->>CLI: export --db plaintext.db --peer-id ...
CLI->>Plain: parse messages and peer map
CLI->>Out: render HTML, Markdown, or CSV
- macOS with native Telegram for macOS data present
- Python 3.10 or newer
- SQLCipher support for the
sqlcipher3Python package - Telegram local passcode, if Passcode Lock was enabled
- A virtual environment for local installs
On macOS, install SQLCipher first if sqlcipher3 fails to build:
brew install sqlcipherFrom a clone:
git clone https://github.com/soakes/telegram-message-exporter.git
cd telegram-message-exporter
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Or install the latest revision directly from GitHub:
pip install -U "git+https://github.com/soakes/telegram-message-exporter.git"The native macOS app normally stores its data below:
TELEGRAM_STABLE="$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable"
ls -la "$TELEGRAM_STABLE"
ls "$TELEGRAM_STABLE"/account-*/postbox/db/db_sqliteYou need:
.tempkeyEncrypted, which is hidden from plainlsbecause it starts with.- the matching
account-*/postbox/db/db_sqlite
telegram-exporter decrypt \
--key "$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable/.tempkeyEncrypted" \
--db "$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable/account-123456/postbox/db/db_sqlite" \
--out recovery/plaintext.dbIf Telegram Passcode Lock is enabled:
TG_LOCAL_PASSCODE="your-passcode" \
telegram-exporter decrypt \
--key "$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable/.tempkeyEncrypted" \
--db "$HOME/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable/account-123456/postbox/db/db_sqlite" \
--out recovery/plaintext.dbtelegram-exporter list-peers --db recovery/plaintext.db --search "Alex"telegram-exporter export \
--db recovery/plaintext.db \
--peer-id 123456789 \
--me-name "Me" \
--format html \
--out recovery/alex.htmltelegram-exporter export \
--db recovery/plaintext.db \
--peer-id 123456789 \
--format html \
--me-name "Me" \
--out recovery/chat.htmltelegram-exporter export \
--db recovery/plaintext.db \
--contact "Alex" \
--format md \
--out recovery/alex.mdIf more than one peer matches, the command prints the candidates and asks you to
rerun with --peer-id.
telegram-exporter export \
--db recovery/plaintext.db \
--peer-id 123456789 \
--start-date 2024-01-01 \
--end-date 2024-12-31 \
--format html \
--out recovery/chat-2024.htmltelegram-exporter export \
--db recovery/plaintext.db \
--format csv \
--out recovery/all-chats.csvtelegram-exporter diagnose --db recovery/plaintext.dbSample a specific table:
telegram-exporter diagnose --db recovery/plaintext.db --table t7telegram-exporter decrypt \
--key /path/to/.tempkeyEncrypted \
--db /path/to/db_sqlite \
--out recovery/plaintext.db \
--debug| Command | Purpose |
|---|---|
decrypt |
Decrypt Telegram's encrypted db_sqlite to a plaintext SQLite file |
diagnose |
List tables, columns, and sample rows from a plaintext database |
list-peers |
Find likely peer IDs by name fragment |
export |
Render messages to HTML, Markdown, or CSV |
| Flag | Required | Description |
|---|---|---|
--key |
yes | Path to .tempkeyEncrypted |
--db |
yes | Path to encrypted db_sqlite |
--out |
no | Output plaintext DB path, default plaintext.db |
--passcode |
no | Telegram local passcode; can also use TG_LOCAL_PASSCODE |
--debug |
no | Print key/profile diagnostics |
| Flag | Required | Description |
|---|---|---|
--db |
yes | Path to plaintext SQLite database |
--table |
no | Table to sample; defaults to t7 when present |
| Flag | Required | Description |
|---|---|---|
--db |
yes | Path to plaintext SQLite database |
--search |
no | Name fragment to filter peers |
| Flag | Required | Description |
|---|---|---|
--db |
yes | Path to plaintext SQLite database |
--contact |
no | Contact name to resolve to a peer |
--peer-id |
no | Numeric peer ID to export |
--table |
no | Override detected message table |
--limit |
no | Maximum number of messages |
--start-date |
no | Start date, YYYY-MM-DD or ISO datetime |
--end-date |
no | End date, YYYY-MM-DD or ISO datetime |
--format |
no | html, md, or csv; default md |
--out |
no | Output path; defaults to chat_export.<format> |
--me-name |
no | Display label for outgoing messages; default Me |
--show-direction |
no | Append (in) or (out) labels in Markdown |
| Format | Best for | Notes |
|---|---|---|
html |
Reading and sharing a polished transcript | Includes summary cards, date jump, back-to-top, and link handling |
md |
Archival text, notes, version control | Compact, portable, and easy to diff |
csv |
Analysis in spreadsheets or scripts | Includes date, time, Unix timestamp, direction, speaker, text, peer ID, and author ID |
# Telegram Chat History: Alex Example
**Exported:** 2026-02-04 16:05:12
**Total Messages:** 418
---
## Wednesday, February 04, 2026
**14:13:09 β Me**
3h48 is good alsodate,time,timestamp,direction,speaker,text,peer_id,author_id
2026-02-04,14:13:09,1770214389,out,Me,"3h48 is good also",123456789,123456789Native Telegram for macOS typically stores recovery-relevant files here:
~/Library/Group Containers/6N38VWS5BX.ru.keepcoder.Telegram/stable/
βββ .tempkeyEncrypted
βββ account-*/
βββ postbox/
βββ db/
βββ db_sqlite
The account-* directory must match the database you are decrypting. If there
are multiple accounts, try list-peers after decrypting each one and keep notes
about which plaintext database came from which account directory.
- Work from copies of Telegram files whenever possible.
- Keep the Mac offline during time-sensitive recovery to avoid sync changes.
- Treat
plaintext.db, HTML, Markdown, and CSV outputs as sensitive private data. - Delete or encrypt intermediate files when recovery work is complete.
- The tool reads local files and writes local outputs only; it does not contact Telegram or any third-party recovery service.
| Symptom | What to check |
|---|---|
Key file not found |
Confirm the .tempkeyEncrypted path and quote paths containing spaces |
Database file not found |
Confirm the selected account-* directory has postbox/db/db_sqlite |
Failed to decrypt database |
Verify the key and DB belong to the same Telegram account; pass --passcode if Passcode Lock was enabled |
file is not a database |
Usually a mismatched key/database pair or an unsupported SQLCipher profile |
No peer records found |
Run diagnose and confirm this is the native macOS Telegram database |
No messages found with the current filters |
Remove date filters, confirm --peer-id, or try exporting all decoded messages |
sqlcipher3 install fails |
Install SQLCipher with Homebrew, then reinstall the package in a clean virtual environment |
For decryption problems, rerun with --debug:
telegram-exporter decrypt \
--key /path/to/.tempkeyEncrypted \
--db /path/to/db_sqlite \
--out recovery/plaintext.db \
--debug- Does not restore messages into Telegram.
- Does not bypass a Telegram local passcode; you need the passcode.
- Does not recover messages that no longer exist in the local cache.
- Does not download content from Telegram servers.
- Does not currently extract media files from Telegram's file cache.
- Some newer or uncommon Telegram message payloads may only partially decode.
- Does not support Telegram Desktop/Qt, the Mac App Store version, mobile backups, or Telegram cloud export archives.
No. It exports a transcript from local data; it does not write messages back to Telegram.
The command can read it, but for recovery work it is safer to copy the key and database into a separate working directory and decrypt from that copy.
The native macOS app uses a different storage layout from Telegram Desktop/Qt and mobile clients. This project is built around the native app's local Postbox/SQLCipher data.
No. This currently targets the direct download/Homebrew build from macos.telegram.org. The Mac App Store version uses a different app packaging and storage setup, so it is outside the supported recovery path.
Not currently. The exporter focuses on decoded message text and transcript metadata.
The canonical version is stored in VERSION and exposed by the CLI:
telegram-exporter --versionUse the helper script when preparing a version bump:
.github/scripts/bump_version.py patch
.github/scripts/bump_version.py minor
.github/scripts/bump_version.py major
.github/scripts/bump_version.py --set 1.2.3The CI workflow runs Black, Ruff, and Pylint across Python 3.10 through 3.13.
pip install black ruff pylint
black --check src/telegram_message_exporter telegram_exporter.py .github/scripts/bump_version.py
ruff check src/telegram_message_exporter telegram_exporter.py .github/scripts/bump_version.py
pylint src/telegram_message_exporter telegram_exporter.pytelegram-message-exporter/
βββ .github/
β βββ dependabot.yml # Dependency update schedule
β βββ scripts/
β β βββ bump_version.py # Version helper
β βββ workflows/
β βββ ci.yml # Python lint matrix
βββ pyproject.toml # Packaging metadata and CLI entrypoint
βββ telegram_exporter.py # Convenience wrapper for source checkouts
βββ src/
β βββ telegram_message_exporter/
β βββ __init__.py # Version metadata
β βββ __main__.py # python -m entrypoint
β βββ cli.py # Argument parsing and commands
β βββ crypto.py # SQLCipher and tempkey handling
β βββ db.py # DB heuristics and message extraction
β βββ exporters.py # HTML, Markdown, and CSV renderers
β βββ hashing.py # MurmurHash helper
β βββ models.py # Message data model
β βββ postbox.py # Postbox parsing utilities
β βββ utils.py # Date parsing and link helpers
βββ requirements.txt # Runtime dependencies
βββ VERSION # Canonical package version
βββ LICENSE
βββ README.md
This project builds on community reverse-engineering work. Special thanks to @stek29 for the original research and reference implementation of Telegram for macOS local key format and Postbox structure.
Issues and pull requests are welcome, especially for:
- additional Telegram for macOS storage variants
- safer recovery workflows
- better Postbox decoding
- export formatting improvements
- tests around real-world cache shapes
Please keep recovery and privacy in mind when sharing examples. Do not attach private Telegram databases or transcripts to public issues.
This project is licensed under the MIT License. See LICENSE for
details.