Documentation menu
Getting started
New in v2.0
Feature deep dives
Reference
Architecture
Architecture
How AutoTelegramForward is put together: 4 services, a 78-method gRPC contract, the ingest-to-delivery pipeline, ordered lanes and the durable queue.
Verified against the checkout built for v2.2.4 · updated 20 Oct 2026
Four services, one contract, one isolation boundary. The split exists for a reason: the Python core owns the Telegram conversation, the Go API owns trust, the logger owns history, and the web client owns presentation. Each can be replaced without touching the others.
Multi-Service Resilient Forwarding Engine
Decoupled microservice topology with asynchronous non-blocking event loops, lease-based durable queues, and fail-closed tenant boundaries.
Telegram MTProto
Asynchronous client pool connects to DC2/DC4 via user sessions and bot tokens.
• Album Aggregator
• FloodWait Detector
Python Core (50051)
Strict stage-by-stage pipeline executing domain filtering, cleaning & shortcodes.
• [PIPELINE:ROUTE]
• [PIPELINE:FILTER]
• [PIPELINE:TRANSFORM]
Lease Workers
Lease-based jobs with exponential backoff, jitter, and automatic account resume.
• Restricted bypass
• [PIPELINE:DELIVERED]
Go API & Web (8080/8088)
High-performance REST API, interactive Telegram Bot UI, and React dashboard.
• Bot Manager FSM
• Logger (50052)
Services
atf-core
50051 (gRPC)Data planePython core: MTProto sessions, the message pipeline, the durable queue and all persistence.
- ClientPool keeps user and bot sessions alive, reconnects, and parks an account on FloodWait.
- Content pipeline: normalize → route → filter → clean → transform → (optional) rewrite.
- DurableQueue with leases, retries with jittered backoff, dead-letter capture.
- Album aggregation inside a debounce window, so a media group is one job.
atf-api
8080 (HTTP)Control planeGo service that owns authentication, tenancy, task CRUD, log queries, DLQ control and admin.
- Registers 102 routes across system, auth, control-plane and admin groups.
- Resolves identity once, then scopes every downstream gRPC call to that identity.
- Mirrors every request — success and failure — into the logger with actor and correlation id.
- Exposes readiness that answers in booleans, never configuration values.
atf-logger
50052 (gRPC)ObservabilityStructured log and audit store, kept off the delivery path so logging never slows a forward.
- Stores attributable history: actor, service, level, subject task, correlation id.
- Backs the dashboard log explorer, atf logs queries and the export endpoints.
- Queries are paged and tenant-filtered server-side.
atf-web
8088 (HTTP)ClientReact dashboard: tasks, per-task rules, sessions, AI configs, log explorer and queue control.
- Talks only to the REST API — no direct database access.
- Per-task rule editors render from the settings schema the API publishes.
- Live log stream, delivery statistics, dead-letter inspection and replay.
Message pipeline
Ten stages, strictly ordered, each producing a log line you can query later. A message that stops early stops with a reason — never silently.
- 1
INGEST
A Telegram update arrives over MTProto and is normalized into one internal shape (UTF-16 handled, album fragments buffered).
- 2
ROUTE
The task list is resolved for the source chat and owner. A task that is paused or disabled is skipped here, not halfway through.
- 3
AUTHORIZE
Ownership and subscription state are checked before any work is performed on the message.
- 4
FILTER
Media-type gates, link rules, whitelist and blacklist — Unicode-folded, word-boundary keyword matching with ReDoS-refused patterns.
- 5
CLEAN
URLs, mentions, hashtags, phone numbers, emojis and custom word lists are stripped deterministically in a single pass.
- 6
TRANSFORM
Replacements, then the header and footer with shortcode interpolation resolved per delivery.
- 7
REWRITE
Optional AI pass for this task only. Provider failure is non-fatal: the transformed text is delivered as-is.
- 8
QUEUE
The delivery job is persisted with a lease, then handed to a worker. Nothing is delivered straight from the event handler.
- 9
DELIVER
MTProto send, album as one atomic group, with copy-protection fallback to download-and-re-upload.
- 10
REPORT
Outcome is recorded: SUCCEEDED, FAILED (retry), or OUTCOME_UNCONFIRMED when Telegram did not confirm.
Routing resolution
A task holds a list of sources and a list of targets. The first entry of each list is mirrored into the legacy scalar column so older code paths keep working, and the sanctioned write path keeps both in step. Overrides are additive only: a per-target rule can add to the task policy, never silently replace it.
Sources (M)
Targets (N)
Queue & durability
| Property | Behaviour | Why it matters |
|---|---|---|
| Persistence | Jobs live in SQLite or PostgreSQL, not in memory | A restart does not lose accepted work |
| Leases | A worker holds a job under a lease while delivering | A crashed worker's job is reclaimed, not stranded |
| Retries | Transient errors retry with jittered exponential backoff | Ordered backoff instead of a retry storm |
| Dead-letter | Jobs past their retry budget land in the DLQ with the last error | Failures are inspectable, replayable and purgable |
| Idempotency | MessageMap links source message to delivered message | A reclaim cannot double-post |
| Unconfirmed | OUTCOME_UNCONFIRMED is a first-class state | "Maybe delivered" is never reported as success |
Ordering
Without help, a queue that runs jobs in parallel reorders a burst: post 3 finishes before post 1 and your mirror reads backwards. Ordered lanes serialize per task, so the sequence a channel published is the sequence the target shows, while different tasks still run concurrently.
Per-task lane
Jobs of one task never overtake each other.
Parallel across tasks
Throughput scales with the number of tasks, not the number of posts.
Edits keep their place
An edited post updates in order instead of jumping to the end.
Isolation boundary
The boundary sits in the API, not in the client. Identity is resolved once from a verified token, attached to the request, and every storage operation below that point is scoped by it. A caller who is not linked to Telegram resolves to id 0, which owns nothing — the failure direction is closed, never open.
Read the security model