Messages

sendMessage

A practical guide to sendMessage: sending text, HTML markup, escaping, working with chat_id and handling API errors in PHP with cURL.

sendMessage — the main method of the Bot API for sending text messages. In this chapter we\\'ll cover required parameters, safe handling of HTML markup, common errors, and how to check the API response in PHP.

Method signature and required fields

The method takes chat_id (delivery target) and text (content up to 4096 characters). All other parameters are optional: parse_mode, disable_web_page_preview, reply_markup and others.

We use the function telegramApi() which we created in chapter Первый запрос: getMe. Here we only call it — we don\\'t copy the implementation.

<?php
// reminder: telegramApi() из главы first-request
// function telegramApi(string $token, string $method, array $params = []): array { ... }

$token = getenv('TELEGRAM_BOT_TOKEN') ?: exit('TELEGRAM_BOT_TOKEN not set');
$chatId = 123456789; // замените на реальный chat_id

$result = telegramApi($token, 'sendMessage', [
'chat_id' => $chatId,
'text' => 'Привет! Это тестовое сообщение.',
]);

// Как посмотреть результат в терминале:
print_r($result);
// или для красивого JSON:
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

Example of successful API response

{
"message_id": 42,
"date": 1725000000,
"chat": {
"id": 123456789,
"type": "private",
"first_name": "Ivan",
"username": "ivan_test"
},
"text": "Привет! Это тестовое сообщение."
}

What to look at: message_id — unique identifier of the sent message (needed for editMessageText, deleteMessage); date — timestamp of delivery; chat — chat object confirming delivery. If field ok in the wrapper equals true — the message has been queued in Telegram.

HTML parse_mode and escaping

Telegram does not automatically escape text. Sending user input without escaping in HTML mode breaks the markup and opens a vector for link/tag injections. Always use htmlspecialchars with flags ENT_QUOTES | ENT_SUBSTITUTE and UTF-8 encoding.

We use the function telegramApi() from chapter Первый запрос: getMe.

<?php
$safe = static fn(string $s): string =
htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

$name = $_POST['name'] ?? 'Аноним';
$email = $_POST['email'] ?? 'не указан';

$result = telegramApi($token, 'sendMessage', [
'chat_id' => $chatId,
'parse_mode' => 'HTML',
'text' => "<b>Новая заявка</b>\n"
. "Имя: {$safe($name)}\n"
. "Email: {$safe($email)}",
]);

echo "OK, message_id: {$result['message_id']}\n";

Supported HTML tags: <b>, <i>, <u>, <s>, <code>, <pre>, <a href="...">, <tg-spoiler>. Line break — ordinary \n. More details on markup and MarkdownV2 — in the next chapter.

chat_id parameter: where to get it and limitations

  • Private chat: user_id (number), which comes in message.chat.id updates. The bot cannot write to a user first if that user hasn\\'t clicked /start or written to the bot.
  • Group/supergroup: negative chat_id (e.g., -1001234567890). The bot must be a member.
  • Channel: @channelusername (public) or numeric chat_id (private). The bot must be an administrator of the channel.

You can obtain chat_id by outputting $update['message']['chat']['id'] in the updates handler (see chapter getUpdates и update_id).

Common errors and how to handle them

  • chat not found — invalid chat_id or the bot is not in the chat. Check the ID, add the bot to the group/channel.
  • bot was blocked by the user — the user blocked the bot. Remove the chat_id from the distribution list, do not spam.
  • Too Many Requests: retry later (HTTP 429) — limit exceeded (30 msg/sec per chat, 20 msg/min in groups). Read header Retry-After, queue it, retry after the specified time.
  • Message is too long — text > 4096 characters. Split into parts or send as a document (sendDocument).
  • Bad Request: can\\'t parse entities — invalid HTML/Markdown. Escape input, verify closing tags.

We use the function telegramApi() from chapter Первый запрос: getMe.

<?php
try {
$result = telegramApi($token, 'sendMessage', [
'chat_id' => $chatId,
'text' => $longText,
]);
echo "OK, message_id: {$result['message_id']}\n";
} catch (RuntimeException $e) {
// Логируем для отладки
error_log('sendMessage failed: ' . $e->getMessage());
// Если 429 — можно извлечь Retry-After из заголовков (нужно расширить telegramApi)
// Если chat not found — пометить чат как неактивный в БД
}

Useful optional parameters

  • disable_web_page_preview (bool) — disables link previews in the message.
  • disable_notification (bool) — sends silently (silent message).
  • reply_to_message_id (int) — makes the message a reply to a specific one.
  • reply_markup — keyboards (inline/reply/force_reply), covered in subsequent chapters.

Continue: HTML и Markdown — полное руководство по разметке.

"}