📨

Telegram Forwarder

Mirror any Telegram channel, chat, or forum topic — including the protected ones. Handles 80k+ message channels without running out of memory. Doesn't post duplicates.

Open source · MIT · Built on Telethon · Self-host on any VPS

What it does

Self-hosted mirroring for serious channel operators — backup, redundancy, cross-posting, archival.

Native batch forward, 100/call

Server-side forward_messages at 100 messages per API call, with drop_author=True to strip the "Forwarded from" header. ~50× faster than copy-mode forwarders.

🛡️

Works on protected channels

When a source has "Restrict saving content" enabled, the tool auto-falls back to copy mode — downloads each message, re-uploads to the destination, preserves captions and formatting.

🧠

OOM-safe on huge channels

Streaming iteration with per-batch flush — memory stays flat regardless of channel size. Successfully mirroring an 80,000+ message channel in flat RAM right now.

🔒

Atomic watermarks · no duplicates

Per-pair read-modify-write under an asyncio lock, with regression protection. Concurrent scheduled, bulk, and manual runs can't clobber each other.

🔁

Live edit / delete propagation

When the source channel edits or deletes a message, the mirror is updated within milliseconds via Telethon MessageEdited / MessageDeleted event handlers.

🪐

Forum clone end-to-end

One click creates a new private supergroup with forum=True, mirrors every source topic, and registers a recurring pair per topic. No manual setup.

⏸️

Pause / resume kill switch

One toggle cancels every in-flight job, halts the scheduler, and blocks all triggers until resumed. For when something looks wrong and you need to stop bleeding fast.

🖥️

Web UI included

A Quart-based dashboard for pair management, bulk backfill, live job tracker with per-pair progress bars, watermark repair, and run log. Behind HTTP Basic Auth.

✏️

Per-pair text replacements

Regex find/replace rules per pair, applied to message text and captions before sending. Useful for stripping spam footers, channel mentions, or affiliate links.

Why I built it

The bug that made me give up on existing forwarders.

I needed to mirror a few Telegram channels for backup and cross-posting. The first three open-source forwarders I tried (tgcf, telemirror, and a couple of smaller cloners) all worked for a few hours and then started posting duplicates to the destination.

The cause turned out to be the same in every tool. When more than one runner (a scheduler tick + a manual "run now" click, say) operates on the same pair, both load the full state dict from disk, each holds a stale snapshot, and the slower writer clobbers the faster one's watermark. Next cycle, the lagging key looks fresh again — and the tool happily re-forwards the same messages it forwarded an hour ago.

The fix is mechanical: don't write the whole dict, write only one key, under a lock, with read-modify-write. Refuse to write a value lower than what's on disk unless explicitly told to.

async def save_pair_watermark(name, last_msg_id, updated_at,
                              *, allow_regression=False):
    async with _save_lock:
        state = json.load(open(STATE_PATH)) if STATE_PATH.exists() else {}
        if not allow_regression:
            current = int(state.get(name, {}).get("last_msg_id", 0))
            if last_msg_id < current:
                return  # refuse to roll backwards
        state[name] = {"last_msg_id": last_msg_id, "updated_at": updated_at}
        # atomic .tmp + replace
        tmp = STATE_PATH.with_suffix(".tmp")
        json.dump(state, open(tmp, "w"))
        tmp.replace(STATE_PATH)

That's the whole patch. It killed the duplicates permanently and made me realise the bigger forwarders had this bug too — they just hadn't hit a load high enough to expose it. Once I had a tool that didn't lie about its watermark, I kept adding the other things I wished existed: native batch forward, OOM-safe streaming for huge channels, live edit/delete propagation, a kill switch.

Then I open-sourced it. MIT, single Docker container, runs on a $5 VPS. If you've been chasing duplicate posts in your mirror channels, try it.

How it compares

Against the two most popular open-source Telegram forwarders.

Feature telegram-forwarder tgcf telemirror
Atomic concurrent watermark save
OOM-safe streaming on 80k+ channels
Native batch forward (100/call) partial
drop_author=True on forwards
Copy-mode fallback for protected sources
Live edit / delete propagation
Per-pair regex find/replace partial
Forum clone end-to-end
Web UI
Pause / resume kill switch
Last commit (May 2026) active Dec 2022 active

Credit where it's due — the text-replacement pattern is borrowed from tgcf and the live edit/delete pattern from telemirror. Full attribution in CREDITS.md.

Install

Python 3.11+. Single process (Quart web UI + scheduler share one event loop). Self-hosts on any VPS — needs ~256 MB RAM idle, more during media-heavy copy-mode runs.

Docker

docker build -t tg-forwarder .
docker run -p 5000:5000 \
  -v ./data:/app/data \
  -e TELEGRAM_API_ID=... \
  -e TELEGRAM_API_HASH=... \
  -e DASH_USER=admin \
  -e DASH_PASS=changeme \
  tg-forwarder

Bare metal

git clone https://github.com/apppurchasespro-hash/telegram-forwarder
cd telegram-forwarder
pip install -r requirements.txt
cp config.example.json config.json   # edit
export DASH_USER=admin DASH_PASS=changeme
python server.py

After install, open http://localhost:5000 in your browser. The README has Railway / Fly.io deployment notes if you want a managed PaaS instead of a VPS.

Open source · MIT licensed

If this saved you debugging time, star the repo.

It's how I know what people find useful and what to keep working on. Issues and PRs welcome — bug reports especially.

⭐ Star on GitHub →