Auto-send personalized Telegram DMs to Google Form respondents using n8n and a Telethon-based userbot service. New form rows trigger an n8n workflow that waits a random delay, then calls a small FastAPI service which sends a DM from your personal Telegram account.
Google Form -> Google Sheet -> n8n Trigger -> Wait 5-10 min
|
v
Telethon Service
(FastAPI + Telethon)
|
v
Telegram DM sent
|
v
Log row to "DM Log" sheet
The Telethon service reads config.json at startup and owns message formatting,
column mapping, and phone number parsing. n8n just forwards the raw form row.
.
|- config.json Columns, message template, country code
|- workflow.json Importable n8n workflow
|- docker-compose.yml Runs telethon-service + n8n together
|- .env.example Root env vars used by docker compose
|- telethon-service/
| |- app.py FastAPI + Telethon service
| |- Dockerfile
| |- requirements.txt
| |- .env.example Env vars for non-Docker local dev
- A Telegram account and its phone number.
- Telegram API credentials from https://my.telegram.org/apps (
api_id,api_hash). - A Google account with access to the source Google Form / Sheet.
- Docker and Docker Compose (recommended), or Python 3.11+ and an existing n8n instance.
Everything user-facing lives in config.json. Edit it before starting the service.
{
"session_name": "telegram_session",
"default_country_code": "1",
"phone_local_length": 10,
"columns": {
"contact": ["Telegram", "Phone", "Telegram Username"],
"name": ["Name", "Full Name", "First Name"],
"interest": ["Interest", "Topic", "Looking For"]
},
"message_template": "Hi {name},\n\nThanks for your interest in {interest}. I'll follow up shortly with next steps.\n\nBest regards."
}| Field | Purpose |
|---|---|
session_name |
Telethon session file name. Change if you run multiple instances. |
default_country_code |
Prepended to local-format phone numbers (e.g. "1" for US, "44" UK, "998" UZ). |
phone_local_length |
Number of digits in a local-format phone number (without country code). |
columns |
Map of logical field -> list of possible Google Sheet column headers. First match wins. |
message_template |
Python str.format_map template. Reference any key from columns. Missing keys render empty. {name} falls back to "there". |
Add any custom fields you need: put them under columns and reference them in
message_template with {field_name}.
Copy .env.example to .env at the repo root and fill in:
cp .env.example .envTELEGRAM_API_ID,TELEGRAM_API_HASH: from https://my.telegram.org/apps.TELEGRAM_PHONE: the phone number of the account that will send DMs, international format (e.g.+12025550123).TELETHON_API_SECRET: any random string. Shared between n8n and the service.N8N_HOST,WEBHOOK_URL: adjust for VPS deployments behind a domain.
git clone <your-repo-url> google-forms-telegram-automation
cd google-forms-telegram-automation
cp .env.example .env
# edit .env and config.json
docker compose up -d --buildOn first run the Telethon service needs an interactive login. Attach to the container and enter the SMS code Telegram sends you:
docker attach telethon-service
# enter the code, then Ctrl-p Ctrl-q to detachThe session file is persisted in the telethon_sessions Docker volume, so you
only need to do this once per account.
Verify the service is up:
curl http://localhost:8100/healthn8n is now available at http://localhost:5678.
Run the Telethon service directly with Python while pointing at an existing n8n install.
cd telethon-service
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# edit .env
uvicorn app:app --host 0.0.0.0 --port 8100On first run you will be prompted for the SMS code in the terminal.
- Open n8n -> Workflows -> Import from file -> select
workflow.json. - Open each Google Sheets node and:
- Create or select a Google Sheets OAuth2 credential.
- Set the Document ID to your Google Sheet (the form responses sheet).
- Set the sheet name for the trigger (usually
Form Responses 1) and the logging nodes (create aDM Logtab with columns:name,contact,status,timestamp,error).
- Open the Send Telegram DM node:
- If n8n runs in the same Docker Compose stack, the default URL
http://telethon-service:8100/send-from-rowalready works. - If n8n runs separately, change it to
http://<host>:8100/send-from-rowand make sure the port is reachable. - The
x-api-secretheader reads$env.TELETHON_API_SECRET. Set this env var on your n8n instance (Docker Compose does this for you).
- If n8n runs in the same Docker Compose stack, the default URL
- Optionally adjust the Wait 5-10 minutes node to tune your human-like delay.
- Activate the workflow.
Your responses sheet needs at least a contact column (Telegram username or
phone number) and the columns referenced in config.json. The log sheet (DM Log
by default) should have these columns in the header row:
name | contact | status | timestamp | error
The service accepts messy input in the contact column:
@username,username,t.me/username,https://t.me/username- Full international phone:
+12025550123,12025550123 - Local phone:
2025550123(prepended withdefault_country_code) - Names mixed with handles:
John @john_doe,Jane 2025550123
Phones require the service to add the contact first, so the target must allow DMs from non-contacts and the number must be linked to a Telegram account.
All endpoints require the x-api-secret header.
POST /send-from-row - used by the workflow.
{ "row": { "Name": "Ada", "Telegram": "@ada", "Interest": "IELTS" } }POST /send-dm - low-level, for direct use.
{ "contact": "@ada", "message": "Hi Ada" }GET /health - returns connection status and logged-in account.
- Rate limits: the 5-10 minute random wait is there to keep traffic human-like. Do not remove it for high-volume sends, or Telegram will flood-wait your account.
- Privacy settings: some users block DMs from non-contacts. The service
returns a typed error for this case and the workflow logs it to
DM Log. - Session file: never commit
*.sessionfiles. They grant full access to the logged-in account.
MIT - see LICENSE.