Как сделать идеальный lead gateway-бот на Go для Telegram: опыт и открытый исходник

Сегодня Telegram — одна из самых эффективных площадок для сбора лидов и создания закрытых сообществ. Если вы хотите, чтобы подписчики проходили через автоматическую регистрацию, подтверждали вступление в приватный канал, оставляли контакты и попадали в вашу CRM — всё это можно реализовать одним ботом на Go.
В этой статье я расскажу о реальном проекте Telegram Gateway Bot:
Поделюсь готовой архитектурой
Покажу, как настроить миграции и хранение в MySQL
Объясню, как сделать удобную интеграцию с CRM
Расскажу про systemd-деплой и best practices
И немного про "маскотов" и визуальный стиль
Кратко о возможностях
Интерактивная регистрация и сбор e-mail/телефона
Кнопка-приглашение в приватный канал/группу
Проверка членства через Telegram API
Хранение всех лидов в MySQL
Массовая рассылка (только для админов)
Интеграция с CRM через webhook/REST API
Удобные миграции через Goose
Запуск в продакшене как systemd-демон
Поддержка .env для конфигов
Красивый маскот — anime gatekeeper-сорceress
Структура проекта
.
├── README.MD
├── assets
│ └── img
├── cmd
│ ├── main.go
│ └── migrate.go
├── go.mod
├── go.sum
├── internal
│ ├── components
│ │ ├── config
│ │ │ └── config.go
│ │ ├── crm
│ │ │ └── crm.go
│ │ └── db
│ │ └── db.go
│ └── models
│ └── user.go
└── storage
└── migrations
└── 20250701120306_create_users_table.sql
Кратко о каждом файле
main.go
Главный файл, где происходит всё взаимодействие с Telegram через tgbotapi, обработка команд (/start
, /joined
, /check
, /me
, /broadcast
) и "разруливание" сценариев.
Здесь же используется godotenv для загрузки конфигов из .env.
package main
import (
"fmt"
"github.com/digkill/telegram_gateway_bot/internal/components/config"
"github.com/digkill/telegram_gateway_bot/internal/components/crm"
"github.com/digkill/telegram_gateway_bot/internal/components/db"
"github.com/digkill/telegram_gateway_bot/internal/models"
"github.com/joho/godotenv"
"github.com/liudng/godump"
"log"
"os"
"regexp"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
// Загружаем .env (опционально: если не найден — не ругается)
_ = godotenv.Load()
cfg := config.LoadConfig()
conn, err := db.NewDB(cfg.MySQLDNS)
if err != nil {
log.Fatalf("DB connect error: %v", err)
}
if err := conn.Init(); err != nil {
log.Fatalf("DB init error: %v", err)
}
bot, err := tgbotapi.NewBotAPI(cfg.TgToken)
if err != nil {
log.Panic(err)
}
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil {
continue
}
chatID := update.Message.Chat.ID
user := update.Message.From
// Сохраняем пользователя, если новый
conn.CreateUser(user.ID, user.UserName)
urec, _ := conn.GetUser(user.ID)
// --- Broadcast (только для админа)
if update.Message.IsCommand() && update.Message.Command() == "broadcast" && user.UserName == cfg.AdminUser {
text := update.Message.CommandArguments()
users, _ := conn.AllUsers()
for _, u := range users {
bot.Send(tgbotapi.NewMessage(u.UserID, text))
time.Sleep(50 * time.Millisecond)
}
bot.Send(tgbotapi.NewMessage(chatID, "Рассылка отправлена!"))
continue
}
// --- /start
if update.Message.IsCommand() && update.Message.Command() == "start" {
welcome := `
<b>👋 Привет, %s!</b>
Рады видеть тебя в нашем Telegram-боте!
Здесь ты получаешь:
• 🎁 Спец-доступ к приватному каналу
• 📩 Уведомления о новостях и акциях
• 💬 Крутая поддержка
👇 Жми кнопку для вступления!
`
msg := tgbotapi.NewMessage(chatID, fmt.Sprintf(welcome, user.FirstName))
msg.ParseMode = "HTML"
btn := tgbotapi.NewInlineKeyboardButtonURL("Вступить в канал", os.Getenv("INVITE_LINK"))
keyboard := tgbotapi.NewInlineKeyboardMarkup(
[]tgbotapi.InlineKeyboardButton{btn},
)
msg.ReplyMarkup = keyboard
bot.Send(msg)
conn.UpdateStatus(user.ID, "invited")
return
}
// --- /joined (запуск регистрации)
if update.Message.IsCommand() && update.Message.Command() == "joined" {
conn.UpdateStatus(user.ID, "joined")
conn.UpdateStep(user.ID, models.StepWaitEmail)
bot.Send(tgbotapi.NewMessage(chatID, "Введи, пожалуйста, свой email:"))
continue
}
// --- /check (проверка вступления в канал/группу)
if update.Message.IsCommand() && update.Message.Command() == "check" {
chatMember, err := bot.GetChatMember(tgbotapi.GetChatMemberConfig{
ChatConfigWithUser: tgbotapi.ChatConfigWithUser{
ChatID: cfg.ChannelID,
UserID: user.ID,
},
})
if err == nil && (chatMember.Status == "member" || chatMember.Status == "administrator" || chatMember.Status == "creator") {
bot.Send(tgbotapi.NewMessage(chatID, "Ты уже в группе, красавчик!"))
conn.UpdateStatus(user.ID, "joined")
} else {
bot.Send(tgbotapi.NewMessage(chatID, "Похоже, ты ещё не вступил в группу 😔"))
}
continue
}
// --- /me (инфа о себе)
if update.Message.IsCommand() && update.Message.Command() == "me" {
u, _ := conn.GetUser(user.ID)
godump.Dump(u)
msg := fmt.Sprintf("User: %s\nЗарегистрирован: %s\nСтатус: %s\nEmail: %s\nТелефон: %s\nUTM: %s",
u.Username, u.FirstSeen, u.Status, u.Email, u.Phone, u.UTM)
bot.Send(tgbotapi.NewMessage(chatID, msg))
continue
}
// --- Registration steps
step := urec.RegistrationStep
if step == models.StepWaitEmail && !update.Message.IsCommand() {
// Email validation
email := update.Message.Text
emailRegexp := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
if !emailRegexp.MatchString(email) {
bot.Send(tgbotapi.NewMessage(chatID, "Некорректный email, попробуй еще раз:"))
continue
}
conn.UpdateEmail(user.ID, email)
conn.UpdateStep(user.ID, models.StepWaitPhone)
bot.Send(tgbotapi.NewMessage(chatID, "Спасибо! Теперь введи свой телефон:"))
continue
}
if step == models.StepWaitPhone && !update.Message.IsCommand() {
phone := update.Message.Text
phoneRegexp := regexp.MustCompile(`^\+?\d[\d\s\-]{7,}$`)
if !phoneRegexp.MatchString(phone) {
bot.Send(tgbotapi.NewMessage(chatID, "Некорректный номер, попробуй еще раз:"))
continue
}
conn.UpdatePhone(user.ID, phone)
conn.UpdateStep(user.ID, models.StepDone)
bot.Send(tgbotapi.NewMessage(chatID, "Спасибо! Ты успешно зарегистрирован как участник."))
// Интеграция с CRM
u2, _ := conn.GetUser(user.ID)
go crm.SendToCRM(cfg.CRMEndpoint, *u2)
continue
}
}
}
db.go
Обёртки над SQL-запросами к MySQL: добавление/обновление пользователя, получение статуса и шага регистрации, выгрузка всех лидов, обновление контактов и т.д. Всё вынесено в отдельные функции ради чистоты и переиспользуемости.
crm.go
Вся логика интеграции с внешними CRM или Google Sheets. Пример — отправка POST-запроса с инфой о лиде на внешний endpoint. При необходимости модифицируется под любую CRM (amoCRM, Bitrix24 и др.).
config.go
Модуль загрузки переменных окружения (os.Getenv) + парсинг .env через godotenv. Позволяет гибко настраивать бот без правок в коде.
types.go
Здесь объявлены все структуры пользователя (User
), статусы и состояния регистрации (RegistrationStep
), вспомогательные константы. Разделение типов позволяет быстро расширять и масштабировать проект.
migrate.go и /migrations
Миграции через goose — золотой стандарт для production. В /storage/migrations
лежит SQL-файл, который создаёт таблицу users
с нужными полями.
migrate.go
позволяет запускать миграции программно.
Goose можно запускать и через CLI:
goose -dir ./migrations mysql "$MYSQL_DSN" up
.env
TG_TOKEN=ваш_токен_бота
MYSQL_DSN=логин:пароль@tcp(localhost:3306)/leadbot?parseTime=true
CHANNEL_ID=-1001234567890
ADMIN_USER=your_admin_username
CRM_ENDPOINT=https://your.crm/api/leads
INVITE_LINK=https://t.me/joinchat/xxxxxx
Файл не пушится в git (добавьте в .gitignore
!).
Логика работы и фичи
Онбординг пользователя
Пользователь пишет
/start
— получает приветствие с красивой кнопкой для вступления в канал.После вступления пишет
/joined
— бот спрашивает e-mail и телефон, валидирует и сохраняет в MySQL.По команде
/check
бот автоматом проверяет, состоит ли пользователь в канале (через GetChatMember).Все данные лидов можно выгрузить для рассылок или аналитики.
Админские фичи
Команда
/broadcast <text>
доступна только админу (ADMIN_USER
). Позволяет разослать сообщение всем лидам из базы.Возможна интеграция с любым webhook CRM — при завершении регистрации данные улетают на нужный endpoint.
Как развернуть проект
1. Склонировать репозиторий и сконфигурировать .env
git clone https://github.com/yourusername/telegram_gateway_bot.git
cd telegram_gateway_bot
# отредактируйте .env
2. Запустить миграции
go install github.com/pressly/goose/v3/cmd/goose@latest
goose -dir ./migrations mysql "$MYSQL_DSN" up
3. Собрать и запустить локально
go build -o telegram_gateway_bot
./telegram_gateway_bot
или
go run main.go
4. Настроить в продакшене как сервис
Создайте файл /etc/systemd/system/telegram_gateway_bot.service
:
[Unit]
Description=Telegram Gateway Bot
After=network.target
[Service]
Type=simple
WorkingDirectory=/var/www/telegram_gateway_bot
ExecStart=/var/www/telegram_gateway_bot/telegram_gateway_bot
Restart=always
RestartSec=5
User=www-data
EnvironmentFile=/var/www/telegram_gateway_bot/.env
[Install]
WantedBy=multi-user.target
Затем:
sudo systemctl daemon-reload
sudo systemctl enable telegram_gateway_bot
sudo systemctl start telegram_gateway_bot
sudo systemctl status telegram_gateway_bot
Итог
Бот готов для использования как “ворота” в ваши приватные Telegram-сообщества, школы, мастер-майнды и любые лид-магниты.
Он легко кастомизируется, расширяется под любые цели, интегрируется с CRM и работает как сервис на сервере.
Вопросы, доработки, форки?
Пишите в комментарии или следите в Telegram канале за обновлениями и новостями: https://t.me/notecto
P.S.
Готовлю расширенную версию с платёжками, подпиской, доп. аналитикой, и автосегментацией лидов — следите за обновлениями!