Привет! Меня зовут Кирилл Семенко, я AQA в SDET-команде Битрикс24. Сегодня расскажу о проекте GiftsHub — веб-платформе для координации мероприятий и дней рождений, которой пользуются сотрудники компании.
С 2019 года у нас в компании проводят внутренние Битрикс-хакатоны. Эта традиция даёт возможность поработать в новых командах, узнать что-то новое и сделать уникальный и интересный проект. Для лидеров команд это ещё и шанс развить свои способности менеджера и руководителя для достижения цели командой.
На хакатоне 2024 года начался проект, о котором я рассказываю сегодня — Telegram-бот для организации подготовки к праздникам. Потом появились Telegram WebApp / MiniApp, а следом полноценная платформа: отдельный сайт, публичные ссылки, email-вход, realtime и нормальная инфраструктура вокруг backend и frontend.
Технически задача такого приложения выглядит простой: хранить вишлисты и помогать выбрать, что подарить. Самой сложной частью оказалась координация действий всех участников: кто участвует в сборе, какая сумма уже собрана, кто отвечает за покупку подарка и как сохранить сюрприз до нужного момента.
В статье расскажу про путь от быстрого bot-first MVP к самостоятельной продуктовой системе: с доменной моделью, несколькими интерфейсами, realtime и инфраструктурой вокруг backend и frontend.

Посмотреть и попробовать можно здесь
Содержание:
Откуда появилась задача
Исходная боль была жизненной и практической: организовать подарок человеку так, чтобы остальные участники успели все обсудить, собрать деньги, выбрать вариант и не раскрыть сюрприз.
До автоматизации процесс держался на переписках и памяти организатора. Где-то обсуждали идею подарка, где-то уточняли участников, где-то вспоминали, какая сумма уже собрана. Пожелания именинника могли лежать отдельно, договоренности терялись, ответственность расползалась между людьми.
Список желаний сам по себе решается просто: заметки, таблица или любой чат. Сложность начинается вокруг процесса: собрать людей, скрыть подготовку от именинника, избежать дублей при выборе подарка (если все дарят по подарку от себя), сохранить договоренности и вовремя напомнить о событии.
Если такие события происходят раза в 1-2 месяца, все можно вести руками. Когда сценарий повторяется регулярно, ручная организация начинает съедать слишком много времени и внимания. В какой-то момент стало ясно, что люди готовы участвовать, но процесс слишком завязан на одном человеке и множестве мелких сообщений.
Для хакатона это была удобная задача: боль понятная, цикл проверки короткий, сценарий уже существовал в жизни. Первая версия проекта была быстрой проверкой идеи: получится ли убрать часть ручной операционки из подготовки подарков.

Почему стартовали через Telegram
Telegram выбрали как самый быстрый канал для MVP. Пользователи уже там, сценарий организации подарка похож на переписку — кого поздравляем, когда день рождения, кто участвует. Ещё бота можно добавить в группу и сразу проверить базовый путь без отдельного frontend.
Поэтому на хакатоне с Telegram было удобно: можно быстро собрать реально работающий сценарий. Команды, кнопки, callback-события и сообщения позволяли провести пользователя по шагам: добавить бота, создать группу, присоединиться, указать день рождения, начать собирать данные.
Backend на этом этапе обслуживал bot-flow. В центре были обработчики, состояния диалога и тексты сообщений. Это нормальная цена быстрого старта: сначала появляется рабочий путь, потом становится понятно, какие сущности и правила за ним стоят.
Первая версия закрывала базовые вещи: профили пользователей, группы, дни рождения, вишлисты и участников поздравления. Но уже тогда стало видно ограничение: Telegram удобен как вход и канал общения, но в его возможности не помещаются все нужные функции.
Когда одного Telegram стало недостаточно
Поворот случился из-за конкретного тупика.
Главный сценарий сервиса — подготовка подарка для конкретного человека. Остальным участникам нужно место для обсуждения деталей: что дарить, кто участвует в сборе, какая сумма уже собрана. Подготовка при этом остается скрытой от самого именинника.
В Telegram это почти решаемо: можно попробовать тред, топик или отдельное обсуждение. Но если обсуждение живет внутри общего пространства, из него нельзя аккуратно убрать одного человека: то есть человек видит обсуждение собственного подарка, и сценарий ломается.
Второе ограничение рядом: боту недоступно создание управляемого приватного чата нужного состава. То есть бот должен создать отдельный закрытый чат, добавить туда только нужных участников и исключить именинника. В Telegram это невозможно, и для безопасности мессенджера это логично, но для продукта — тупик.

После этого стало ясно, что Telegram не может быть центром модели. Он оставался полезным каналом: привести пользователя, отправить уведомление, дать быструю кнопку. Но правила сценария уже должны были жить внутри продукта: кто участник события, кто именинник, кто может присоединиться и что нужно скрыть.
При создании события дня рождения именинник находится среди пользователей группы, но в участники события не попадает и не видит новый чат с обсуждением подарка на свой день рождения или другой праздник.
const participants = users.filter((user) => {
return user !== birthdayPerson;
});
const event = new EventEntity();
event.birthdayPerson = birthdayPerson;
event.participants = participants;Этот кусок хорошо показывает переход: сначала задача звучала как "создать обсуждение без именинника". Потом она стала backend-правилом. Именинник существует в событии как birthdayPerson и остается вне обсуждения подготовки.
Telegram WebApp / MiniApp как промежуточный этап
Между ботом и полноценной платформой был важный этап — Telegram WebApp / MiniApp.
WebApp позволил оставить Telegram входной точкой, но вынести сложный интерфейс туда, где уже можно нормально управлять состоянием, навигацией, событиями, списками и чатами.

3 февраля 2025 рядом с ботом вышло в релиз наше приложение Telegram WebApp, которое открывался прямо из Telegram. Внутри уже были NestJS, Angular, Postgres, Socket.IO и JWT. Часть функций оставалась в боте, часть переезжала на frontend.
WebApp закрыл то, что плохо помещалось в формат команд и сообщений: просмотр хотелок, личные списки, обсуждение подарка во внутреннем чате. Там, где бот превращался бы в длинную цепочку кнопок, WebApp давал нормальный экран.
Авторизация тоже стала переходной. Пользователь приходит из Telegram, backend проверяет initData, дальше выдает access и refresh JWT в cookie. Вход еще был завязан на Telegram-контекст, но приложение уже работало как web-клиент с собственной сессией.
if (this.authService.validateInitData(body.initData, body.hash)) {
const user = await this.createUserIfNotExists(body.initData, body.userId);
const accessToken = this.authService.generateAccessToken(user.id, user.tokenVersion, user.role);
const refreshToken = this.authService.generateRefreshToken(user.id, user.tokenVersion, user.role);
AuthService.setTokensCookie(res, { accessToken, refreshToken });
}На этом этапе проект получил два слоя взаимодействия: Telegram приводил пользователя и отправлял уведомления, WebApp забирал на себя продуктовый интерфейс.
Что на самом деле делает продукт
Список сущностей звучит скучно: пользователи, группы, события, вишлисты, сообщения. Польза проекта раскрывается в сценариях между ними.

Первый сценарий — вишлист. Это список желаний, у которых есть название, ссылка, описание, цена и картинка. В подарочной логике есть важная деталь: участвующие в поздравлении могут забронировать пункт из вишлиста, который решили купить. Тогда несколько человек не купят случайно одно и то же. Сам владелец wishlist не видит, что кто-то забронировал в его вишлисте один из подарков.
Эту логику держит API. Когда wishlist смотрит владелец, backend скрывает данные бронирования.
if (isOwner) {
dto.isLocked = false;
dto.lockedBy = null;
} else if (dto.lockedBy && dto.lockedBy.id !== req.userId) {
dto.lockedBy = null;
}Одна и та же запись выглядит по-разному для разных пользователей:
Для владельца это просто пункт его вишлиста.
Для участника подготовки — подарок, который можно забронировать или увидеть как занятый.
Для пользователя, который забронировал желание — его ответственность.
Второй сценарий — группы и события. Это пространства подготовки подарка: участники, обсуждение, именинник, invite-ссылка, ответственный за сбор суммы на подарок и правила доступа. И ещё публичность: если пользователь не зарегистрирован на платформе и ему скинули ссылку на вишлист пользователя, ссылка откроется без авторизации.
Третий сценарий — внутренний чат события. Telegram не дал управляемое пространство для приватного обсуждения подарка, поэтому обсуждение переехало внутрь продукта.
В WebSocket-слое тоже есть проверка доступа на основе встроенной механики room. Пользователь не может подключиться к комнате события, если он не участник.
const hasAccess = user?.events?.some((event) => {
return event.id.toString() === payload.room.toString();
});
if (!hasAccess) {
return;
}
client.join(payload.room);Чтобы участники не возвращались к обсуждению в обычных чатах, внутренний чат должен закрывать привычные сценарии: показывать не прочитанные сообщения, поддерживать реакции, ответы на конкретные сообщения. Тогда обсуждение подарка остается удобным внутри события, и договоренности не расползаются обратно по личным сообщениям и отдельным чатам.
Четвертый сценарий — сбор суммы на подарок. В раннем посте это была одна из главных болей: нужно понимать, кто участвует в сборе и какая сумма уже собрана. В текущей системе у события есть ответственный за сбор, собранная сумма, платежные данные и отдельная логика платежей.
В итоге сервис занимается не хранением данных о подарках, а координирует подготовку: кто участвует в сборе, что обсуждают, какая сумма уже собрана и кто отвечает за покупку подарка.
Почему backend стал центром
Когда был только бот, часть логики можно было держать в обработчиках. Пользователь нажал кнопку, бот ответил, состояние изменилось. То есть backend был, но только в роли сервера для Telegram-бота: принимал события из Telegram и отвечает на них.
Когда появились Telegram WebApp, отдельный сайт, публичные ссылки, email-вход, realtime и админские сценарии, такая схема перестала работать. Один и тот же пользователь может прийти из MiniApp, зайти через браузер, открыть публичный wishlist или попасть в событие через invite. Во всех случаях система должна точно понимать, кто он и что ему можно делать.
Так backend перестал быть службой при боте. Он стал местом, где живут правила: доступ к событиям, состав участников, скрытие бронирований, авторизация. A Telegram — один из клиентов рядом с web, публичными страницами и realtime.
Frontend здесь тоже важен: WebApp и отдельный сайт сделали продукт понятным для пользователя. Backend держит правила, но ценность видна через интерфейсы: список желаний, карточки событий, чат, публичные страницы, регистрацию, invite-ссылки и нормальную навигацию.

Когда проект вышел за пределы Telegram
Следующий большой шаг — отдельный сайт и публичные сценарии.
До этого Telegram все еще оставался обязательным контекстом. Даже с WebApp-интерфейсом регистрация была возможна только через бот. Это удобно для старта, но ограничивает продукт. Поэтому понадобилась вторая точка входа.
Публичные wishlist-страницы поменяли модель и стали первым сценарием, где Telegram больше не нужен: пользователь может открыть ссылку вида /u/:username и посмотреть желания человека, если тот дал на это согласие.
В backend появился публичный контроллер, который отдает wishlist по username, но не раскрывает лишнего. В публичной выдаче остаются только данные, которые можно показывать внешнему пользователю.
return {
name: dto.name,
description: dto.description,
link: dto.link,
price: dto.price,
image: dto.image,
isGifted: dto.isGifted,
};Во frontend под это есть отдельный route без авторизации.
{
path: 'u/:username',
children: [ ... ]
}Похожая история с invite flow. Событие можно открыть по токену, посмотреть базовую информацию и присоединиться, если правила это позволяют. Канал входа меняется, правила сценария остаются общими.
Позже появилась email-регистрация. Она сняла обязательную привязку к Telegram, который перестал быть условием существования пользователя в системе.
Изначально Telegram-группы помогали управлять составом внутренних групп событий: через них добавляли и исключали пользователей. Сейчас Telegram можно подключить как канал уведомлений о новых событиях, назначенных ответственных и днях рождения в календаре Ещё авторизованным через Telegram пользователям приходят личные уведомления через бота.
На этом этапе проект стал платформой с несколькими точками входа: Telegram MiniApp, отдельный сайт, публичные ссылки, email-аккаунт. Все они обращаются к одной продуктовой модели.

Инфраструктура как следствие боли
Инфраструктура обычно появляется, когда менять проект становится страшно.
Пока проект был ботом, многое можно было проверить руками: поправить обработчик, нажать пару кнопок, посмотреть ответ Telegram. Для MVP терпимо.
Когда один backend обслуживает Telegram, MiniApp, отдельный web, публичные страницы, realtime, фоновые задачи и уведомления, цена ошибки возрастает. Одно изменение может сломать авторизацию, чат, деплой.
Отсюда вырос отдельный orchestration-слой. Он собирает вокруг продукта backend, frontend, nginx, Redis, docker-compose, CI/CD и Makefile-команды. Этот слой не виден пользователю, зато продукт удобнее развивать и выкатывать.
В docker-compose видно, что система живет как набор связанных сервисов.
services:
nginx:
depends_on:
backend:
condition: service_healthy
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
backend:
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:${APP_PORT}/api/health || exit 1"]Nginx ждет здоровый backend, Redis поднимается отдельно, backend имеет healthcheck, frontend собирается своим слоем. На этом этапе продукт уже нужно собирать, выкатывать и проверять как систему.
CI/CD закрепил ту же логику. В pipeline появились стадии validate, prepare, build, deploy, verify. Makefile закрыл повторяемые команды: деплой всего продукта, отдельно frontend, отдельно backend, dev-ветка, проверки статуса и синхронизация submodules.
Redis здесь тоже появился из практической нагрузки: Telegram-сессии, throttling, rate limiting и кеширование tokenVersion для auth-запросов. Он снижает лишние обращения к базе и убирает часть состояния из памяти процесса.
Текущая форма продукта
К этому этапу система уже объединяла вишлисты, события, участников, обсуждения, бронирования, сбор суммы, уведомления и публичные ссылки. Telegram MiniApp и бот остались удобными точками входа, а общая логика переехала в backend и frontend.
Пользователь может:
Прийти через Telegram, открыть MiniApp и работать внутри привычного мессенджера.
Зайти на сайт.
Зарегистрироваться по email.
Поделиться публичной ссылкой на wishlist.
Попасть в событие через invite.
Для продукта это разные входы в одну и ту же модель.
В отчетном посте на апрель 2026 уже было 455 человек — без дополнительного привлечения и рекламы. Для pet/side/hackathon-проекта это хороший сигнал: сценарий не был выдуманным. Людям действительно нужен способ проще координировать подарки и не держать все в чатах.
Итоговая схема: Telegram остался каналом входа и уведомлений. MiniApp дал удобный интерфейс внутри Telegram. Отдельный сайт убрал обязательную зависимость от Telegram. Backend стал местом, где живут правила продукта. Realtime закрыл обсуждения событий. Инфраструктура сделала все это сопровождаемым.

Что я бы сделал раньше
Старт через Telegram был правильным решением для первой проверки идеи. Пользователи уже были в мессенджере, сценарий был разговорным, а бот позволял быстро собрать рабочий путь без большого frontend.
Но границу между каналом и продуктовой моделью я бы провел раньше.
Удачный старт легко спутать с удачной долгосрочной архитектурой. Telegram помог быстро появиться, но потом начал диктовать ограничения там, где продукту уже нужны были собственные правила: приватность обсуждений, роли участников, события, бронирования, invite-ссылки и разные точки входа.
Backend быстро выходит за рамки CRUD, если продукт не просто хранит данные, а координирует действия нескольких людей. Пользователь участвует в процессе: обсуждает, бронирует, присоединяется, участвует в сборе, получает уведомления, видит одну часть информации и не видит другую.
Инфраструктуру лучше не откладывать до момента, когда менять уже страшно. Миграции, health checks, CI/CD, Redis, воспроизводимое окружение и нормальная схема деплоя выглядят избыточно, пока проект маленький. Потом внезапно оказывается, что без них каждое изменение превращается в маленькое гадание.
Ссылка на текущую версию приложения: https://gifth.ru/
Если сжать весь путь проекта до одной мысли, она будет такой: быстрый внешний канал помогает стартовать, но продукт должен вовремя стать самостоятельной системой.
Проект перерос Telegram по простой причине: реальный сценарий организации подарков потребовал собственной модели приватности, ролей, событий, обсуждений и точек входа.




















