Сообщение «опять не работает» само по себе почти ничего не говорит. Что не работает: принтер, доступ к CRM, VPN? Что значит «опять»? Но если то же сообщение пришло внутри обращения, где час назад специалист просил перезагрузить роутер, а сотрудник прислал скриншот ошибки, оно превращается в ясный сигнал: перезагрузка не помогла. Вся разница между чатом и поддержкой — в этой рамке вокруг сообщения. Построить её нужно самим: Telegram доставляет сообщения, а понятия «обращение», «ответственный» и «история» живут в вашем собственном сервисе.

Ниже разберём, как устроить такой сервис для внутренней поддержки: где кончается сообщение и начинается обращение, как закрепить за тикетом файлы и людей, как передать дело другому специалисту, не показав лишнего, и что делать, когда Telegram временно недоступен.

Сообщение — это событие, обращение — это состояние

Бот получает поток отдельных событий. Каждое — это текст, фото, документ или нажатие кнопки от конкретного пользователя в конкретном чате. У события нет памяти о том, что было раньше. Обращение, напротив, — это долгоживущая сущность со своим номером, статусом, темой и списком участников. Задача бота — при каждом входящем событии ответить на один вопрос: к какому обращению оно относится?

Есть три разумных способа ответить, и у каждого своя цена.

  • Один открытый тикет на человека. Пока обращение не закрыто, всё, что пишет сотрудник, попадает в него. Просто и предсказуемо, но смешивает темы: человек, у которого висит вопрос про почту, напишет про сломанный монитор — и монитор окажется в «почтовом» тикете.
  • Явная команда для новой темы. Сотрудник нажимает кнопку «Новое обращение», и только тогда создаётся тикет. Границы чёткие, но люди забывают нажимать и продолжают писать в старую тему.
  • Ответ на конкретное сообщение. Привязка идёт через reply: если сотрудник отвечает на сообщение бота из тикета, оно уходит в тот тикет. Точно, но требует дисциплины и плохо работает с пересланными сообщениями.

На практике хорошо работает сочетание. Если у человека есть ровно один открытый тикет, а сообщение пришло без явного контекста, бот добавляет его туда и в ответе показывает номер: «Добавлено в обращение №412». Если открытых тикетов несколько, бот не угадывает, а спрашивает кнопками, к какому из них относится сообщение или это новая тема. Кнопка «Новая тема» всегда рядом. Лишний вопрос стоит секунду; тихая ошибка привязки стоит специалисту часов разбирательства.

Отдельно решите, когда тема считается новой автоматически. Разумное правило — по состоянию, а не по времени: если последний тикет закрыт, любое новое сообщение открывает следующий. Если же закрыт совсем недавно, бот может предложить выбор: «Это продолжение №412 или новая проблема?» — это подводит нас к повторному открытию, о котором речь пойдёт позже.

Номер, который не меняется

Устойчивый номер тикета — опора всей системы. Его выдаёт ваша база данных, а не Telegram: идентификаторы сообщений и чатов принадлежат платформе, они нужны для ответов и ссылок, но обращением не являются. Хорошая схема хранения выглядит так.

  • Таблица обращений: номер, тема, статус, кто создал, текущий ответственный, время создания и последнего движения.
  • Таблица сообщений: ссылка на номер обращения, автор, направление (от сотрудника, от специалиста, служебное), текст, время, идентификатор сообщения в Telegram.
  • Таблица вложений: ссылка на сообщение, тип, имя, размер, а также идентификатор файла в Telegram и, при необходимости, копия в вашем хранилище.
  • Таблица назначений: кто, когда и на каком этапе стал ответственным.

Идентификатор Telegram в таблице сообщений нужен по двум причинам: чтобы отвечать на нужное сообщение и чтобы не обработать одно и то же событие дважды. Если по какой-то причине обновление придёт повторно, уникальное ограничение на пару «чат + идентификатор сообщения» не даст создать дубль. Это защита именно от повторной доставки, а не от того, что человек сам отправил два одинаковых сообщения — те останутся двумя записями, как и должно быть.

