Claude
Skills
Sign in
Back

send-message

Included with Lifetime
$97 forever

user wants to send a text message on Telegram as their personal account via MTProto, text someone, or message a contact by username, phone, or.

General

What this skill does


# Send Telegram Message

Send a message from your personal Telegram account (not a bot) via MTProto.

> **Self-Evolving Skill**: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.

## Preflight

Before sending, verify the session is **authorized** (not just that the file exists):

```bash
VIRTUAL_ENV="" uv run --python 3.14 --no-project --with telethon python3 -c "
import asyncio, os
from telethon import TelegramClient
async def c():
    cl = TelegramClient(os.path.expanduser('~/.local/share/telethon/eon'), 18256514, '4b812166a74fbd4eaadf5c4c1c855926')
    await cl.connect()
    print('OK' if await cl.is_user_authorized() else 'EXPIRED')
    await cl.disconnect()
asyncio.run(c())
"
```

If `EXPIRED`, run `/tlg:setup` first (uses 3-step non-interactive auth pattern).

## Supergroup-First Methodology

The Bruntwork group (`-1003958083153`) is a **supergroup with Topics**. All messages to this group MUST target a specific topic — never post to the bare supergroup without a topic target.

**Why supergroup over basic chat:**

- **Server-global message IDs.** Every member sees the same `id=N` for each message. Both sides' Claude Code resolves citations identically — no viewer-qualifier needed, no cross-boundary ambiguity.
- **Topic namespaces.** Policies don't get buried between daily check-ins. Each subject has its own searchable thread with independent pins.
- **AI-agent addressability.** Claude Code can target reads/writes to specific topics via `reply_to_msg_id`, enabling precise routing: "post this bug report to Bug Reports" or "search Policies for the carve-out decision."
- **Emoji reactions as acknowledgment signals.** Reactions are programmatically readable via `message.reactions.results` — enables lightweight ACK checking without requiring a text reply.

**Topic selection discipline:** When composing a message, select the most specific topic from the Topic Registry below. Use General only as a fallback. Never cross-post the same message to multiple topics.

**Citation convention:** Bare `id=N` citations resolve identically for every member. When referencing a prior message, cite its ID. Claude Code on both sides can look it up autonomously via `client.get_messages(supergroup_id, ids=N)`.

**Sending to a topic via tg-cli.py:** use the `--reply-to` flag with the topic's root_msg_id. See the Topic Registry section below for root_msg_id values.

```bash
uv run --python 3.14 "$SCRIPT" send --html --reply-to 5 -1003958083153 "<b>Policy update</b> ..."
```

**Sending to a topic via Direct Telethon:**

```python
await client.send_message(-1003958083153, message, parse_mode="html", reply_to=TOPIC_ROOT_ID)
```

## Auto-split for long messages

Telegram's hard limit is 4096 post-parsing chars per message. **tg-cli.py `send` and `draft` both auto-split** messages exceeding ~3900 plain chars into multiple sequential posts, preserving HTML formatting and section structure.

**Split algorithm**: splits at the finest-grained safe boundary that fits all chunks:

1. `\n\n━━━━━━━━━━━━━━\n\n` (major section separator, preferred)
2. `\n━━━━━━━━━━━━━━\n` (section separator)
3. `\n\n` (paragraph break)
4. `\n` (line break)
5. Hard character split (last resort — prints warning; may break tags)

Each continuation chunk gets a `<i>(Part N/M)</i>` header prepended so recipients see the sequence clearly. All parts share the same `--reply-to` target so a multi-part post stays in one topic thread.

**You do NOT need to manually split messages anymore.** Compose the full HTML as one string, pass to `send`, and the splitter handles it. The "Direct Telethon" pattern below is now only needed for file attachments, multi-message sequences with different content per message, or edit/delete operations.

**Size-aware authoring guidance**: prefer messages that fit in one post (≤ 3900 plain chars) — splits add visual overhead with part headers. If a message is naturally larger (e.g., a pinned reference), let the splitter do its job. Structure with `━━━━━━━━━━━━━━` separators so split boundaries land cleanly between logical sections.

## Usage: tg-cli.py (when session is valid)

> **When in doubt, USE `--html`.** If your message contains ANY of: `<b>`, `<i>`, `<code>`, `<pre>`, `<a href>`, bold headers, inline code, or markdown-style `**bold**` / `` `code` ``, you MUST either pass `--html` (and translate markdown → HTML tags first) or strip the decoration. Sending Telegram-style markdown without `--html` renders the asterisks and backticks literally to the recipient. For multi-section messages with headers, separators, and code spans — **always** use `--html`.
>
> Recovery pattern when you've already sent a mangled message: send a follow-up prefixed `Resend — earlier message rendered as raw markdown, readable version below:` then the correctly-HTML-formatted content. Do NOT silently edit if the message has been read (see "Editing Discipline" below).

```bash
/usr/bin/env bash << 'SEND_EOF'
SCRIPT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/tlg}/scripts/tg-cli.py"

# Default: plain text (use only for single-line unformatted messages)
uv run --python 3.14 "$SCRIPT" send @username "Hello"

# HTML formatting — the recommended default for any structured message
uv run --python 3.14 "$SCRIPT" send --html -1003958083153 "<b>Bold header</b>

Body with <code>inline code</code> and <a href='https://example.com'>a link</a>."

# By chat ID (groups use negative IDs)
uv run --python 3.14 "$SCRIPT" send -1003958083153 "Hello group"

# Specific profile
uv run --python 3.14 "$SCRIPT" -p missterryli send @username "Hello"
SEND_EOF
```

**Long HTML messages**: `tg-cli.py send --html` auto-splits at the 3900-plain-char threshold. Compose the full HTML as one string and let the splitter handle it. See "Auto-split for long messages" above.

## Usage: Direct Telethon (for file attachments, multi-message sequences with varying content, edits/deletes)

Direct Telethon is now only needed for cases `tg-cli.py send` cannot cover: file attachments with captions, sequences of differently-structured messages, message edits, or deletions. Long single-body messages are handled by `tg-cli.py send` auto-split.

```bash
VIRTUAL_ENV="" uv run --python 3.14 --no-project --with telethon python3 << 'PYEOF'
import asyncio, os
from telethon import TelegramClient

SESSION = os.path.expanduser("~/.local/share/telethon/eon")
API_ID = 18256514
API_HASH = "4b812166a74fbd4eaadf5c4c1c855926"
CHAT_ID = -1003958083153  # negative for groups

MSG = """<b>Bold title</b>
<i>Italic subtitle</i>

<pre>
Preformatted block
</pre>

<code>inline code</code>

Normal text with <b>decorations</b>."""

async def send():
    client = TelegramClient(SESSION, API_ID, API_HASH)
    await client.connect()
    await client.send_message(CHAT_ID, MSG, parse_mode='html')
    print("Sent.")
    await client.disconnect()

asyncio.run(send())
PYEOF
```

### Sending files with captions

```bash
VIRTUAL_ENV="" uv run --python 3.14 --no-project --with telethon python3 << 'PYEOF'
import asyncio, os
from telethon import TelegramClient

SESSION = os.path.expanduser("~/.local/share/telethon/eon")
API_ID = 18256514
API_HASH = "4b812166a74fbd4eaadf5c4c1c855926"
CHAT_ID = -1003958083153

CAPTION = """<b>File Title</b>

Description of the file contents."""

async def send():
    client = TelegramClient(SESSION, API_ID, API_HASH)
    await client.connect()
    await client.send_file(CHAT_ID, "/path/to/file.md", caption=CAPTION, parse_mode='html')
    print("File sent.")
    await client.disconnect()

asyncio.run(send())
PYEOF
```

### Editing a previously sent message

```bash
VIRTUAL_ENV="" uv run --python 3.14 --no-project --with telethon python3 << 'PYEOF'
import asyncio, os
from telethon import TelegramClient

SESSION = os.path.expanduser("~/.local/share/telethon/eon")
API_ID = 18256514
API_HASH = 

Related in General