remnashop: selling VPN subscriptions through a Telegram bot
This project is a Telegram bot for selling VPN subscriptions, integrated with Remnawave.
At a glance
- What is it?
- remnashop is a Telegram bot that sells VPN subscriptions for a Remnawave panel, with a web interface, a payments flow, a referral system and a background task queue. It is a FastAPI and aiogram application on Postgres and Redis, packaged with Docker Compose files for local and production use.
- Who is it for?
- Adopt remnashop if you already run a Remnawave panel and want to sell subscriptions to your users through Telegram instead of building a shop from scratch. Do not adopt it if you have no Remnawave backend, since the bot is tightly coupled to it, and note that the payment provider and the account model are decisions you make rather than defaults the project imposes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 78 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the bot is for
remnashop is a Telegram bot for selling VPN subscriptions, integrated with Remnawave, and the project description puts it in one line: This project is a Telegram bot for selling VPN subscriptions. It works with Remnawave.
Remnawave is the panel behind the subscriptions, so the bot is the storefront rather than the product. That division matters for anyone evaluating it, because all the difficult parts of running a VPN business, the panel, the user accounts, the traffic accounting, the subscription state, stay where they were, and this project only handles the part where somebody opens a Telegram chat and pays you.
The features implied by the repository contents are substantial. There is a web interface alongside the bot, since the project ships FastAPI and uvicorn alongside the Telegram library, and the documentation link points at a Mintlify docs site with an overview and releases section. There is a payments flow, since the dependencies include a payment integration and the environment file talks about payment tokens being encrypted in the database. There is a task queue, since Taskiq is a dependency along with its Redis broker, and there is a referral system, since a VPN subscription shop in Telegram without referral codes would be unusual.
The .env.example shows the shape of the configuration and it is worth reading as the map of what the bot does. There is APP_DOMAIN, the domain through which the bot is publicly accessible, used for receiving webhooks from services such as Telegram and the payment system. There is APP_CRYPT_KEY, the encryption key for securing sensitive data such as payment tokens in the database. There is BOT_TOKEN from BotFather, BOT_SECRET_TOKEN for webhook verification so that requests originate from Telegram, and BOT_OWNER_ID, the owner Telegram user ID, which grants elevated permissions.
There is also BOT_SUPPORT_USERNAME, used for deep-linking, and BOT_MINI_APP, which controls the behaviour of the connect button. The empty or false case opens the subscription page in the browser, true opens it in a WebApp, and a URL opens that address in a WebApp. The comments point at three separate implementations you can plug in, the Remnawave subscription page, Orion, and the Maposia Mini App, which is a good indication that the project treats the front end as interchangeable.
The dependency list, and what each one is doing
The pyproject is short and readable, and the grouping of the dependencies tells you the architecture.
The web and bot layer is FastAPI with uvicorn, python-multipart for form handling, and aiogram with aiogram-dialog and fluentogram for the Telegram interface. aiogram-dialog is the library that makes a multi-step conversation in a chat feel like a form, and fluentogram is its localisation layer, which is consistent with the readme having a Russian translation alongside the English one.
The data layer is asyncpg with SQLAlchemy, Alembic for migrations, and Redis. The combination of SQLAlchemy with the asyncpg driver means the database access is through the async engine rather than a synchronous one, which is the right choice for an application driven by a bot loop rather than by request handlers.
The Remnawave integration is remnapy, pinned to version 2.7.0. There is also aiohttp-socks, which is there for the subscription links the bot hands out, since a VPN subscription is delivered as a proxy URL that the client has to fetch.
The background work is Taskiq with its Redis broker and the orjson extra. That is a separate concern from the bot loop, and putting it behind a queue means a long job such as a bulk operation or a scheduled reconciliation does not block the chat.
Then there is the support layer. pydantic-settings for configuration, loguru for logging, msgspec for fast serialisation, cryptography and pyjwt for token handling, and qrcode with the Pillow extra, which is there to generate the subscription QR codes that Telegram clients display.
Two entries deserve particular attention. dishka is a dependency injection container, and adaptix is a serialisation library pinned to a beta, 3.0.0b11. A beta pin in a production dependency set is a deliberate choice, and the version being a beta rather than a release is worth checking before you depend on it.
The Python version is pinned narrowly to 3.12, with requires-python set to >=3.12,<3.13, and the ruff configuration targets py312 with a line length of 100 and an explicit list of rule sets including flake8-comprehensions, flake8-datetimez, isort and pylint rules. The mypy configuration covers the src package with the SQLAlchemy and Pydantic plugins.
Building the image, and the secrets the entrypoint expects
The Dockerfile is a two-stage build on Alpine, and it is careful in the ways that matter for a service that holds payment tokens.
The builder stage uses the official uv image and installs git, which is needed because one dependency is not on PyPI. The pyproject pins remnapy to a git revision rather than a release: the uv sources section points it at a fork of remnapy at a specific commit hash, 95e15ed. So the bot depends on a patched version of the Remnawave client rather than the published one, and that is why git has to be present in the build image.
The dependency install uses uv sync with --locked, so the committed lockfile is authoritative, and --no-dev to keep the development group out. It then removes pip, setuptools and wheel from the site-packages, and compiles bytecode in the same step.
The final stage is plain python:3.12-alpine, which is smaller than the uv image, and it copies only the virtual environment across. It installs postgresql-client, which is there for database connectivity checks, sets PYTHONUNBUFFERED so logs appear promptly in a container log collector, and points PYTHONPATH at the application directory.
The build metadata is carried through as build arguments rather than being baked in: BUILD_TIME, BUILD_BRANCH, BUILD_COMMIT and BUILD_TAG are declared as arguments and then exported as environment variables. That is what lets a deployed instance report which commit it is running, and for a bot that people pay money, being able to tie a running instance to a specific commit is worth the four lines.
The assets directory is copied in as assets.default, and the entrypoint script is made executable and used as the container command.
The Makefile has a setup-env target that generates the secrets rather than leaving them to be typed into a file:
APP_DOMAIN=change_me
APP_CRYPT_KEY=change_me
BOT_TOKEN=change_me
BOT_SECRET_TOKEN=change_me
BOT_OWNER_ID=change_meThose are the placeholder values in the example file, and the make target replaces the ones it can with generated values, using openssl to produce a base64 key for the encryption secret, a hex token for the webhook secret and the database and Redis passwords. The environment file itself flags the two most dangerous values with warnings, saying never share this token publicly, and for the bot token that it grants full control over your bot.
Running it, and the local versus production split
There are three Compose files and a Makefile target for each, which is how the project separates environments.
There is docker-compose.local.yml, docker-compose.prod.external.yml and docker-compose.prod.internal.yml, and the Makefile wires them to run, run-local and run-prod targets. The local target builds and starts the local compose file and then follows the logs, and the production target does the same with the external production file. Both targets accept a reset goal, which tears the stack down with volumes removed before starting, so a reset gives you a clean database rather than a half-migrated one.
The two production variants are the interesting distinction. An external production deployment is one where the Postgres and Redis live somewhere else, reached over the network, and an internal one is where the compose file runs the whole stack including the databases. The environment file supports both with a hostname or Docker service name for connecting to the Remnawave panel and a separate database host and port.
Migrations are first-class, with four Makefile targets. The migration target runs alembic revision with --autogenerate to create a new revision from the models, and the migrate target runs alembic upgrade head to apply them. There is a local variant of each that injects the database host and port, and the alembic configuration lives at src/infrastructure/database/alembic.ini, which tells you the layering of the source tree: infrastructure at the bottom, and the Alembic configuration inside it.
The local database defaults are visible in the Makefile as a host of 0.0.0.0 and a port of 6767, both overridable, and the local variants are the ones that export those as environment variables.
The project also ships a Dockerfile.local alongside the main one, a docker-entrypoint.sh, and a logs/ directory in the repository. It uses uv for dependency management with a committed uv.lock, and the Makefile has a version target with a rev variable, so releases are driven from the Makefile rather than by hand.
How the payment and key handling is arranged
The environment file is where the security posture of the project is actually stated, and it is worth reading closely.
APP_CRYPT_KEY is described as the encryption key for securing sensitive data, with payment tokens given as the example, in the database. The comment says it is critically important and never to share this token publicly. That is a meaningful design choice: the bot stores something that has monetary value, and it stores it encrypted rather than in the clear.
BOT_SECRET_TOKEN is the mechanism that stops arbitrary people from posting fake webhook updates at the bot. The comment describes it as the secret token for webhook verification, ensuring webhook requests originate from Telegram. This is the standard pattern, and having it as a separate value from the bot token means a leaked webhook secret is less catastrophic than a leaked bot token.
BOT_OWNER_ID grants elevated permissions, which is the admin escape hatch. In a bot that handles money and can create subscriptions, you want one identified administrator account rather than a broad permission system, and that is what this is.
APP_DOMAIN is annotated with two warnings. Specify it without HTTP or HTTPS and without a trailing slash, because the bot appends the scheme itself when it builds webhook URLs, and a stray slash produces a URL that Telegram rejects. And it is the domain through which the bot is publicly accessible, so it is the value that determines whether the bot is reachable from the internet, which is a security decision as much as a configuration one.
The BOT_MINI_APP setting then decides what a customer sees when they press connect, and the three commented alternatives point at three different front ends, one of which is a maintained Mini App for Telegram. So the customer-facing surface is something the operator chooses, and the bot is deliberately not tied to one implementation of it.
Editorial conclusion
Adopt remnashop if you already run a Remnawave panel and want to sell subscriptions to your users through Telegram instead of building a shop from scratch. Do not adopt it if you have no Remnawave backend, since the bot is tightly coupled to it, and note that the payment provider and the account model are decisions you make rather than defaults the project imposes. Before you commit, generate your secrets with the setup-env target rather than editing the example file by hand, and set the connect button behaviour, because the default opens a browser page while the alternatives open a WebApp.
Frequently asked questions
What does remnashop do?
It is a Telegram bot for selling VPN subscriptions that works with Remnawave. The bot is the storefront: the Remnawave panel stays in charge of the accounts and subscriptions, while remnashop handles the chat interface, the web interface, payments and the background tasks.
Which Python version and tools does remnashop require?
Python 3.12, with requires-python set to >=3.12,<3.13 and ruff targeting py312. Dependencies are managed with uv and a committed uv.lock, and the build uses uv sync with --locked and --no-dev, so the lockfile decides what is installed.
How are the secrets for remnashop generated?
The make setup-env target rewrites the .env file in place, generating the encryption key with openssl rand -base64 32, the webhook secret token with a 64 byte hex value, and the database and Redis passwords with 24 byte hex values. The example file ships placeholder values and warns never to share the encryption key or the bot token publicly.
What is the difference between the remnashop compose files?
There is a docker-compose.local.yml for development, and two production files, docker-compose.prod.external.yml and docker-compose.prod.internal.yml. The external variant expects Postgres and Redis to be reachable over the network, while the internal one runs the whole stack including the databases. The Makefile has matching run, run-local and run-prod targets, each accepting a reset goal that tears down volumes first.
How does the remnashop connect button behave?
It is controlled by BOT_MINI_APP. Empty or false opens the subscription page in the browser, true opens it in a WebApp, and a URL opens that address in a WebApp. The environment file points at three interchangeable front ends, the Remnawave subscription page, the Orion custom page, and the Maposia Mini App.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/snoups-remnashop)Was this useful?