С файлами стоит быть осторожнее. Ссылаться только на идентификатор файла в Telegram удобно, но тогда скриншот ошибки существует лишь пока он доступен через платформу. Для внутренней поддержки, где скриншот может понадобиться через месяц, разумно сохранять копию на своей стороне и хранить путь к ней в записи вложения. Это решение о политике хранения: оно требует места на диске и срока жизни файлов, который вы определяете сами.

Один тикет, два специалиста: как проходит передача

Посмотрим на одно обращение целиком. Условный пример: бухгалтер Ирина пишет боту «не открывается отчёт в 1С». Бот создаёт тикет №412, автоматически назначает дежурного — первую линию, специалиста Максима. Максим спрашивает, какая ошибка, Ирина присылает скриншот, Максим просит очистить кэш и перезапустить программу. Через десять минут Ирина пишет «опять не работает».

Здесь видна ценность рамки. Максим видит в карточке тикета: тема, скриншот с текстом ошибки, его собственная рекомендация и новое сообщение. Он понимает, что простое лечение не помогло, и передаёт тикет второй линии — администратору Олегу. Что происходит при передаче?

  1. В таблицу назначений добавляется запись: от Максима к Олегу, время, комментарий Максима.
  2. В обращении меняется поле текущего ответственного. Старые сообщения не трогаются и не копируются в новый тикет — история остаётся единой.
  3. Олегу приходит уведомление со сводкой: что сломано, что уже пробовали, что приложено.
  4. Ирине бот сообщает, что обращение передано специалисту второй линии, но не заставляет её повторять рассказ.
  5. Максим теряет право писать от имени ответственного, но, если политика позволяет, сохраняет доступ на чтение к тем тикетам, где он участвовал.

Обратите внимание на последний пункт: передача ответственности и передача права читать — разные вещи, и их стоит разделять явно. Одни команды разрешают Максиму видеть историю после передачи (ему может понадобиться вернуться к делу), другие — нет. Решение зависит от вашей внутренней политики, но принимать его нужно осознанно, а не получать по случайности реализации.

Проследим это на тикете №412. В момент передачи у обращения два связанных набора прав, и они меняются по-разному. Право писать от имени ответственного переходит от Максима к Олегу сразу: с этой секунды сообщения Ирины бот направляет Олегу, а кнопки «Решено» и «Передать дальше» у Максима перестают работать. Право читать устроено иначе: Максим остаётся в списке тех, кто когда-то вёл дело, и видит историю, пока действует политика. Такое различие нужно для конкретной ситуации. Если Олег через час спросит: «Что именно ты просил очистить?», Максим сможет открыть свою же переписку и ответить точно, а не по памяти.

Передачу удобно записывать как отдельное событие в самой истории тикета, а не только в таблице назначений. Строка вида «Максим → Олегу: простые действия не помогли» видна всем участникам и сразу объясняет, почему в переписке меняется тон и появляются другие вопросы. Если дело вернётся к Максиму, это тоже будет отдельная запись, а не молчаливая смена поля. Так по одному обращению всегда можно восстановить цепочку: кто владел делом, сколько оно у кого пролежало и на каком шаге его передавали.

Есть и обратная сторона. Если оставлять доступ на чтение всем прежним ответственным навсегда, круг людей, видящих тикет, только растёт. Для обычных обращений вроде «не открывается отчёт» это терпимо. Для чувствительных тем — зарплата, подозрение на утечку — разумнее ограничить срок: например, чтение сохраняется, пока тикет не закрыт, а затем остаётся только у автора, текущего ответственного и ролей руководителя или аудитора.

Права читать зависят от обращения, а не от знания номера

Самая частая ошибка в простых ботах поддержки — команда вида «/ticket 412», которая показывает историю любому, кто знает номер. Номера идут подряд, и подобрать чужой тикет — дело трёх нажатий. Если в тикете №413 сотрудник описывает проблему с зарплатной ведомостью, а в №414 — подозрение на утечку доступа, подобная открытость недопустима.

Правило должно звучать так: право читать тикет определяется связью человека с этим тикетом, а не знанием его идентификатора. Конкретно, читать историю обращения может:

  • автор обращения;
  • текущий ответственный;
  • те, кто был ответственным ранее (если это разрешено политикой);
  • специалисты с ролью руководителя линии или аудитора — по ролям, а не по случайным исключениям.

Проверку делает ваш backend при каждом запросе, а не интерфейс. Если проверять только на уровне кнопок («не показывать кнопку чужому»), любой, кто отправит команду с чужим номером вручную, обойдёт защиту. Поэтому функция «получить историю обращения» принимает два аргумента — номер и пользователя — и сама решает, отдавать ли данные. Для чужого тикета ответ должен быть таким же, как для несуществующего: «обращение не найдено». Тогда по ответам нельзя перебором выяснить, какие номера заняты.

Разберём проверку на примере двух запросов к одному и тому же номеру. Олег, ставший ответственным по №412, запрашивает историю: backend находит в таблице обращений связь «текущий ответственный» и отдаёт переписку вместе с вложениями. Тот же Олег запрашивает №413: связи нет, роли руководителя у него нет, значит, ответ «обращение не найдено». Код, который решает вопрос, один и тот же; различаются только данные о связи человека с обращением.

Эту же проверку нужно повторять во всех местах, где всплывает содержимое тикета, а не только в команде просмотра истории. Типичные дыры:

  • Вложения. Если файл отдаётся по прямому пути или ссылке без проверки, скриншот из закрытого тикета доступен тому, кто угадал адрес. Скачивание должно проходить через ту же функцию, что и чтение переписки.
  • Поиск. Поиск по тексту обращений обязан сначала отобрать тикеты, доступные пользователю, и только затем искать внутри них; иначе в выдаче проскользнут чужие фрагменты.
  • Уведомления. Сводка о передаче уходит только новому ответственному и автору. В групповой чат её отправлять нельзя, даже если она выглядит безобидной: в строке «что сломано» может оказаться то самое описание проблемы с ведомостью.
  • Кнопки с номером. Данные кнопки приходят от клиента и могут быть подделаны, поэтому номер из них проверяется так же, как номер из набранной команды.

История тикетов и роли — функции вашего собственного сервиса: Bot API доставляет сообщения и обновления, но не знает, кто из сотрудников какую линию поддержки ведёт. Проверку на уровне приложения можно дополнить ограничениями на уровне базы данных. В PostgreSQL права SELECT, INSERT, UPDATE и DELETE — это разные права (документация PostgreSQL о привилегиях). Это даёт простую и полезную схему: учётная запись, под которой бот принимает сообщения, получает INSERT на таблицу сообщений и SELECT, но не DELETE и не UPDATE; тогда ошибка или взлом приложения не позволят стереть историю. Отдельная учётная запись для отчётов получает только чтение. Нужно помнить и о другом: владелец объекта обладает полномочиями, которые не сводятся к выдаваемым правам чтения (там же). Поэтому приложение не должно работать под учётной записью, владеющей таблицами; владельца оставьте для миграций и держите в стороне от повседневной работы бота.

Как выглядит отказ

Допустим, Олег пытается открыть №413, где он не участвует. Backend проверяет связь пользователя с обращением, не находит её и отвечает общей фразой. В журнал аудита записывается попытка: кто, какой номер, когда. Раз в неделю руководитель может просмотреть такие записи. Это не слежка, а способ вовремя заметить, что кто-то перебирает номера или что права назначены неверно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Ожидание, закрытие и повторное открытие

Статусы — это язык, на котором тикет говорит, чего он ждёт. Слишком много статусов превращают работу в бюрократию, слишком мало — скрывают суть. Для внутренней поддержки хватает пяти.

Статусы обращения и кто сейчас должен действовать
СтатусЧего ждёмЧто делает бот
НовоеВзятия в работуНазначает дежурного, напоминает ему, если тикет не взят
В работеДействий специалистаПередаёт сообщения сотрудника ответственному
Ждём ответа сотрудникаИнформации от автораНапоминает автору, не беспокоя специалиста
РешеноПодтверждения от автораСпрашивает, помогло ли, и ждёт заданное время
ЗакрытоНичегоНовое сообщение предлагает открыть заново или создать новое

Статус «Ждём ответа сотрудника» полезен потому, что снимает тикет с радара специалиста: пока человек не ответил, торопить некого. Переключается он автоматически: специалист задал вопрос — статус сменился; сотрудник ответил — вернулся «В работе». Так очередь второй линии не забита делами, которые стоят не по её вине.

Закрытие лучше делать двухшаговым. Специалист ставит «Решено», бот спрашивает автора: «Всё заработало?» Если автор ответил «да» или молчал заданный срок, тикет закрывается. Если написал «нет» или «опять не работает» — возвращается в работу тому же ответственному, а не в общую очередь. Это важно: специалист, который уже знает контекст, быстрее закончит дело, чем новый.

Теперь вернёмся к Ирине. Максим поставил «Решено» после своей рекомендации, но через десять минут она ответила «опять не работает». Бот видит, что тикет в статусе «Решено», а сообщение пришло от автора, и не создаёт новое обращение: он возвращает №412 в работу тому, кто ставил «Решено». Дальше возможны две ветки, и важно, чтобы бот различал их. Если к этому моменту дело уже передано Олегу, возвращать его Максиму бессмысленно, поэтому правило звучит так: возврат идёт к ответственному, который был в момент закрытия, а не к первому в истории. Когда Максим передал тикет и Олег поставил «Решено», именно Олег получит повторное обращение.

Полезно хранить и причину возврата. Запись «возвращено автором: проблема повторилась после очистки кэша» отличается от «возвращено по таймеру» или «возвращено вручную руководителем». По накопленным причинам видно, какие решения на самом деле не решают: если «Решено» регулярно возвращается по одной теме, значит, в инструкции первой линии есть шаг, который лишь выглядит рабочим.

Срок молчания перед автоматическим закрытием тоже стоит привязать к теме обращения. Для «не открывается отчёт» достаточно короткого окна, потому что результат видно сразу. Для задач вроде «обновите права доступа» человек может проверить результат только на следующий рабочий день, и слишком короткое окно закроет тикет раньше, чем автор успеет заметить, что ничего не изменилось.

Повторное открытие — тонкое место. Если сообщение пришло вскоре после закрытия, велика вероятность, что это продолжение. Ваш сервис может задать окно (например, несколько дней, значение выбираете вы по характеру задач) и в его пределах предлагать выбор: «Вернуться к №412 или создать новое обращение?» Вне окна — создавать новое, но с пометкой «связано с №412». Такая ссылка стоит копейки, а специалисту сразу показывает, что у проблемы есть предыстория.

Когда Telegram недоступен, а работа не ждёт

Внутренняя поддержка не должна зависеть от того, что именно сегодня отвечает внешняя платформа. Для этого важно понимать, где кончается ответственность Telegram. Входящие обновления бот получает одним из двух способов: через webhook или через getUpdates, и они взаимоисключающие; кроме того, необработанные обновления хранятся ограниченное время (описание в Bot API). Отсюда следуют практические выводы.

  • Если ваш приёмник обновлений долго не работал, часть сообщений за этот период может не дойти. Платформа не обязана хранить их, пока вы не вернётесь.
  • Нельзя одновременно держать включённый webhook и опрашивать getUpdates — например, запустив второй экземпляр бота для отладки на другой машине. Это одна из самых частых причин «пропавших» сообщений.
  • Что бы ни происходило снаружи, всё уже принятое должно лежать в вашей базе, а не в памяти процесса и не в очереди, которую можно потерять при перезапуске.

Поэтому порядок обработки входящего события такой: сначала запись в собственную базу, затем всё остальное — уведомление ответственного, ответ сотруднику. Если отправка уведомления не удалась, запись об этом остаётся, и повторную попытку сделает фоновая задача. Завершать обработку обновления лучше только после сохранения, чтобы не потерять данные при сбое между этими шагами. Учтите: запись в базу и вызов Telegram — две разные системы, общей транзакции у них нет. Поэтому для исходящих сообщений заведите в базе статус отправки («ожидает», «отправлено», «ошибка») и повторяйте только то, что ещё не подтверждено, сверяясь с сохранённым идентификатором сообщения.

