CDN and media delivery
A media store that uses Telegram (MTProto) as its storage backend.
Dev-storyChallenge
We wanted somewhere to keep and serve product media without paying for object storage or a CDN per project.
What we did
A backend that uploads files to Telegram over the MTProto protocol (using it as free storage) and a Vue admin to organise and hand out the media; delivery is fronted so files load quickly.
Result
Media lives on Telegram's infrastructure at no storage cost, and apps get and manage files through one admin instead of a paid bucket.
Dev-story article
CDN and media delivery: how the project was built
Telegram will hold unlimited files for free, but it is not a CDN: it throttles with FLOOD_WAIT, has no public serving model, and knows nothing about browser CORS or caching. The tension of this project was turning a chat backend into something a real frontend can hammer with 400+ image requests per page without getting the account rate-limited or leaking one user's files to another. Most of the work is the edge that sits in front of MTProto, not the storage itself.
Sections
05
Modules
05
Stack
JavaScript + Vue
Why the project exists
We wanted somewhere to keep and serve product media without paying for object storage or a CDN per project.
Telegram will hold unlimited files for free, but it is not a CDN: it throttles with FLOOD_WAIT, has no public serving model, and knows nothing about browser CORS or caching. The tension of this project was turning a chat backend into something a real frontend can hammer with 400+ image requests per page without getting the account rate-limited or leaking one user's files to another. Most of the work is the edge that sits in front of MTProto, not the storage itself.
What was built
A backend that uploads files to Telegram over the MTProto protocol (using it as free storage) and a Vue admin to organise and hand out the media; delivery is fronted so files load quickly.
A media store where uploads land in a Telegram account's Saved Messages and each file is served back through a public URL. A Node service on uWebSockets.js talks MTProto via gramjs, streams file bytes, and extracts thumbnails; an Nginx layer caches almost every response so Telegram is hit rarely; and a Vue dashboard manages folders, search, move, trash, and per-project API keys. It runs on two VPS nodes behind HAProxy.
Main modules and user path
MTProto storage adapter (gramjs on uWebSockets.js) that uploads to Saved Messages, streams file bytes back, and forwards messages between folders for move/search — including a hand-patched mime.getExtension so downloads resolve the right file extension
Nginx caching edge keyed on Origin plus a Vary: Origin header, after CORS-less <img> responses were being cached and then wrongly served to fetch() calls that needed CORS headers
Auth and abuse protection: phone-number whitelist on code sending, 3 codes per 10 minutes to avoid triggering Telegram FLOOD_WAIT on the account, exponential backoff on wrong passwords, and per-IP rate limits that were tuned up to 300 r/m burst 100 for image-heavy pages
Public no-auth thumbnail endpoints (/thumb, /vthumb) because <img src> can't send a Bearer token — video frames pulled with ffmpeg and cached as jpg
Two-node HAProxy setup with a stick-table pinning each client to one backend, because dashboard tokens live in an in-memory Map per node while public file serving stays stateless on both
Architecture and technology decisions
Built with JavaScript, Vue, Edge delivery.
Node on uWebSockets.js speaking MTProto through gramjs, fronted by Nginx for caching and security headers and HAProxy for two-node round-robin with sticky sessions; the frontend is Vue with a small IndexedDB-backed folder store and a hand-rolled stale-while-revalidate query cache instead of pulling in TanStack Query.
Result and lessons
Media lives on Telegram's infrastructure at no storage cost, and apps get and manage files through one admin instead of a paid bucket.
A working, self-hosted media CDN backed by free Telegram storage, hardened through a long tail of real CORS, caching, and auth bugs; honestly a two-node prototype rather than a fleet, but one that serves an app-scale image library without a storage bill.
Related articles
Read next
Related project stories
These projects share nearby technical or product decisions, so they show how the same principle behaves in another context.
Dev-storyCMS
A dynamic headless CMS on webedge-db — content types, media, roles, and the public read API behind our sites and their articles.
Reactive database platform
Reactive database platform
Our own reactive BaaS on native SpacetimeDB — ~200k realtime deliveries/s at p95<20ms.
Dev-storyMTP-RDMA inference cluster
Tensor-parallel 27B across two Macs over Thunderbolt RDMA — ~70 → 90–100 tok/s.
Have a similar idea?
Discuss your project