Skip to content
teleproto

Messages

The message API is the most-used surface in teleproto. Sending, editing, deleting, forwarding, iterating history, reacting, marking read — all of it lives on TelegramClient. Every method takes an entity as its first argument (username, numeric id, or an InputPeer) and returns typed Message objects.

Sending#

sendMessage(entity, params) is the workhorse. The params object accepts message, parseMode, replyTo, file, buttons, linkPreview, silent, and schedule. Everything except message is optional.

send.ts
import { TelegramClient } from "teleproto";
import { StringSession } from "teleproto/sessions";

const client = new TelegramClient(
  new StringSession(process.env.TG_SESSION!),
  Number(process.env.TG_API_ID),
  process.env.TG_API_HASH!,
  { connectionRetries: 5 },
);
await client.connect();

await client.sendMessage("me", {
  message: "<b>shipped</b> — build <code>#421</code> is live",
  parseMode: "html",
  linkPreview: false,
  silent: true,
});

Editing & deleting#

Edits use the same shape as sends, with the message id stitched into the params object: editMessage(entity, { message, id }). Deletes take an array of numeric ids and an optional revoke flag.

edit-delete.ts
// editMessage takes the message id inside the params object.
const sent = await client.sendMessage("me", { message: "deploying..." });

await client.editMessage("me", {
  message: "deployed",
  id: sent.id,
});

// Delete two messages for everyone in the chat.
await client.deleteMessages("me", [sent.id, sent.id - 1], { revoke: true });

revoke: true removes the messages for everyone in the chat, not just for you. Without it the messages only disappear from your own client, which is almost never what you want.

Forwarding#

forwardMessages takes the destination entity and a params object with a messages id array plus fromPeer. You can move many messages at once — pass multiple ids in the array.

forward.ts
// Forward message #1234 from a source channel to your saved messages.
await client.forwardMessages("me", {
  messages: [1234],
  fromPeer: "@telegram",
});

Iterating history#

iterMessagesis an async iterator over a chat's history. It accepts limit, offsetId, search, fromUser, filter, minId, and maxId. Use for await and let it page transparently.

iterate.ts
// Async iteration — memory-light, paginates under the hood.
for await (const message of client.iterMessages("@telegram", {
  limit: 200,
  search: "release",
})) {
  console.log(message.id, message.message);
}

// Or grab a page at once. The return value carries .total for the chat-wide count.
const page = await client.getMessages("@telegram", { limit: 50 });
console.log(`fetched ${page.length} of ${page.total}`);

If you want the whole page in memory, getMessages returns a TotalList<Message> — an array with an extra .total property holding the chat-wide message count, not just what fit in the page.

Pinning#

pinMessage(entity, messageId, { notify, pmOneside }) pins; unpinMessage(entity, messageId) removes a pin. notify: falseis the polite default — Telegram's default behavior pings every member of the chat. pmOneside: true pins only on your side of a 1:1 conversation.

pin.ts
const note = await client.sendMessage("me", { message: "read me first" });

await client.pinMessage("me", note.id, { notify: false, pmOneside: true });
// ...later
await client.unpinMessage("me", note.id);

Reactions#

sendReaction(entity, msgId, reaction?, big?) attaches (or clears) an emoji reaction. reaction is a string like "👍", or null to remove whatever you currently have on the message. big: trueplays the larger fullscreen animation in recipients' clients.

react.ts
// Add a thumbs-up with the big animation.
await client.sendReaction("@durov", 1, "👍", true);

// Clear your reaction by passing null.
await client.sendReaction("@durov", 1, null);

Marking read#

markAsRead(entity, message?, { clearMentions }) clears unread badges. Pass a message id to mark a specific point in history as read, or omit it to mark the whole dialog. clearMentions: true also clears the @mention counter on chats where you were tagged.

read.ts
// Mark everything up to (and including) message id 9001 as read.
await client.markAsRead("@somechat", 9001, { clearMentions: true });

// No id = mark the whole dialog read.
await client.markAsRead("@somechat");

If you're coming from GramJS, note the rename: sendReadAcknowledge from that library is markAsRead here. Same Telegram-side effect, shorter name.

Parse modes#

teleproto ships two formatters: "html" and "md". HTML mode accepts the Telegram subset — <b>, <i>, <code>, <a href="...">. Markdown mode accepts the standard Telegram Markdown subset (bold, italic, code fences, links). Anything outside that subset is sent as literal characters.

parse-mode.ts
// Per-call override.
await client.sendMessage("me", {
  message: "**bold** and _italic_ via markdown",
  parseMode: "md",
});

// Or set once for the lifetime of the client.
client.setParseMode("html");
await client.sendMessage("me", {
  message: '<a href="https://teleproto.dev">docs</a>',
});

Per-call parseMode wins over the client-wide default, so a global setParseMode("html")doesn't stop one specific call from sending raw text with parseMode: null.