Рассмотрим сбой на конкретном шаге. Ирина присылает скриншот, бот сохраняет сообщение и запись о вложении в базу, а затем пробует уведомить Олега, и именно в этот момент связь с Telegram обрывается. Если обработка была построена наоборот, то есть сначала уведомление, потом запись, то Олег не узнает о скриншоте, а в истории его не будет. При правильном порядке картина другая: в базе есть сообщение, есть вложение, а у уведомления статус «ожидает». Фоновая задача при следующем проходе найдёт такие записи и отправит их; Олег получит сообщение с опозданием, но ничего не потеряется.

Тут есть тонкость с повторной отправкой. Если запрос к Telegram ушёл, а ответ до вас не дошёл, статус останется «ожидает», хотя Олег уже мог получить сообщение. Поэтому уведомление стоит делать безопасным к повтору: в тексте указывать номер обращения и порядковый номер события («№412, событие 7»), чтобы дубль был очевиден читателю и не воспринимался как новое сообщение.

Сам файл тоже нужно сохранять до того, как вы отчитаетесь об успехе. Порядок для вложения: скачать, записать в своё хранилище, создать запись о вложении, только потом подтверждать Ирине получение. Если скачивание упало, лучше честно попросить прислать файл ещё раз, чем создать запись о скриншоте, которого на самом деле нет.

Что остаётся доступным в собственной базе, если Telegram недоступен? Всё главное: тикеты, история, назначения, вложения, журнал аудита. Специалист может открыть панель или выгрузку и увидеть полную картину, ответить позже, когда канал вернётся. Сотрудникам, которым срочно, можно оставить запасной путь — почту или внутренний телефон, — а бот при восстановлении сообщит им о состоянии обращений. Запасной канал — это решение организации, и важно заранее назвать его в приветствии бота.

Где это размещать

Такому сервису нужен постоянно работающий процесс, база данных и место для файлов. Для небольшой команды это вполне посильно одному виртуальному серверу: на VPS MAATRIX доступны NVMe-диски, процессоры AMD EPYC и root-доступ, так что можно поставить приёмник обновлений, PostgreSQL и каталог вложений на одну машину и настроить отдельные учётные записи БД, о которых шла речь выше. Линейка тарифов начинается с Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) и доходит до Djed (16 vCPU, 32 ГБ, 320 ГБ); для бота с базой тикетов на старте логичнее смотреть на младшие варианты и ориентироваться на реальную нагрузку, а не на цифры в каталоге: заявленные ресурсы не равны обещанию ёмкости, и объём диска зависит прежде всего от того, сколько вложений вы храните и как долго.

Если вы уже думаете об уведомлениях для групп людей, соседняя тема — рассылки по подписчикам в Telegram: там другие требования к спискам и скорости отправки, но принципы хранения состояния в своей базе схожи. Резервное копирование базы и файлов настраивается вами отдельно; не стоит исходить из того, что оно включено в тариф по умолчанию.

Сводка, которая экономит первые пять минут

Вернёмся к тикету №412. Олег получил его не как загадочное «опять не работает», а в виде короткой карточки, которую бот собирает при каждой передаче. Её структура одинакова для всех обращений, и в этом её сила:

Обращение №412. Не открывается отчёт в 1С, автор — Ирина (бухгалтерия).
Что сломано: при открытии отчёта появляется ошибка, скриншот приложен.
Что уже пробовали: очистка кэша и перезапуск программы по рекомендации Максима — без результата.
Кто ждёт ответа: Ирина ждёт решения от второй линии; вопросов к ней пока нет.

Три строки отвечают на три вопроса, с которых начинает любой следующий специалист: в чём проблема, что уже исключено и чьё сейчас движение. Если вы реализуете в боте только одну «умную» функцию, пусть это будет она: автор передачи пишет короткий комментарий, бот подтягивает из тикета вложения и последние действия, а получатель с первой секунды работает с делом, а не с расследованием. Сообщение «опять не работает» остаётся тем же, что и было, но теперь за ним стоит номер, история и человек, которому оно адресовано.