Ray
PricingDocsSign inStart free
Browse the docs

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Getting started

Ray documentationAuthenticationUsing Ray with AI coding agentsMCP serverTypeScript SDK

Guides

Sending notificationsIdempotencyTemplatesScheduling and test sendsStatus and feedsClick trackingDelivery webhooks

Channels

Email with Amazon SESEmail with SMTPPush notifications with Firebase Cloud MessagingSlack notificationsDiscord notificationsTelegram notificationsSMS with TwilioSMS with sendsms.mnHTTPS webhook channel

Reference

Rate limits and quotasErrors

Channels

Telegram notifications

Send Ray notifications to Telegram users, groups and channels with your own bot. BotFather setup, finding chat ids, HTML content, pacing and errors.

View as Markdownllms.txtAPI reference

The telegram_bot channel sends messages with the Telegram Bot API (sendMessage) using a bot you create. Each send targets a chat id: a user who started your bot, a group, or a channel. API calls go to https://ray-api.gege.mn with Authorization: Bearer $RAY_API_KEY.

Channel type telegram_bot
Template kind telegram_text
Recipient { "chatId": "..." }
Pacing ratePerMinute, default 1500, max 1600
Suppression None

What you need from Telegram

Create a bot

  1. Open a chat with @BotFather in Telegram and send /newbot.
  2. Choose a display name and a username ending in bot.
  3. BotFather replies with the bot token, shaped <bot_id>:<secret>, for example 123456789:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw. Keep it secret.

Find chat ids

Telegram bots can only message chats that allowed it:

  • A user must open your bot and press Start (or send it any message) first. Bots cannot start conversations.
  • For a group, add the bot to the group.
  • For a channel, add the bot as an administrator with permission to post.

Then read the chat id from the bot's recent updates:

curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates"

Look for message.chat.id (users and groups) or channel_post.chat.id (channels) in the response. User ids are positive numbers, group ids negative, and supergroup and channel ids start with -100. A public channel can also be addressed by its username, such as @my_channel. getUpdates returns nothing while the bot has a webhook set; in your app, you will usually capture chat.id when a user starts your bot and store it with the user.

Channel config

Create the config in the dashboard: Channels, New channel, Telegram bot. The token is stored encrypted; after saving, the dashboard only shows the bot id.

Field Required Notes
Name yes Internal label, for example ops-bot.
Bot token (botToken) yes From BotFather, must match <digits>:<letters, digits, _ or ->. When editing, leave blank to keep the stored token.
Rate limit, messages per minute (ratePerMinute) no Default 1500, max 1600. Telegram allows about 30 messages per second per bot.

Find the config's id with GET /channels; its kind is telegram_bot.

Template content

Field Type Notes
text string, 1 to 4096 Sent with parse_mode: HTML. Use Telegram's HTML subset: <b>, <i>, <u>, <s>, <a href="...">, <code>, <pre>.
disableLinkPreview boolean Optional. true hides the link preview card.

Escaping: param values are HTML-escaped (&, <, >, ", '), so a value can't add tags or links. Markup belongs in the template text. Literal <, > and & that you write in the template itself must be written as &lt;, &gt; and &amp;, or Telegram rejects the message.

Recipient

{ "chatId": "123456789" }
Field Type Notes
chatId string Required. A numeric chat id as a string ("123456789", "-1001234567890") or a public channel username ("@my_channel").

Send a message

curl -X POST https://ray-api.gege.mn/send \
  -H "Authorization: Bearer $RAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payout-po_8842" \
  -d '{
    "channelConfigId": "8e9f0a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b",
    "content": {
      "text": "<b>Payout sent</b>\nAmount: {{amount}}\n<a href=\"https://app.example.com/payouts/{{payoutId}}\">View payout</a>",
      "disableLinkPreview": true
    },
    "params": { "amount": "$1,250.00", "payoutId": "po_8842" },
    "recipient": { "chatId": "123456789" },
    "externalUserId": "user_123",
    "showInFeed": true
  }'
async function telegramNotify(chatId: string, amount: string, payoutId: string) {
  const res = await fetch("https://ray-api.gege.mn/send", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RAY_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `payout-${payoutId}`,
    },
    body: JSON.stringify({
      channelConfigId: "8e9f0a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b",
      templateId: "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a", // a telegram_text template
      params: { amount, payoutId },
      recipient: { chatId },
    }),
  });
  if (res.status !== 202) throw new Error(`Ray ${res.status}: ${await res.text()}`);
  return ((await res.json()) as { sendId: string }).sendId;
}

Broadcast to many chats with targets[] (up to 1000 per request):

{
  "channelConfigId": "8e9f0a1b-2c3d-4e5f-8a6b-7c8d9e0f1a2b",
  "templateId": "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a",
  "params": { "amount": "$1,250.00", "payoutId": "po_8842" },
  "targets": [
    { "recipient": { "chatId": "123456789" }, "externalUserId": "user_1" },
    { "recipient": { "chatId": "987654321" }, "externalUserId": "user_2" }
  ],
  "priority": "low"
}

The delivery row's providerMessageId is Telegram's message_id. delivered means Telegram accepted the message.

Pacing

Ray paces each bot token at ratePerMinute (default 1500), under Telegram's limit of about 30 messages per second per bot. Telegram also limits messages to a single chat (about one per second, lower in groups); a 429 from Telegram is retried with backoff.

Errors

Errors appear on the delivery row as providerError: { "name": "TelegramError", "message": "Telegram sendMessage failed: <code> <description>" }.

Result Cases
failed_terminal (not retried) Any 4xx except 429: 400 Bad Request: chat not found, 400 Bad Request: can't parse entities (invalid HTML), 401 Unauthorized (bad token), 403 Forbidden: bot was blocked by the user, 403 Forbidden: bot is not a member of the channel chat
failed_retryable (retried) 429 Too Many Requests, 5xx, network errors and timeouts

Common fixes:

  • chat not found: the user never started the bot, the id is wrong, or the bot isn't in the group or channel.
  • bot was blocked by the user: the user blocked the bot. Stop messaging that chat id.
  • can't parse entities: unbalanced or unsupported HTML in the template, or a literal < or & in template text. Param values are already escaped.
  • Unauthorized: the token was revoked in BotFather. Paste the new token into the config.

Request errors from POST /send are listed in Errors.

PreviousDiscord notificationsNextSMS with Twilio

On this page

  • What you need from Telegram
  • Create a bot
  • Find chat ids
  • Channel config
  • Template content
  • Recipient
  • Send a message
  • Pacing
  • Errors
Ray

Notification delivery for teams that already have providers.

Product

PricingDocsAPI referenceMCP serverTypeScript SDKAgent skillsllms.txt

Legal

TermsPrivacyRefunds

Contact

[email protected]

© 2026 L’Atelier Gege LLC