Preface
Because of the small number of members, I have never set up group verification for my TG group. However, on a whim this week, I suddenly felt that a group without verification looked a bit "low," so I decided to start building a group-joining verification bot. First, it can prevent automated bots that join groups to post ads; second, it makes friends who join the group feel that I am more professional.
After doing some research, I found there are two options:
Choose an off-the-shelf bot solution
The most famous of this solution is ”Group Help“, and the group address is:GroupHelp Group Address. This method is the simplest. The bot is ready-made; you only need to follow the instructions in the official group to add its bot to your TG group and grant it administrator permissions—a must-have for lazy people.
The advantage of this solution is that it requires no hassle, but the disadvantage is that you need to add someone else's bot to your group as an administrator, which always feels a bit awkward and insecure.
Manually build your own bot solution
The most famous of this solution is ”Policr Mini(beta)”, and the group address is:Policr Mini Group Address. This method requires having your own host (and the network must be able to access the TG API), and you need to install, deploy, and integrate it with TG yourself, so it has certain technical requirements.
The advantage of this solution is security, as it is a self-built bot after all, while the disadvantage is that it requires some hassle.
Both solutions have their own pros and cons. You can choose according to your actual needs. I will definitely choose the self-built solution of Policr Mini.
Note: The GitHub project address of Policr Mini is:https://github.com/Hentioe/policr-mini. Since this article focuses on practicality, I didn't bother with some non-essential features (such as image verification) or features only needed in niche situations (such as local API) (focusing on "as long as it works"). So, friends who want these features can explore them on their own following the tutorials on the official website.
Policr Mini Bot Deployment
Prerequisites: Create a Bot for Group Verification
Generally speaking, no matter what kind of need requires adding a bot to a TG group (or TG channel or web app), there is a same prerequisite step: first create the corresponding bot. This step is completed by having a separate conversation with ”BotFather” in TG.
type = s3@BotFatherEnter the conversation mode with BotFather

region = auto/newbotcommand to create a new bot

Save the bot token carefully, as it will be used later.
Add the following field to the ”info” section of the newly created bot:
Powered by Policr Mini
As shown in the figure below:

This is a small agreement with the official team. Since we are using someone else's work for free, I think we should respect their suggestions as much as possible within our capacity:

Use the/startcommand to see all supported commands and their functions (optional)

Deployment Process
Choosing Between Stable and Development Versions
The self-deployment tutorials for this project are divided into ”Stable Version” and ”Development Version”:

