Telegram
Channels

Telegram

Send Telegram messages via Bot API

The Telegram channel is provided by @betternotify/telegram. It sends messages through the Telegram Bot API with support for text, photos, documents, videos, and audio.

When to use Telegram

Telegram is the right channel when you need to deliver notifications to users or groups inside Telegram. Use it for bot alerts, monitoring dashboards, deployment notifications, or any scenario where your audience already lives in Telegram. Messages support rich formatting (HTML / Markdown) and optional media attachments.

npm install @betternotify/telegram

Setup

import { createNotify } from '@betternotify/core';
import { telegramChannel } from '@betternotify/telegram';

const telegram = telegramChannel();

const rpc = createNotify({ channels: { telegram } });

Slots

Prop

Type

Send arguments

Prop

Type

Full example

import { createNotify, createClient } from '@betternotify/core';
import { telegramChannel, telegramTransport } from '@betternotify/telegram';
import { z } from 'zod';

const telegram = telegramChannel();
const rpc = createNotify({ channels: { telegram } });

const catalog = rpc.catalog({
  deployAlert: rpc
    .telegram()
    .input(z.object({ service: z.string(), version: z.string(), status: z.string() }))
    .body(({ input }) => `<b>${input.service}</b> deployed v${input.version}\nStatus: ${input.status}`)
    .parseMode('HTML'),
});

const notify = createClient({
  catalog,
  transportsByChannel: {
    telegram: telegramTransport({ token: process.env.TELEGRAM_BOT_TOKEN! }),
  },
});

await notify.deployAlert.send({
  to: -1001234567890,
  input: { service: 'api', version: '2.4.0', status: 'healthy' },
});

MarkdownV2 escaping

When using parseMode: 'MarkdownV2', Telegram requires escaping reserved characters in text content. Use the md tagged template to write formatting naturally while auto-escaping interpolated values:

import { md } from '@betternotify/telegram';

.body(({ input }) => md`*${input.service}* deployed v${input.version}`)
// → "*api* deployed v2\.4\.0"

The template structure (*bold*, _italic_, ~strike~, `code`, [link](url)) stays intact. Dynamic values get all reserved characters escaped automatically.

For escaping a single value directly, use escapeMarkdownV2:

import { escapeMarkdownV2 } from '@betternotify/telegram';

escapeMarkdownV2('v2.4.0') // → "v2\\.4\\.0"

Media attachments

Attach photos, documents, videos, or audio using the attachment slot:

const catalog = rpc.catalog({
  chartAlert: rpc
    .telegram()
    .input(z.object({ chartUrl: z.string(), summary: z.string() }))
    .body(({ input }) => input.summary)
    .attachment(({ input }) => ({ type: 'photo', url: input.chartUrl })),
});

When an attachment is present, body becomes the caption. You can override the caption per-attachment:

.attachment(({ input }) => ({
  type: 'document',
  url: input.reportUrl,
  caption: 'Weekly report attached',
}))

Transport

Built-in: telegramTransport

The package includes a ready-to-use transport that calls the Telegram Bot API via fetch:

import { telegramTransport } from '@betternotify/telegram';

const transport = telegramTransport({
  token: process.env.TELEGRAM_BOT_TOKEN!,
  // apiUrl: 'https://custom-bot-api.example.com', // optional, for self-hosted Bot API
});

The transport picks the correct Bot API method based on the attachment type:

AttachmentAPI method
nonesendMessage
photosendPhoto
documentsendDocument
videosendVideo
audiosendAudio

Call transport.verify() to validate your bot token at startup.

Mock transport

Use mockTelegramTransport() in tests:

import { mockTelegramTransport } from '@betternotify/telegram';

const transport = mockTelegramTransport();

// after sending...
console.log(transport.messages); // [{ body, to, parseMode?, attachment?, id }]
transport.reset();

Multi-transport

Use multiTransport from core for failover or load balancing across multiple Telegram bots:

import { telegramTransport } from '@betternotify/telegram';
import { multiTransport } from '@betternotify/core/transports';

const transport = multiTransport({
  strategy: 'failover',
  transports: [
    { transport: telegramTransport({ token: process.env.BOT_PRIMARY! }) },
    { transport: telegramTransport({ token: process.env.BOT_FALLBACK! }) },
  ],
});

On this page