RelayAPI

Telegram API

Schedule and automate Telegram channel and group posts with RelayAPI — text, images, videos, documents, media albums, and rich formatting.

Quick Reference

PropertyValue
Platform keytelegram
Auth methodBot Token
Text limit4,096 characters (text messages)
Caption limit1,024 characters (media captions)
Images per album10
Videos per album10
Mixed mediaYes (images + videos in same album)
Image formatsJPEG, PNG, GIF, WebP
Image max size10 MB
Video formatsMP4, MOV
Video max size50 MB
Post typesText, Photo, Video, Document, Media Album
SchedulingYes
AnalyticsNo (Telegram limitation)

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

Telegram uses a bot-based integration — the bot must be added as an administrator in the target channel or group with posting permissions before you can publish. Posts in channels show the channel name and logo as the author, but posts in groups show the bot name (cannot be changed). Media captions are limited to 1,024 characters, which is shorter than the 4,096-character limit for text-only messages. In media albums, only the first item gets the caption. No analytics are available via the Telegram Bot API.

Quick Start

Send a message to a Telegram channel:

import Relay from '@relayapi/sdk';

const client = new Relay();

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['telegram'],
  scheduled_at: 'now'
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()

post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['telegram'],
    scheduled_at='now'
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"telegram"}),
    ScheduledAt: relaygo.F("now"),
})
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("telegram")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["telegram"],
    "scheduled_at": "now"
  }'

Content Types

Text Message

Plain text message up to 4,096 characters.

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['telegram'],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['telegram'],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"telegram"}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("telegram")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["telegram"],
    "scheduled_at": "now"
  }'

Text with HTML Formatting

Use HTML tags for rich text formatting. HTML is the default parse mode.

const post = await client.posts.create({
  content: '<b>Important Update!</b>\n\nCheck out our <a href="https://example.com">new feature</a>.',
  targets: ['telegram'],
  scheduled_at: 'now',
  target_options: {
    telegram: {
      parse_mode: 'HTML'
    }
  }
});
post = client.posts.create(
    content='<b>Important Update!</b>\n\nCheck out our <a href="https://example.com">new feature</a>.',
    targets=['telegram'],
    scheduled_at='now',
    target_options={
        'telegram': {
            'parse_mode': 'HTML'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("<b>Important Update!</b>\n\nCheck out our <a href=\"https://example.com\">new feature</a>."),
    Targets:     relaygo.F([]string{"telegram"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "telegram": map[string]interface{}{
            "parse_mode": "HTML",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("<b>Important Update!</b>\n\nCheck out our <a href=\"https://example.com\">new feature</a>.")
    .addTarget("telegram")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("telegram", JsonValue.from(Map.of(
            "parse_mode", "HTML"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "<b>Important Update!</b>\n\nCheck out our <a href=\"https://example.com\">new feature</a>.",
    "targets": ["telegram"],
    "scheduled_at": "now",
    "target_options": {
      "telegram": {
        "parse_mode": "HTML"
      }
    }
  }'

Text with Markdown Formatting

Use Markdown or MarkdownV2 for formatting.

const post = await client.posts.create({
  content: '*Bold text* and _italic text_ with a [link](https://example.com)',
  targets: ['telegram'],
  scheduled_at: 'now',
  target_options: {
    telegram: {
      parse_mode: 'Markdown'
    }
  }
});
post = client.posts.create(
    content='*Bold text* and _italic text_ with a [link](https://example.com)',
    targets=['telegram'],
    scheduled_at='now',
    target_options={
        'telegram': {
            'parse_mode': 'Markdown'
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("*Bold text* and _italic text_ with a [link](https://example.com)"),
    Targets:     relaygo.F([]string{"telegram"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "telegram": map[string]interface{}{
            "parse_mode": "Markdown",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("*Bold text* and _italic text_ with a [link](https://example.com)")
    .addTarget("telegram")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("telegram", JsonValue.from(Map.of(
            "parse_mode", "Markdown"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "*Bold text* and _italic text_ with a [link](https://example.com)",
    "targets": ["telegram"],
    "scheduled_at": "now",
    "target_options": {
      "telegram": {
        "parse_mode": "Markdown"
      }
    }
  }'

MarkdownV2 requires escaping special characters: _ * [ ] ( ) ~ > # + - = | { } . ! — if you use MarkdownV2, make sure to escape these in your content.

Photo Message

Single photo with a caption (max 1,024 chars).

const post = await client.posts.create({
  content: 'Check out this photo!',
  targets: ['telegram'],
  media: [
    { url: 'https://cdn.example.com/photo.jpg', type: 'image' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Check out this photo!',
    targets=['telegram'],
    media=[
        {'url': 'https://cdn.example.com/photo.jpg', 'type': 'image'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Check out this photo!"),
    Targets:     relaygo.F([]string{"telegram"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/photo.jpg"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage),
    }}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out this photo!")
    .addTarget("telegram")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out this photo!",
    "targets": ["telegram"],
    "media": [
      {"url": "https://cdn.example.com/photo.jpg", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Video Message

const post = await client.posts.create({
  content: 'Watch our latest video!',
  targets: ['telegram'],
  media: [
    { url: 'https://cdn.example.com/video.mp4', type: 'video' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Watch our latest video!',
    targets=['telegram'],
    media=[
        {'url': 'https://cdn.example.com/video.mp4', 'type': 'video'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Watch our latest video!"),
    Targets:     relaygo.F([]string{"telegram"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/video.mp4"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo),
    }}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Watch our latest video!")
    .addTarget("telegram")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/video.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Watch our latest video!",
    "targets": ["telegram"],
    "media": [
      {"url": "https://cdn.example.com/video.mp4", "type": "video"}
    ],
    "scheduled_at": "now"
  }'

Document Message

Send any file type as a document attachment.

const post = await client.posts.create({
  content: 'Here is the report.',
  targets: ['telegram'],
  media: [
    { url: 'https://cdn.example.com/report.pdf', type: 'document' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Here is the report.',
    targets=['telegram'],
    media=[
        {'url': 'https://cdn.example.com/report.pdf', 'type': 'document'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Here is the report."),
    Targets:     relaygo.F([]string{"telegram"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{{
        URL:  relaygo.F("https://cdn.example.com/report.pdf"),
        Type: relaygo.F(relaygo.PostNewParamsMediaTypeDocument),
    }}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Here is the report.")
    .addTarget("telegram")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/report.pdf")
        .type(PostCreateParams.Media.Type.DOCUMENT)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Here is the report.",
    "targets": ["telegram"],
    "media": [
      {"url": "https://cdn.example.com/report.pdf", "type": "document"}
    ],
    "scheduled_at": "now"
  }'

Media Album (Up to 10 Items)

Mix images and videos in a single album. Caption appears on the first item only.

const post = await client.posts.create({
  content: 'Product gallery!',
  targets: ['telegram'],
  media: [
    { url: 'https://cdn.example.com/photo1.jpg', type: 'image' },
    { url: 'https://cdn.example.com/photo2.jpg', type: 'image' },
    { url: 'https://cdn.example.com/video.mp4', type: 'video' },
    { url: 'https://cdn.example.com/photo3.jpg', type: 'image' }
  ],
  scheduled_at: 'now'
});
post = client.posts.create(
    content='Product gallery!',
    targets=['telegram'],
    media=[
        {'url': 'https://cdn.example.com/photo1.jpg', 'type': 'image'},
        {'url': 'https://cdn.example.com/photo2.jpg', 'type': 'image'},
        {'url': 'https://cdn.example.com/video.mp4', 'type': 'video'},
        {'url': 'https://cdn.example.com/photo3.jpg', 'type': 'image'}
    ],
    scheduled_at='now'
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Product gallery!"),
    Targets: relaygo.F([]string{"telegram"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/photo1.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
        {URL: relaygo.F("https://cdn.example.com/photo2.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
        {URL: relaygo.F("https://cdn.example.com/video.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
        {URL: relaygo.F("https://cdn.example.com/photo3.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Product gallery!")
    .addTarget("telegram")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo1.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo2.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/video.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/photo3.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Product gallery!",
    "targets": ["telegram"],
    "media": [
      {"url": "https://cdn.example.com/photo1.jpg", "type": "image"},
      {"url": "https://cdn.example.com/photo2.jpg", "type": "image"},
      {"url": "https://cdn.example.com/video.mp4", "type": "video"},
      {"url": "https://cdn.example.com/photo3.jpg", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Telegram is one of the few platforms that supports mixing images and videos in the same album. However, only the first item in the album displays the caption.

Silent and Protected Message

Send without notification sound, and prevent forwarding or saving.

const post = await client.posts.create({
  content: 'Confidential update',
  targets: ['telegram'],
  scheduled_at: 'now',
  target_options: {
    telegram: {
      silent: true,
      protect_content: true
    }
  }
});
post = client.posts.create(
    content='Confidential update',
    targets=['telegram'],
    scheduled_at='now',
    target_options={
        'telegram': {
            'silent': True,
            'protect_content': True
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Confidential update"),
    Targets:     relaygo.F([]string{"telegram"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "telegram": map[string]interface{}{
            "silent":          true,
            "protect_content": true,
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Confidential update")
    .addTarget("telegram")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("telegram", JsonValue.from(Map.of(
            "silent", true,
            "protect_content", true
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Confidential update",
    "targets": ["telegram"],
    "scheduled_at": "now",
    "target_options": {
      "telegram": {
        "silent": true,
        "protect_content": true
      }
    }
  }'

Text Formatting Reference

HTML (Default)

<b>bold</b>, <i>italic</i>, <u>underline</u>, <s>strikethrough</s>
<code>inline code</code>, <pre>code block</pre>
<a href="https://example.com">link</a>

Markdown

*bold*, _italic_, [link](https://example.com), `inline code`

MarkdownV2

*bold*, _italic_, __underline__, ~strikethrough~, ||spoiler||
`inline code`

Media Requirements

Images

PropertyRequirement
Max per album10
FormatsJPEG, PNG, GIF, WebP
Max file size10 MB (auto-compressed)
Max resolution10,000 x 10,000 px

Videos

PropertyRequirement
Max per album10
FormatsMP4, MOV
Max file size50 MB (auto-compressed)
Max durationNo limit
Recommended codecH.264

Documents

PropertyRequirement
FormatsAny file type
Max file size50 MB

target_options Fields

All fields go inside target_options.telegram on your post request.

FieldTypeDefaultDescription
contentstringOverride post content for Telegram specifically
mediaobject[]Override media for Telegram specifically
parse_modestring"HTML"Text formatting mode: "HTML", "Markdown", or "MarkdownV2"
disable_previewbooleanfalseDisable link preview in text messages
silentbooleanfalseSend without notification sound
protect_contentbooleanfalsePrevent forwarding and saving by recipients

Channel vs Group

DestinationAuthor Display
ChannelChannel name and logo
GroupBot name

Common Errors

ErrorCauseFix
Bot is not a memberBot has not been added as an adminAdd the bot as an administrator in the channel or group with posting permissions.
Message is too longText exceeds 4,096 chars or caption exceeds 1,024 charsShorten the content. Use target_options.telegram.content for a shorter version.
Wrong file identifierMedia URL is inaccessible or uses HTTPUse a direct HTTPS URL that is publicly accessible.
Can't parse entitiesInvalid HTML or Markdown syntaxCheck tag closure in HTML. Escape special characters in MarkdownV2.

Known Quirks

  • Channel posts show channel name and logo as the author (correct behavior).
  • Group posts show the bot name as the author — this cannot be changed.
  • Media captions limited to 1,024 characters — shorter than the 4,096-character text message limit.
  • Album captions appear on the first item only — subsequent items in a media group do not display captions.
  • No analytics available via the Telegram Bot API.
  • Bot must be admin in the channel or group before posting is possible.
  • Private channels require the user to forward a message from the channel to the bot for connection.
  • Mixed media albums supported — images and videos can be combined in a single album.
  • Documents sent separately — documents cannot be mixed with photos/videos in an album.

Automations

Telegram is a Tier 1 automation platform. Bots use the token as a single credential — no OAuth refresh needed.

Triggers

TypeFires on
telegram_messageAny inbound message
telegram_command/start, /help, or custom command
telegram_channel_postNew post in a channel the bot belongs to
telegram_callback_queryInline keyboard button tap
telegram_reactionEmoji reaction
telegram_member_joinedNew chat member
telegram_chat_join_requestJoin-request flow
telegram_business_messageTelegram Business message
telegram_inline_queryUser invokes inline bot

Send nodes

Endpoint shape: POST https://api.telegram.org/bot{token}/{method}.

NodeMethodRequired fields
telegram_send_textsendMessagetext, optional parse_mode / disable_web_page_preview
telegram_send_mediasendPhoto/Video/Audio/Document (auto)url, media_type
telegram_send_media_groupsendMediaGroupmedia[] (2–10 items)
telegram_send_pollsendPollquestion, options[] (2–10)
telegram_send_locationsendLocationlatitude, longitude
telegram_send_keyboardsendMessage + inline_keyboardtext, buttons[][] (rows × buttons)
telegram_edit_messageeditMessageTexttext, message_id
telegram_pin_messagepinChatMessagemessage_id
telegram_reactsetMessageReactionemoji, message_id
telegram_set_chat_actionsendChatActionaction (default typing)

Rate limits

  • 30 msg/sec globally, 1 msg/sec per chat. Burst over the per-chat limit returns 429 Retry-After.

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo