Telegraft

Справочник по Bot API

Что на самом деле доказывает успешная отправка

Успешный вызов `sendMessage` возвращает созданный объект `Message`, и это подтверждает, что Telegram принял и сохранил сообщение. Это не подтверждает, что устройство получателя его получило, что чат был открыт или что кто-то его прочитал. Bot API не отдаёт ни подтверждений прочтения, ни статуса доставки по каждому получателю.

Нет подтверждений доставки и прочтения: точные цифры

Подтверждения прочтения
Нет. Bot API их не отдаёт
Подтверждения доставки
Нет по получателям. Код 200 означает, что Telegram принял сообщение
Наблюдаемые сигналы
Нажатия кнопок, ответы, полезная нагрузка диплинков
Явный отказ
Блокировка пользователем, приходит перехватываемой ошибкой

As of 2025-10-01, Telegram Bot API 13.4

Что это значит на практике

Это намеренное свойство приватности, а не пробел, и сказать это прямо стоит, потому что оно противоречит тому, чего ждут от канала общения заказчики. Любой, кто покупал рассылки по SMS или почте, приходит с ожиданием доли доставленных и доли открытий. Здесь нет ни того, ни другого. Панель подрядчика, показывающая «98% доставлено» по рассылке в Telegram, отчитывается о том, сколько вызовов API вернули 200, — это другая величина под знакомым именем.

Наблюдать бот может взаимодействие, и оно честнее доли открытий. Нажатая инлайн-кнопка порождает `callback_query` с указанием пользователя и сообщения. Диплинк с полезной нагрузкой фиксирует, какой точкой входа воспользовались. Ответ однозначен сам по себе. Это настоящие признаки внимания, а не приближения к нему, и для большинства продуктов они отвечают ровно на тот вопрос, который подменяла доля открытий.

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

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

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

Как это обрабатывать в коде

// The honest engagement signal: an explicit acknowledgement, not an inferred read.
await bot.api.sendMessage(chatId, 'Your delivery is 10 minutes away.', {
  reply_markup: {
    inline_keyboard: [[{ text: 'Got it', callback_data: 'ack:' + jobId }]],
  },
})

bot.on('callback_query:data', async (ctx) => {
  const [kind, id] = (ctx.callbackQuery.data ?? '').split(':')
  if (kind !== 'ack' || id === undefined) return

  await recordAcknowledged(id, ctx.from.id)
  // Answering closes the client-side spinner. Skipping it leaves the button
  // looking broken for several seconds, which reads as an unresponsive bot.
  await ctx.answerCallbackQuery({ text: 'Thanks' })
})
Одно нажатие превращает ненаблюдаемое прочтение в зафиксированное событие с пользователем и меткой времени — а именно это и пытались приблизить долей открытий.

Какие вопросы это порождает

Можно ли узнать, открыл ли конкретный человек чат с ботом?

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

Что тогда означает графа «доставлено» в инструменте рассылок?

Она означает, что API принял отправку. Число полезное: из него исключены заблокировавшие бота и неудавшиеся вызовы, — но это не доставка в том смысле, в каком её понимает отчёт по SMS, и подавать её так значит создавать ожидание, которого платформа не выполнит.

Есть ли способ понять, что подписчик бота неактивен?

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

Помогает ли чем-то объект сообщения, который вернул sendMessage?

Он даёт `message_id`, который нужен, чтобы позже отредактировать или удалить сообщение, и метку времени, когда Telegram его принял. И то и другое полезно для эксплуатации бота. И ни то ни другое ничего не говорит о получателе.

Смежные ограничения