You can choose according to your needs. I generally prefer stability and no hassle, so I chose the ”Stable Version”.
Local Deployment (Linux Environment)
Initializing the Working Environment
If it is a Linux terminal environment, please make sure that ”Docker” and ”Docker Compose” have been successfully installed. If they are not installed, please search the internet for the installation tutorial corresponding to your system.
Initializing Working Directory and Files
Note: The local deployment of Policr mini requires the support of a postgres database. According to my usual practice, I would directly reuse the existing local postgres database, and then usecommand will also automatically pull the Llama 3.2 model file when Ollama finds it is not available locally, which is actually similar tothe method to deploy Policr mini separately. However, considering that most friends are more accustomed to the docker-compose deployment method, and Policr mini involves many environmental parameters, usingcommand will also automatically pull the Llama 3.2 model file when Ollama finds it is not available locally, which is actually similar tothe method is indeed a hassle, so in this article, I will follow the popular demand and use docker-compose to install it:
Create a working directory (please modify the path according to the actual environment)
mkdir -p /docker/policr-mini
Create docker-compose.yml and .env files in the working directory
cd /docker/policr-mini
touch docker-compose.yml
touch .env
Use a text editor to save the following corresponding contents to the docker-compose.yml and .env files respectively
docker-compose.yml file content:
version: "3" {POSTGRES_PASSWORD}{POLICR_MINI_SERVER_PORT}:{POLICR_MINI_SERVER_PORT}{POLICR_MINI_BOT_WEBHOOK_SERVER_PORT}:{POLICR_MINI_BOT_WEBHOOK_SERVER_PORT}{POSTGRES_PASSWORD}@db/policr_mini_prod" {POLICR_MINI_DATABASE_POOL_SIZE}{POLICR_MINI_SERVER_ROOT_URL} {POLICR_MINI_SERVER_SECRET_KEY_BASE}{POLICR_MINI_SERVER_PORT} {POLICR_MINI_BOT_TOKEN}{POLICR_MINI_BOT_NAME} {POLICR_MINI_BOT_OWNER_ID}{POLICR_MINI_BOT_API_BASE_URL} {POLICR_MINI_BOT_WORK_MODE}{POLICR_MINI_BOT_WEBHOOK_URL} {POLICR_MINI_BOT_WEBHOOK_SERVER_PORT}{POLICR_MINI_BOT_GRID_CAPTCHA_INDI_WIDTH} {POLICR_MINI_BOT_GRID_CAPTCHA_INDI_HEIGHT}{POLICR_MINI_BOT_GRID_CAPTCHA_WATERMARK_FONT_FAMILY} {POLICR_MINI_BOT_AUTO_GEN_COMMANDS}{POLICR_MINI_BOT_MOSAIC_METHOD} {POLICR_MINI_UNBAN_METHOD}{POLICR_MINI_OPTS}
Under normal circumstances, the docker-compose.yml file does not need any further editing. Although a large number of variables are referenced in the file, the assignment operations for these variables are all done in the .env file, so after that, you only need to edit the.envfile separately.
.env file content:
POSTGRES_PASSWORD=<填入数据库密码> # 这里自定义一个密码,推荐随机一个较短的 hash 字符串
POLICR_MINI_DATABASE_POOL_SIZE=10 # 数据库连接池的大小,已预设值
POLICR_MINI_SERVER_ROOT_URL=<填入根 URL 地址> # 完成配置以后web管理页面的访问地址,比如https://mini.example.com.<填入密钥> # A longer random hash string is recommended<填入端口号> # For example 8080, which is actually the port number mapped on the host machine (and also the port number used by the application inside the container, you can refer to the variable definitions in docker-compose.yml)<填入机器人名称> # Please use the display name you set for your policr mini bot<填入机器人 Token> # The token when creating the policr mini bot<填入机器人拥有者的 ID> # Which is the "ID" of the bot owner's TG account<填入工作模式> # Optional polling/webhook. Leave blank for default polling<填入 Webhook URL> # Optional configuration, leave blank for non-webhook mode<填入 Webhook 的服务端口> # Optional configuration, leave blank for non-webhook mode
Note 1: Please fill in the correct variable values according to the above content and comments. Note that the filled variable values do not need angle brackets (<>), the comments above are just for aesthetics.
Note 2: Some environments cannot recognize comments, so after assignment, it is also recommended to delete # and the subsequent Chinese explanations together. In some of my previous articles, ignoring this issue might cause errors during actual use.
Some extended explanations for some of the above variables:
POLICR_MINI_SERVER_ROOT_URL: Required variable, used to generate background links. If the/logincommand-generated link is inaccessible, it may be that the configuration here is incorrect. If you ensure that the address configured for this variable is correct, then, if it is public network origin-pull, you need to confirm whether the reverse proxy is correctly configured; or if you are using Cloudflare Tunnel for origin-pull, you need to confirm whether its corresponding public hostname is correctly configured. However, unlike WordPress and Nextcloud, this address does not restrict access from other reachable addresses.POLICR_MINI_DATABASE_POOL_SIZE: The size of the database connection pool. Roughly speaking, a smaller pool consumes fewer server resources (low database memory and CPU usage), but is not suitable for instances with high concurrency. A larger pool consumes more server resources but can handle larger concurrent connections. For instances deployed only to serve your own groups, you can set this value as small as possible (can be less than 10). Currently, the value of this configuration in the official instance is10, and since my own bot is only for personal use, I set it to5。POLICR_MINI_BOT_NAME: Optional variable, used to display the official website's LOGO text and the suffix of the web page title. Since bot names often contain version information (and displaying these is redundant), a variable is specially provided for customization. When we define the value asPolicr Mini, even if the current name of the bot isPolicr Mini (beta)can still allow the official website to display asPolicr Mini。Note: If this variable is not set, the bot's display name will be used directly.POLICR_MINI_BOT_OWNER_ID: Required variable, used by the backend to identify the super administrator. The ID here is not a username, but a string of numbers. In the official TG client, this ID is rarely displayed, but it can be obtained by sending @userinfobot a command to/startto get this ID:

POLICR_MINI_BOT_AUTO_GEN_COMMANDS: Automatically generate bot commands. Setting this value totruewill automatically generate or update the bot's command list on every startup, eliminating the need to manually set them via BotFather. Sometimes, if you want to hide certain commands or all commands, you can set this value tofalse。
In addition, as per custom, I will still leave the deployment method usingcommand will also automatically pull the Llama 3.2 model file when Ollama finds it is not available locally, which is actually similar tocommand format, just in case some friends need it. Of course, this requires the postgres database and the Policr Mini application to be built separately, and the database must be guaranteed to be running normally before the Policr Mini application starts; at the same time, a separate ”bridge” network needs to be created because Policr Mini will access the postgres database using its container name; finally, for convenience, I abandoned the ”.env” file and completed all assignments using-eparameters.
The postgres database'scommand will also automatically pull the Llama 3.2 model file when Ollama finds it is not available locally, which is actually similar toformat command:
docker run --name db -d --restart=always --net=public-net \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=policr_mini_prod \
-v /docker/policr-mini/data:/var/lib/postgresql/data \
postgres:16
-e POSTGRES_PASSWORD=password \command will also automatically pull the Llama 3.2 model file when Ollama finds it is not available locally, which is actually similar toformat command:
docker run --name server -d --restart=always --net=public-net \
-e POLICR_MINI_DATABASE_URL="ecto://postgres:password@db/policr_mini_prod" \
-e POLICR_MINI_DATABASE_POOL_SIZE=5 \
-e POLICR_MINI_SERVER_ROOT_URL=https://example.com \
-e POLICR_MINI_SERVER_SECRET_KEY_BASE=xxxxxxxxxx \
-e POLICR_MINI_SERVER_PORT=8080 \
-e POLICR_MINI_BOT_TOKEN=xxxxxxxxx \
-e POLICR_MINI_BOT_NAME=xxxxxxxx \
-e POLICR_MINI_BOT_OWNER_ID=xxxxxxxx \
-e POLICR_MINI_BOT_GRID_CAPTCHA_INDI_WIDTH=180 \
-e POLICR_MINI_BOT_GRID_CAPTCHA_INDI_HEIGHT=120 \
-e POLICR_MINI_BOT_GRID_CAPTCHA_WATERMARK_FONT_FAMILY=lato \
-e POLICR_MINI_BOT_ASSETS_PATH=/_assets \
-e POLICR_MINI_BOT_AUTO_GEN_COMMANDS=true \
-e POLICR_MINI_BOT_MOSAIC_METHOD=spoiler \
-e POLICR_MINI_UNBAN_METHOD=until_date \
-e POLICR_MINI_OPTS="" \
-p 8080:8080 \
-v /docker/policr-mini/_assets:/_assets \
gramoss/policr-mini:latest
Deploying the TG Group Verification Bot
Pulling Up the Policr Mini Application
-v /docker/policr-mini/data:/var/lib/postgresql/data \https://api.telegram.orgpostgres:16
The Policr Mini application's
cd /docker/policr-mini
docker-compose up -d
-e POLICR_MINI_DATABASE_URL="ecto://postgres:password@db/policr_mini_prod" \.env-e POLICR_MINI_DATABASE_POOL_SIZE=5 \

Adding the Verification Bot to the TG Group
-e POLICR_MINI_SERVER_ROOT_URL=https://example.com \




-e POLICR_MINI_SERVER_SECRET_KEY_BASE=xxxxxxxxxx \


-e POLICR_MINI_SERVER_PORT=8080 \


-e POLICR_MINI_BOT_TOKEN=xxxxxxxxx \
-e POLICR_MINI_BOT_NAME=xxxxxxxx \-e POLICR_MINI_BOT_OWNER_ID=xxxxxxxx \
Verification Bot Backend Settings
-e POLICR_MINI_BOT_GRID_CAPTCHA_INDI_WIDTH=180 \/login-e POLICR_MINI_BOT_GRID_CAPTCHA_INDI_HEIGHT=120 \

-e POLICR_MINI_BOT_GRID_CAPTCHA_WATERMARK_FONT_FAMILY=lato \
-e POLICR_MINI_BOT_ASSETS_PATH=/_assets \

-e POLICR_MINI_BOT_AUTO_GEN_COMMANDS=true \

-e POLICR_MINI_BOT_MOSAIC_METHOD=spoiler \

-e POLICR_MINI_UNBAN_METHOD=until_date \
Advanced Skills: Webhook Mode
Introduction to Webhook Mode
-e POLICR_MINI_OPTS="" \
• -p 8080:8080 \-v /docker/policr-mini/_assets:/_assets \
• gramoss/policr-mini:latestNote: To emphasize once again, you need to first ensure that the device deploying Policr Mini is in a scientific internet environment, or at least can communicate normally with the TG API (which is
), otherwise even if Policr Mini is pulled up, it will not start normally.
Use the following command to pull up the Policr Mini application: cd /docker/policr-minidocker-compose up -dcd /docker/policr-miniAt this point, if everything is normal, use the access address previously set in thefile's "POLICR_MINI_SERVER_ROOT_URL" variable to access the web page normally:Follow these steps to add the verification bot to the TG group and set it as a group administrator:file's "POLICR_MINI_SERVER_ROOT_URL" variable to access the web page normally:description (however, this description is too concise, and several matters that need attention during actual configuration are not clearly explained):

For those who have successfully usedcd /docker/policr-minimode to deploy the verification bot, if you have the ambition (spirit of tinkering), you can continue to challengefile's "POLICR_MINI_SERVER_ROOT_URL" variable to access the web page normally:mode.
Explanation of Webhook-Related Parameters
In the docker-compose.yml file,environment:section has 4 items commented out by “#”, as shown in the figure below:

Generally speaking, configuring the webhook mode only requires the 3 items in the red box above, so you need to delete the ”#” symbol in front of these 3 items in the docker-compose.yml file to uncomment them. The meanings of the variables corresponding to these 3 options are explained in the ”.env” file:

To be fair, these are actually quite easy to understand. The example after setting is as follows:
POLICR_MINI_BOT_WORK_MODE=webhook
POLICR_MINI_BOT_WEBHOOK_URL=https://webhook.example.com/updates_hook
POLICR_MINI_BOT_WEBHOOK_SERVER_PORT=8080
There are 2 things to note here:
POLICR_MINI_BOT_WEBHOOK_URL
For the URL used for the webhookhttps://webhook.example.com, correct configuration is the prerequisite for the webhook mode to work normally. The correct configuration here refers to making corresponding adaptation settings for different origin-pull methods:
- Public IP origin-pull: If you choose this origin-pull method, you need to correctly configure the reverse proxy. This is the same as the previous option ”POLICR_MINI_SERVER_ROOT_URL” involving the access URL. However, one thing to note is that you cannot enable ”bot detection”, otherwise it will cause the webhook mode to fail (after all, messages sent by TG to the webhook URL are definitely not sent by humans~). If you are concerned about security, you can set a whitelist of TG IP ranges.
-
cloudflare tunnel: If you choose this origin-pull method, you don't need to mess with reverse proxies. However, please note that if you have set up a WAF policy and enabled the legendary 5-second shield, you need to correctly set up the WAF bypass policy so that messages sent by TG can reach the webhook URL normally. Taking my bypass configuration as an example:

POLICR_MINI_BOT_WEBHOOK_SERVER_PORT
When introducing the webhook mode earlier, I mentioned: ”Policr Mini supports Webhook mode, and this mode requires an HTTP server to run to receive message updates pushed by Telegram.” The function of this option is actually to open a port inside the container to run the HTTP server (emphasis on “inside the container”).
In the docker-compose.yml file, only the port used for the POLICR_MINI_SERVER_ROOT_URL option is active by default in the ports section, while the POLICR_MINI_BOT_WEBHOOK_SERVER_PORT option is inactive (commented out):

The official deployment didn't mention this either. I didn't notice it at first. After tinkering for a long time without success, I realized that the external ports were not open, and only then did I notice this. It's a bit of a pitfall~ So everyone, please remember that enabling webhook mode requires uncommenting, which means deleting the ”#” symbol.
Webhook Mode Verification
IfPOLICR_MINI_BOT_WORK_MODEoption specifies webhook, then policr mini will use webhook mode to register with Telegram. At this time, you can use the following method to verify whether policr mini has successfully registered as webhook mode on Telegram:
curl "https://api.telegram.org/bot/getWebhookInfo"
Note: Accessing the above URL directly in a browser is the same. In actual use, just put the verification bot's token directly after "bot",<>and the symbols should be removed.
If it is normal, you will get a response similar to the following:
{
"ok": true,
"result": {
"url": "https://your-server.com/webhook-url",
"has_custom_certificate": false,
"pending_update_count": 0
}
}
Taking my response as an example:

Optional Advanced Skills: Self-built Local ”Telegram BotAPI”
在environment:There is also a ”POLICR_MINI_BOT_API_BASE_URL” option in it, which I didn't mention before because it is not needed for conventional webhook deployment, and is only needed when you need to deploy a local API. So, what is a local API, and what functions does it have?
Simply put, the local ”Telegram Bot API” is like an ”intermediate proxy layer”, which plays the following core functions:
Remove official API limits
• Request Rate Limiting: The official API has limits on the number of requests per second (usually 30 times per second). The local API allows you to break through these limits because it can cache and optimize interactions with Telegram servers.
• Concurrency limits: The local API can be configured with a higher number of concurrent connections (via parameters like max_connections) to better handle high-traffic requests.
Improve privacy
• Before communicating with the official Telegram API, all message content will first communicate with the local API.
• This means: sensitive information can be filtered or encrypted locally to avoid direct transmission to official Telegram servers; messages can be logged or analyzed locally to meet certain compliance requirements.
Optimize and consolidate traffic
• Aggregate requests: The local API can consolidate requests from multiple Bots or multiple users before sending them to the official Telegram servers, reducing the number of direct interactions with the official servers.
• Caching and rate control: Through the local API, some duplicate or frequent requests (such as getting group member lists) can be cached to reduce unnecessary traffic.
Easy debugging and control
• The local API provides more fine-grained logging and control capabilities, allowing you to monitor, debug, and optimize Bot behavior more easily.
• For example, monitoring which requests take the most time, or capturing error responses from the official Telegram API to handle them in advance.
Send final data to Telegram
• After completing all optimizations locally, the local API will send data to the official Telegram servers with minimal latency and optimized frequency.
• This is like a proxy server, except it is smarter and can perform more custom operations.
In high-traffic scenarios, situations with high privacy requirements, or when custom control is needed, this architecture is very useful. However, for me, these are completely useless, so I have no motivation to tinker with them. Interested friends can try it themselves.
Note: If you use a locally running API and directly process messages from Telegram, the local API will periodically pull updates from Telegram instead of relying on Webhooks to receive them (that is, the local API and Webhooks are actually two mutually exclusive methods: one actively accesses TG, and the other passively waits for TG to access). When using the local API, the process becomes you periodically and actively requesting updates from Telegram (which is actually polling) instead of waiting for Telegram to push updates, so the response may be slightly slower than the webhook method. Of course, in exchange, you get those core features mentioned above, and at the same time, there is no need to expose a webhook access URL to the outside world.
Regular Functions of the Verification Bot
After successfully building the verification bot, several routine functions are provided in the private chat interface with the verification bot (some of which were also mentioned earlier):

