Metadata-Version: 2.4
Name: telexporter
Version: 1.0.3
Summary: Forward new emails from IMAP mailboxes to Telegram forum topics in real time.
Author-email: Mhd Zeid Mahfouz <mhd.zeid.mahfouz@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/Zeid-M/telexporter
Project-URL: Issues, https://github.com/Zeid-M/telexporter/issues
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: sqlalchemy==2.0.41
Requires-Dist: aiogram==3.20.0.post0
Requires-Dist: tenacity==9.1.2
Requires-Dist: attrs==25.3.0
Requires-Dist: aioimaplib==2.0.1
Requires-Dist: z-omni-logger==1.3.0
Requires-Dist: dynaconf==3.2.11
Dynamic: license-file

# 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](https://t.me/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

1. Talk to [@BotFather](https://t.me/BotFather), run `/newbot`, and copy the **bot token**.
2. Create a Telegram group, convert it to a supergroup, and enable **Topics** in the group settings.
3. Add your bot to the group and give it permission to send messages (making it an admin is the simplest way).
4. Create one topic per mailbox you want to watch.
5. 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](https://t.me/RawDataBot) or [@getidsbot](https://t.me/getidsbot) can show these values.

## Installation

### Option 1: Docker (recommended)

The image is published to the GitHub Container Registry:

```bash
docker pull ghcr.io/zeid-m/telexporter:latest
```

Using the files in the [`docker/`](docker) directory:

```bash
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`](docker/.env)                                                                                                                                                         |

To build the image yourself:

```bash
./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

```bash
pip install telexporter
```

Then create a config file and point Telexporter at it (see below):

```bash
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

```bash
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](https://www.dynaconf.com/). A documented template lives in [`src/telexporter/config/config.toml`](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`

```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`](https://github.com/Zeid-M/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`:

```toml
[production.logger_config]
logs_directory = "/var/log/telexporter"
refresh_enabled = true
refresh_interval = 900
```

## Security notes

- `config.toml` contains **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 = false` for 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 `INBOX` folder is watched.
- Only IMAP over SSL/TLS is supported.
- Only **unread** messages trigger notifications.
- Telegram forum topics are required (`topic_id` is mandatory).

## Development

```bash
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:

```bash
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](https://github.com/Zeid-M/telexporter/issues).

## License

Released under the [MIT License](LICENSE). Copyright © 2026 Mhd Zeid Mahfouz.
