Email → ticket → Telegram support-desk automation.
Portfolio demonstration — a clean, generic reimplementation of a support-desk automation I built for a client. It contains no client data: every mailbox, address, and token here is a placeholder (
example.com,CHANGE_ME). Bring your own configuration via.env.
Mail-Ops Bot watches one or more IMAP mailboxes in near-real-time, turns inbound mail into tickets, tracks first-response SLA against your business hours, and posts events to a Telegram chat. It also produces a daily PDF dashboard. It is deliberately dependency-light and runs as a single long-lived process.
- Multi-account IMAP ingestion — connect to several mailboxes at once, each on its own worker thread.
- Near-real-time delivery via IMAP
IDLE— react to new mail without polling; re-issue IDLE before servers time out; reconnect with backoff. - Per-account UID cursors — durable, resumable, and reset automatically when
the server's
UIDVALIDITYchanges. - MIME parsing — decode encoded headers/bodies, normalize
Re:/Fwd:subjects, extract reply targets and@mentions, thread by normalized subject andReferences. - Ticket state machine — open/answer/close transitions, first-response-time tracking, Sent-folder watching to auto-mark answered, and JSON persistence with a forward-only schema-migration helper.
- Work-hours / SLA gating — SLA timers only advance during configured business hours and workdays; compute the next working moment; escalate overdue tickets.
- Telegram notifications — new-ticket, SLA-breach, and daily-summary messages plus the PDF as a document.
- PDF dashboard — daily tickets opened/closed, average response time, and a per-mailbox breakdown rendered with matplotlib.
- Clean concurrency — one worker per account + a maintenance worker, with signal-driven graceful shutdown.
flowchart LR
subgraph Mailboxes
M1[(support@example.com)]
M2[(billing@example.com)]
end
M1 -->|IMAP IDLE| W1[AccountWorker]
M2 -->|IMAP IDLE| W2[AccountWorker]
W1 --> P[MIME parse]
W2 --> P
P --> TS[(TicketStore\nJSON state)]
TS --> SLA[SLA engine\nwork-hours + maintenance]
SLA -->|new / overdue / summary| TG[[Telegram Bot API]]
SLA -->|daily| PDF[[matplotlib PDF]]
PDF --> TG
See docs/architecture.md for the component and
concurrency notes.
| Area | Choice |
|---|---|
| Language | Python 3.11+ (type hints, dataclasses, zoneinfo) |
imaplib + ssl with the certifi CA bundle, RFC 2177 IDLE |
|
| Notifications | Telegram Bot API via requests |
| Reporting | matplotlib (headless Agg backend) |
| Config | python-dotenv + environment variables |
| Persistence | Atomic JSON files (no external database) |
mail-ops-bot/
├── README.md
├── LICENSE
├── .gitignore
├── .env.example
├── requirements.txt
├── config.py # env-driven settings (dataclasses)
├── mailops/
│ ├── __init__.py
│ ├── __main__.py # entrypoint: spins up workers
│ ├── imap_client.py # connect, select, IDLE reader, fetch
│ ├── mime.py # decode headers, normalize subject, mentions
│ ├── tickets.py # Ticket + TicketStore (JSON + migration)
│ ├── workhours.py # is_workday, next_work_time, SLA helpers
│ ├── telegram.py # Telegram Bot API client
│ ├── dashboard.py # matplotlib PDF report
│ └── state.py # atomic JSON state with dirty-tracking
└── docs/
└── architecture.md
# 1. Install dependencies (use a virtualenv)
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 2. Create your configuration from the template
cp .env.example .env
# …then edit .env: set your IMAP accounts, Telegram token/chat, work hours.
# 3. Run the service
python -m mailopsThe process runs until interrupted (Ctrl+C), flushing state on shutdown.
State files are written under MAILOPS_STATE_DIR and PDFs under
MAILOPS_REPORT_DIR (both gitignored).
All configuration is environment-driven. See .env.example for
the full annotated template.
| Variable | Description | Example |
|---|---|---|
MAILOPS_ACCOUNTS |
JSON array of IMAP accounts (host, port, username, password, inbox, sent). | [{"name":"support","host":"imap.example.com",…}] |
MAILOPS_ACCOUNT_N_* |
Numbered fallback vars when MAILOPS_ACCOUNTS is unset. |
MAILOPS_ACCOUNT_1_HOST=imap.example.com |
TELEGRAM_BOT_TOKEN |
Bot token from @BotFather. | CHANGE_ME |
TELEGRAM_CHAT_ID |
Destination chat/channel id. | CHANGE_ME |
TELEGRAM_ENABLED |
Set false for a dry run (no messages sent). |
true |
WORKHOURS_TIMEZONE |
IANA timezone for SLA math. | Europe/Berlin |
WORKHOURS_START / WORKHOURS_END |
Daily business-hours window (HH:MM). |
09:00 / 18:00 |
WORKHOURS_WORKDAYS |
ISO weekdays that count (Mon=1…Sun=7). | 1,2,3,4,5 |
SLA_FIRST_RESPONSE_MINUTES |
First-response SLA in business minutes. | 120 |
TICKET_AUTO_CLOSE_HOURS |
Auto-close answered tickets after N business hours idle. | 48 |
TICKET_RETENTION_DAYS |
Purge closed tickets after N days. | 30 |
IMAP_IDLE_TIMEOUT_SECONDS |
Re-issue IDLE before the server drops it. | 1500 |
MAINTENANCE_INTERVAL_SECONDS |
Maintenance loop cadence. | 300 |
MAILOPS_STATE_DIR / MAILOPS_REPORT_DIR |
Output directories. | ./state / ./reports |
LOG_LEVEL |
DEBUG / INFO / WARNING / ERROR. |
INFO |
MIT © 2026 Egor Fomenko