Telexporter
Get instant Telegram notifications for new emails.
Telexporter watches one or more IMAP mailboxes using IMAP IDLE (push, not polling) and forwards a short summary of each new unread email to a Telegram forum topic. It never marks your mail as read and never downloads message bodies, only the Subject, From and Date headers.
Features
- Multiple accounts: each IMAP account is routed to its own Telegram chat and topic.
- Real-time: uses IMAP IDLE, so notifications arrive as soon as the server pushes them.
- Non-intrusive: fetches headers only, with
BODY.PEEK, so messages stay unread. - No duplicates: alerts are tracked in a local SQLite database, so restarts never re-send old notifications.
- Heartbeat: if an account has been silent for longer than a configurable threshold, a "still alive" message is posted, so you can tell "no mail" from "the watcher is broken".
- Resilient: automatic IMAP reconnection with backoff, and Telegram sends retried with exponential backoff.
- Docker-ready: ships with a Dockerfile and a Compose file.
How it works
IMAP account(s) ──IDLE──▶ Watcher ──▶ asyncio.Queue ──▶ Notifier ──▶ Telegram topic
│
SQLite (dedup)
▲
Heartbeat task (per account)
Each notification looks like this:
UID: 1234
2026-09-30T08:15:00+00:00
Subject
Your invoice is ready
From: (billing@example.com)
Requirements
- Python 3.12+ (or Docker)
- A Telegram bot token from @BotFather
- A Telegram supergroup with Topics enabled, with your bot added to it and allowed to post
- IMAP access (over SSL/TLS) to the mailbox(es) you want to watch. Many providers require an app-specific password.
Telegram setup
- Talk to @BotFather, run
/newbot, and copy the bot token. - Create a Telegram group, convert it to a supergroup, and enable Topics in the group settings.
- Add your bot to the group and give it permission to send messages (making it an admin is the simplest way).
- Create one topic per mailbox you want to watch.
- Find the chat ID (supergroup IDs are negative, e.g.
-1001234567890) and the topic ID (the ID of the topic's first message / thread). Bots such as @RawDataBot or @getidsbot can show these values.
Installation
Option 1: Docker (recommended)
The image is published to the GitHub Container Registry:
docker pull ghcr.io/zeid-m/telexporter:latest
Using the files in the docker/ directory:
git clone https://github.com/Zeid-M/telexporter.git
cd telexporter/docker
# 1. Start once so the default configuration is copied to /etc/telexporter on the host
docker compose up -d
docker compose down
# 2. Edit your configuration (see "Configuration" below)
sudo nano /etc/telexporter/config.toml
# 3. Start for real
docker compose up -d
How the Compose setup is wired:
| What | Where |
|---|---|
| Configuration | Host /etc/telexporter is mounted at the container's TELEXPORTER_ROOT_PATH_FOR_DYNACONF (/etc/telexporter). On first start, if the folder is empty, the default config is copied into it. |
| SQLite state | Named volume telexporter_sqlite_data at /code/db (database file: /code/db/alerts.db). |
| Restart policy | unless-stopped |
| Environment | Read from docker/.env |
To build the image yourself:
./scripts/build_registry_image.sh
The Dockerfile installs Telexporter from PyPI (
pip install telexporter), so it builds whatever version is published there.
Option 2: pip
pip install telexporter
Then create a config file and point Telexporter at it (see below):
mkdir -p ~/.config/telexporter
# create ~/.config/telexporter/config.toml (see the "Configuration" section)
export TELEXPORTER_ROOT_PATH_FOR_DYNACONF=$HOME/.config/telexporter
export TELEXPORTER_SQLITE_DB_PATH=$HOME/.config/telexporter/alerts.db
start_telexporter
Option 3: From source
git clone https://github.com/Zeid-M/telexporter.git
cd telexporter
pip install -e .
Configuration
Telexporter is configured with a single TOML file, config.toml, loaded with Dynaconf. A documented template lives in src/telexporter/config/config.toml.
Environment variables
| Variable | Required | Description |
|---|---|---|
TELEXPORTER_ROOT_PATH_FOR_DYNACONF |
yes | Directory containing config.toml. |
TELEXPORTER_SQLITE_DB_PATH |
yes | Path of the SQLite file used to remember which emails were already sent. The parent directory must exist. |
OMNILOGGER_ROOT_PATH_FOR_DYNACONF |
for logging config | Directory containing the logger configuration (logger_config.json, logger_settings.toml). Used by the Docker image (/etc/omni_logger). |
Any setting can also be overridden through environment variables with the TELEXPORTER_ prefix, following Dynaconf conventions (for example TELEXPORTER_TELEGRAM__BOT_TOKEN).
Example config.toml
[[accounts]]
name = "Personal"
host = "imap.mail1.com"
user = "alice@example.com"
password = "app-specific-password"
verify_ssl = true
backoff_interval = 15
idle_timeout = 1800
chat_id = -1001234567890
topic_id = 2
heartbeat_threshold = 10800 # 3 hours
[[accounts]]
name = "Work"
host = "imap.mail2.com"
user = "bob@work.com"
password = "another-password"
verify_ssl = false # e.g. self-signed certificate
backoff_interval = 15
idle_timeout = 1800
chat_id = -1009876543210
topic_id = 1
heartbeat_threshold = 10800
[telegram]
bot_token = "123456:ABC-your-token"
retry_attempts = 10
wait_multiplier = 3
wait_max_time = 60
heartbeat_check_interval = 60
[system]
queue_maxsize = 300
Account options ([[accounts]])
Add one table per mailbox. All fields are required.
| Field | Type | Description |
|---|---|---|
name |
string | Label used in logs and heartbeat messages. |
host |
string | IMAP server hostname (connects over SSL). |
user |
string | IMAP username, usually the full email address. |
password |
string | IMAP password or app-specific password. |
verify_ssl |
bool | true verifies certificates. false disables hostname and certificate checks (use only for self-signed servers). |
backoff_interval |
int (s) | Wait time before reconnecting after an IMAP or network error. |
idle_timeout |
int (s) | How long each IDLE session lasts before it is renewed. Keep it below your server's IDLE limit (commonly 29 min, hence 1800 or less). |
chat_id |
int | Telegram chat / supergroup ID (negative for supergroups). |
topic_id |
int | Forum topic ID that receives this account's notifications. |
heartbeat_threshold |
int (s) | Silence duration after which a heartbeat message is sent. |
[telegram]
| Field | Description |
|---|---|
bot_token |
Bot token from @BotFather. |
retry_attempts |
Maximum send attempts per notification. |
wait_multiplier |
Base seconds for randomized exponential backoff between retries. |
wait_max_time |
Maximum backoff delay in seconds. |
heartbeat_check_interval |
Seconds between heartbeat checks. |
[system]
| Field | Description |
|---|---|
queue_maxsize |
Maximum size of the internal alert queue. |
The configuration is validated at startup, and Telexporter exits with a descriptive error if anything is missing or has the wrong type.
Heartbeat behavior
The heartbeat task checks every heartbeat_check_interval seconds. It posts a message such as:
🟢 [Personal] no new emails — still alive!
when the last recorded event for the account (an email alert or a previous heartbeat) is older than heartbeat_threshold. On the very first run, with nothing recorded yet, one heartbeat is sent immediately. Heartbeats are recorded, so they repeat at most once per threshold period.
Logging
Logging is provided by omni-logger. By default logs are written to /var/log/telexporter and the logger configuration is refreshed every 15 minutes. Both are adjustable in logger_settings.toml:
[production.logger_config]
logs_directory = "/var/log/telexporter"
refresh_enabled = true
refresh_interval = 900
Security notes
config.tomlcontains plaintext credentials (IMAP passwords and the bot token). Restrict its permissions (chmod 600) and never commit it.- Use app-specific passwords where your provider supports them.
- Only set
verify_ssl = falsefor servers you trust; it disables protection against man-in-the-middle attacks. - Notifications contain the sender and subject of your emails, so make sure only the right people are in the Telegram group.
Limitations
- Only the
INBOXfolder is watched. - Only IMAP over SSL/TLS is supported.
- Only unread messages trigger notifications.
- Telegram forum topics are required (
topic_idis mandatory).
Development
git clone https://github.com/Zeid-M/telexporter.git
cd telexporter
python -m venv .venv && source .venv/bin/activate
pip install -e .
export TELEXPORTER_ROOT_PATH_FOR_DYNACONF=./src/telexporter/config
export TELEXPORTER_SQLITE_DB_PATH=./alerts.db
start_telexporter
Build and publish the package:
python -m pip install build twine
python -m build
twine upload dist/*
Contributing
Issues and pull requests are welcome at github.com/Zeid-M/telexporter/issues.
License
Released under the MIT License. Copyright © 2026 Mhd Zeid Mahfouz.
Metadata
Release files for telexporter 1.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| telexporter-1.0.3.tar.gz | 21.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| telexporter-1.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.8 kB
Release files / telexporter-1.0.3.tar.gz
| Download URL | telexporter-1.0.3.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b58dec94985a3725e9fab3588bbd2b4f14051ab368c15733c200c964d06b6d05
|
|
BLAKE2b-256 checksum How to use checksums |
8233563471efd73234e8124f42d4009ad4057c6ac4d52c3f82f1769d98be11b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / telexporter-1.0.3-py3-none-any.whl
| Download URL | telexporter-1.0.3-py3-none-any.whl |
|---|---|
| Size | 19.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b01a2858c4c6cb081bc3c131d11efd13a115d1ee0d334784f1cdbe302cd6a54
|
|
BLAKE2b-256 checksum How to use checksums |
9b341eb97d7b629146b3528bf754ed94a8c8f61321c22014d9eda9e412352777
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|