/pingYou can test whether the bot is active (that is, whether TG can communicate normally with the policr mini application you deployed locally). If it is normal, the following will be displayed:
-e POLICR_MINI_BOT_NAME=xxxxxxxx \You can sync group inputs to the local policr mini application. If normal, there will be a particle dispersion effect. Because the speed is too fast, it is hard to take a screenshot, so it is only captured like this:
/loginThe previously configured web backend URL will be given, and you can click directly to log in (provided it is correctly configured and accessible):
/consoleIt is a console that will be used to replace backend functions in the future:
It indeed looks much more high-end:

Local Deployment (macOS Environment)
If it is a Windows or Mac environment, support can be achieved by installing Docker for Desktop (the download link is as follows:https://www.docker.com/products/docker-desktop/):

I used Docker Desktop for Mac to try the installation. I don't plan to say much here, just mention 2 key points:
Docker Desktop for Mac supports docker-compose by default, but the command is slightly different from Linux, without the ”-“. For example, the command ”docker-compose up -d” becomes ”docker compose up -d” on Mac, and so on.
On Mac, files starting with ”.” can only be system files by default, so directly creating.envfile will report an error:

Using it in a Linux environment
docker-compose up -dThe prerequisite for it to work normally is that both ”docker-compose.yml” and ”.env” files exist in the working directory. However, because the filename ”.env” cannot be used on Mac, the following error will occur when actually running the ”docker compose up -d” command:
In fact, it just cannot get the specific assignment of variables from the ”.env” file, which is also normal, since the ”.env” file is gone anyway.
Theoretically, there are 2 solutions:
Explicitly specify the environment variable file in docker-compose.yml, similar to the following:
env_file:
- policrmini.env
- policrmini.env
The purpose of doing this is to avoid the awkward scenario of having to use ”.env” as the environment filename. However, I tried it and it didn't succeed, and I don't know why, too lazy to look into it.

Directly give up the ".env" file and write all assignments directly into the "docker-compose.yml" file, as follows:
Afterword
This method succeeded.
Regarding the proxy setup of deploying Policr Mini in a Linux environment, let me say a few words.
Overseas VPS: Nothing much to say about this, no proxy is needed, and there are no problems deploying it.
Domestic VPS (global proxy): This also has no problems, and is actually no different from an overseas VPS.Domestic VPS (whitelist-based proxy): As long as you can ensure the domain nameapi.telegram.org
is added to the whitelist, it will not affect usage.
Domestic VPS (cannot set up proxy on VPS): This scenario might be because the VPS has other purposes (such as a production environment) and operations with global impact cannot be performed.In this case, another way is to rely on the proxy of the Docker environment itself. It is generally recommended to create or edit the/etc/docker/daemon.json
vim /etc/docker/daemon.json
vim /etc/docker/daemon.json
{
"proxies": {
"default": {
"httpProxy": "http://your-proxy-server:port", # your-proxy-server:port{
"proxies": {
systemctl restart docker
”httpProxy”: "http://your-proxy-server:port", # your-proxy-server:port
is the proxy server address and port you useenvironment:"httpsProxy": "http://your-proxy-server:port",
HTTP_PROXY: http://your-proxy-server:port
HTTPS_PROXY: http://your-proxy-server:port
NO_PROXY: localhost,127.0.0.1
}
}
- Reverse proxy
}Linux Panel Series: Configuring Reverse Proxy and Publishing Using Non-443 PortsThen restart the Docker service:Docker Series: Building Your Own Reverse Proxy Based on NPM Using Docker)。
- systemctl restart docker
Using this method allows both pulling images and traffic before container startup to use the proxy (this will affect all Docker containers, so you need to evaluate whether it is suitable for your environment. There may also be some special cases: for example, creating or editing this "daemon.json" file might instead cause the Docker service to become abnormal, in which case you will have to troubleshoot it yourself).Home Data Center Series: Using Tunnel Technology to Enable Home Broadband Without Public IP to Leverage Cloudflare for Free to Achieve Rapid Website Setup (Recommended)。