Wiki
猫Table of Contents
- Drafts
- When drafts are useful
- Main pieces
- The easiest entry point: MessageContext.NewDraft()
- Creating a provider manually
- Draft lifecycle
- Push(...) versus Flush()
- Validation behavior
- Draft IDs
- Managing drafts through the provider
- Entities and parse mode
- When to prefer drafts over Answer(...)
- Caveats
- Related pages
Drafts
Russian version: Drafts-RU
Drafts provide a staged way to accumulate message text and send it later as a final message. They are useful when you want to build a response incrementally instead of sending each intermediate state directly to the chat.
When drafts are useful
Drafts are a good fit when:
- you collect output in several steps and want to send only the final result;
- you want a stable draft identifier while building a message;
- you need explicit control over when the final message is published;
- you want to update Telegram-side draft state during message construction.
They are usually better than direct replies when your message is assembled progressively or may be canceled before final delivery.
Main pieces
The draft system has two layers:
DraftProvider, which owns drafts and generates their IDs;Draft, which holds one staged message.
The provider is safe for concurrent use. Individual drafts are intended for single-goroutine use unless you add your own synchronization.
The easiest entry point: MessageContext.NewDraft()
Inside a handler, the usual entry point is MessageContext.NewDraft() or MessageContext.NewDraftMarkdown().
Those helpers:
- create a draft from the bot's configured
DraftProvider; - automatically bind it to the current chat;
- preserve the current message thread when applicable.
Typical usage:
func report(ctx *laniakea.MessageContext, db *App) error {
draft := ctx.NewDraft()
if draft == nil {
return nil
}
if err := draft.Push("Collecting data...\n"); err != nil {
return err
}
if err := draft.Push("Building summary...\n"); err != nil {
return err
}
return draft.Flush()
}
Use NewDraftMarkdown() when the final message should use MarkdownV2.
Creating a provider manually
If you need explicit provider control, create one yourself and attach it to the bot with SetDraftProvider(...).
Available constructors:
NewRandomDraftProvider(api)for random IDs;NewLinearDraftProvider(api, startValue)for monotonic IDs.
Random IDs are the default choice. Linear IDs are useful when:
- you want predictable ordering in logs;
- you want to persist or resume draft IDs across restarts;
- you want deterministic behavior in tests or debugging.
Draft lifecycle
A typical draft goes through these steps:
- create the draft;
- set the target chat if needed;
- append text with
Push(...); - optionally inspect or clear it;
- publish it with
Flush()or discard it withDelete().
Main APIs:
SetChat(chatID, messageThreadID)overrides the target chat and thread;SetEntities(...)sets explicit message entities;Push(text)appends text and updates the server-side draft;GetMessage()returns the current accumulated text;Clear()empties local content;Flush()sends the final message and removes the draft from the provider;Delete()removes the draft without sending it.
Push(...) versus Flush()
Push(...):
- appends text to
Draft.Message; - validates the resulting text length;
- sends an update to the Telegram-side draft API;
- keeps the draft alive for later changes.
Flush():
- validates the final message again;
- sends a normal final message with
SendMessage; - deletes the draft from the provider only on success;
- leaves the draft intact when sending fails so you can retry.
If the draft message is empty, Flush() returns nil and does not call the API.
Validation behavior
Drafts now validate message size before sending invalid requests.
Important rules:
- a draft must have a non-zero chat ID before
Push(...)orFlush(); - oversized message text is rejected before sending;
Push(...)updates the localMessagefield before returning the validation error.
That last detail matters: if a Push(...) call makes the draft too large, you still have the accumulated content available to inspect or adjust.
Draft IDs
Each draft gets a provider-generated ID.
ID generation modes:
- random IDs from
RandomDraftIDGenerator; - monotonic IDs from
LinearDraftIDGenerator.
The ID is mainly useful when:
- correlating draft activity in logs;
- storing or restoring draft metadata outside the process;
- addressing drafts through
DraftProvider.GetDraft(id).
Managing drafts through the provider
The provider can also manage drafts directly:
NewDraft(parseMode)creates a new draft;GetDraft(id)looks up an existing draft;FlushAll()tries to flush every pending draft.
FlushAll() is best-effort:
- it attempts all known drafts;
- it returns the first encountered error;
- successful drafts are still removed as they flush successfully.
This is useful for controlled shutdown or batch publishing flows.
Entities and parse mode
Each draft stores:
- a parse mode;
- optional message entities;
- target chat and message thread metadata.
Two details are easy to miss:
SetEntities(...)stores the slice by reference, so pass a copy if you plan to mutate your original slice later;NewDraftMarkdown()setsMarkdownV2, but the same escaping rules still apply to user input.
When to prefer drafts over Answer(...)
Prefer drafts when:
- you are building a message in phases;
- intermediate state should not be visible as separate chat messages;
- you may want to cancel or discard the response before publication.
Prefer direct reply helpers such as Answer(...) or AnswerLong(...) when:
- you already have the final text;
- you want immediate delivery;
- there is no value in staging or revising the message first.
Caveats
- A draft needs a valid target chat before it can be pushed or flushed.
- Draft providers are concurrency-safe; individual drafts are not automatically safe for concurrent mutation.
Delete()removes the draft locally and clears its message, but does not send anything.- Successful
Flush()removes the draft from the provider. - Failed
Flush()keeps the draft so you can retry.
Related pages
- MessageContext for handler-scoped reply and draft helpers
- Bot-Lifecycle for draft-provider attachment through the bot
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview