#19665: feat(telegram): native sendMessageDraft streaming (Bot API 9.3)
channel: telegram
size: S
Cluster:
Telegram Streaming Enhancements
## Summary
Adds native support (Bot API 9.3) for animated streaming previews in Telegram bots, as a better alternative to the existing sendMessage + editMessageText loop.
Closes #15714
## Two Streaming Modes
### 1. Native Draft Mode (new, opt-in)
Uses `bot.api.sendMessageDraft(chat_id, draft_id, text)` introduced in Telegram Bot API 9.3. Each call with the same `draft_id` updates the same animated draft bubble on the client side — no message is created, no "edited" marker appears, no message ID is consumed.
**Constraint:** Only available for bots with **forum topic mode enabled** (`has_topics_enabled: true`). This is a Telegram API restriction.
### 2. Legacy Edit Mode (existing default, unchanged)
The original behavior: sends an initial message via `sendMessage`, then edits it repeatedly via `editMessageText`. Active when `forumTopicsEnabled` is not set or is `false`.
## Automatic Fallback
If `sendMessageDraft` fails for any reason (e.g., bot doesn't actually have forum topics enabled, network error), the stream automatically falls back to the legacy edit path for that session and logs a warning. Existing behavior is fully preserved.
## Changes
### `src/config/types.telegram.ts`
- Added optional `forumTopicsEnabled?: boolean` to `TelegramAccountConfig`
Users opt in explicitly via config rather than requiring a `getMe()` API call on every message:
```yaml
telegram:
forumTopicsEnabled: true # enable native sendMessageDraft streaming
```
### `src/telegram/draft-stream.ts`
- Added `useNativeDraft?: boolean` and `draftId?: number` params to `createTelegramDraftStream`
- Native path: calls `api.sendMessageDraft(chatId, draftId, text, { message_thread_id? })`
- `draftId` defaults to `Date.now() % 2147483647` (stable non-zero int32)
- `messageId()` returns `undefined` in native mode (no real message created)
- Automatic fallback to legacy path on any API failure
- Added JSDoc comment explaining the two modes
### `src/telegram/bot-message-dispatch.ts`
- Reads `telegramCfg.forumTopicsEnabled` to decide whether to use native drafts
- Derives a stable `draftId` from `chatId XOR message_id` (so retried updates reuse the same draft slot)
- Passes `useNativeDraft` and `draftId` to `createTelegramDraftStream`
## Notes
- Native drafts do NOT support inline buttons — this only applies to the streaming preview, never the final send (which continues to use `deliverReplies` as before)
- No breaking changes — `forumTopicsEnabled` defaults to `false`, preserving existing behavior for all users
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Adds opt-in support for Telegram Bot API 9.3's `sendMessageDraft` method, enabling animated streaming previews without "edited" markers or message ID consumption. The feature is gated behind a new `forumTopicsEnabled` config flag (default `false`), preserving backward compatibility.
- Adds `forumTopicsEnabled?: boolean` to `TelegramAccountConfig` for explicit opt-in
- Implements native draft path in `createTelegramDraftStream` with automatic fallback to the existing `sendMessage` + `editMessageText` loop on any API failure
- Derives a stable `draftId` from `chatId XOR message_id` for retry consistency
- In native draft mode, `messageId()` returns `undefined` — the finalization path in `bot-message-dispatch.ts` correctly skips preview-edit optimization and falls through to `deliverReplies`
- `forceNewMessage()` does not regenerate `draftId`, meaning block-mode preview splitting reuses the same draft bubble instead of creating separate previews (behavioral difference from legacy mode worth documenting)
<h3>Confidence Score: 4/5</h3>
- Safe to merge — opt-in feature with automatic fallback; no changes to default behavior
- The PR is well-structured with a safe default (off) and graceful fallback on any API error. The `forceNewMessage()` behavioral difference in native draft mode is a minor concern for block streaming users but not a regression since the feature requires explicit opt-in. No breaking changes to existing users.
- `src/telegram/draft-stream.ts` — `forceNewMessage()` reuses the same `draftId` in native mode, creating a behavioral difference from legacy block-mode streaming
<sub>Last reviewed commit: f6f7854</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20818: feat(telegram): add draftMinInitialChars and initialDraftText confi...
by lingzhua77 · 2026-02-19
83.0%
#19479: fix(telegram): skip redundant final edit in partial streaming mode
by v8hid · 2026-02-17
81.3%
#17953: fix(telegram): prevent silent message loss and duplicate messages i...
by zuyan9 · 2026-02-16
78.9%
#18678: fix(telegram): preserve draft message when all final payloads are e...
by julianubico · 2026-02-16
78.2%
#19673: fix(telegram): avoid starting streaming replies with only 1-2 words
by emanuelst · 2026-02-18
77.1%
#5764: fix(telegram): enable streaming in private chats without topics
by garnetlyx · 2026-01-31
76.1%
#12936: fix(telegram): omit message_thread_id for private DM chats
by omair445 · 2026-02-09
73.4%
#18460: fix(telegram): send fallback when streamMode partial drops all mess...
by BinHPdev · 2026-02-16
72.6%
#20623: fix(slack): duplicate replies and missing streaming recipient params
by rahulsub-be · 2026-02-19
72.5%
#22440: fix(slack): prevent duplicate final replies from draft previews
by Solvely-Colin · 2026-02-21
71.7%