# Telegram Send

> Send Telegram messages/documents with dry-run, MTProto session safety, Pafi copy, SHA/message-ID delivery logs, and reusable scripts. Use when asked to send, forward, deliver, DM, or Telegram a message/file to named people or handles from Codex. ANTI-PATTERN: Do not use for Slack/email/WhatsApp, unauthorised external outreach, or casual reply drafting without sending.

- Skill: `cryptopafi/telegram-send` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add cryptopafi/telegram-send`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cryptopafi/telegram-send/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: cryptopafi (https://skillmd.com/u/cryptopafi)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/cryptopafi/telegram-send

---


<!-- GENERATED by forgebuild portable-skill v0.1.1; runtime=codex; core_sha256=e20894b8426ba2a7fb134ef151c5a88ccdc94f69cf259a6d33ed1a27796cc269; target_sha256=60ffb3ea70a560cad48bda6eacd8b3155fb16e76e9a48105e74c6d0add6de02b; do not hand edit -->

# Telegram Send

Use this skill to send Telegram direct messages or documents on Pafi's behalf through a reusable, logged workflow.

## Quick Rule

Default to dry-run while preparing a send. Use live mode only when the user explicitly asked to send now or the standing task already authorizes the outbound message.

For every outbound Telegram, WhatsApp, or email-style outreach performed through this skill:

- preserve Pafi on the communication trail;
- send Pafi an exact copy of the text and attachments unless the only recipient is Pafi;
- when any `copy-to` recipient is used, send the primary and copy messages from the Telegram account `Leonardo Concierge`;
- make the copy message to Pafi state who the primary message was sent to and that it was sent using the `Leonardo Concierge` Telegram account;
- record channel/account, recipient, copied recipient, message IDs, attachment filenames, SHA256 values, and exact message text;
- immediately report any copy failure.

## Standard Workflow

1. **Prepare message material** - write the final text into a local text file when it is more than one short sentence. Keep attachments as files and compute SHA256 values.
2. **Run dry-run first** - call `scripts/run_send_telegram.sh --dry-run ...` or `python3 scripts/send_telegram.py --dry-run ...` with the intended recipients, copy recipients, text file, and attachments.
3. **Review dry-run output** - verify recipients, copy recipients, sender account, attachment paths, and output log paths. Do not proceed if Pafi would not receive a copy or if a copy workflow is not using `Leonardo Concierge`.
4. **Send live** - rerun with `--live`. Do not use live mode for exploratory testing. If ledger paths are omitted, the script auto-creates them under the configured ledger directory.
5. **Verify result** - inspect the result JSON and Markdown ledger. Confirm primary and copy message IDs exist.
6. **Report succinctly** - state recipient, copied recipient, message IDs, attachment SHA256, and ledger path. Do not paste tokens or session data.

## Script Interface

Preferred command:

```bash
scripts/run_send_telegram.sh --dry-run --to @recipient --text-file message.txt --attach packet.zip --result-json result.json --log-md delivery-log.md
scripts/run_send_telegram.sh --live --to @recipient --text-file message.txt --attach packet.zip --result-json result.json --log-md delivery-log.md
```

Direct Python is also supported when Telethon is already available:

```bash
python3 scripts/send_telegram.py --dry-run --to @recipient --text "Message"
```

Common flags:

- `--to`: primary Telegram handle/chat; repeat for multiple recipients.
- `--copy-to`: copy recipient; defaults to `@callmepafi`. For non-owner recipients, at least one copy recipient must be Pafi.
- `--no-copy`: disable copy only for owner-only self-tests. Non-owner recipients always require Pafi copy.
- `--sender-account`: `auto`, `leonardo-concierge`, or `default`. `auto` selects `Leonardo Concierge` whenever `copy-to` is active. Any explicit `copy-to` workflow must use `leonardo-concierge`.
- `--text` or `--text-file`: message body.
- `--attach`: attachment path; repeat for multiple files.
- `--caption`: optional attachment caption.
- `--allow-sensitive-attachment`: allow attachment preflight hits only when the exact recipient/send was explicitly authorized.
- `--sensitive-authorization-note`: required note when allowing a flagged sensitive attachment.
- `--result-json`: machine-readable result path. For live sends, the script auto-creates a default when omitted.
- `--log-md`: human-readable delivery ledger path. For live sends, the script auto-creates a default when omitted.
- `--session-source`: Telegram MTProto session file. If omitted, use runtime env or the default local session convention.
- `--session-work`: copied working session path. Never mutate the source session directly.
- `--preserve-session-work`: debug-only flag to keep the copied session after live send.
- `--preserve-session-work-note`: required note when `--preserve-session-work` is used. Default behavior deletes the work session and SQLite sidecars.
- `--timeout-seconds`: live Telegram operation timeout; defaults to 60 seconds.

## Credentials And Session Handling

The script reads Telegram API credentials from environment variables first, then from the host secret manager when available:

- `TELEGRAM_API_ID`
- `TELEGRAM_API_HASH`
- secret-manager services named `TELEGRAM_API_ID` and `TELEGRAM_API_HASH`

Before reading secrets, the script loads profile configuration from these places, in order, without printing values:

- built-in safe defaults for environment-variable based sessions;
- bundled `scripts/profiles.local.json`;
- `~/.config/telegram-send/profiles.json`, when present;
- `TELEGRAM_SEND_PROFILE_CONFIG`, when present.

The profile configuration declares env files and session candidates. The bundled `scripts/profiles.local.json` is env-only; host-local NexusOS/Hermes fallback paths belong in `~/.config/telegram-send/profiles.json` or a path supplied through `TELEGRAM_SEND_PROFILE_CONFIG`, not in the Python implementation.

For `Leonardo Concierge`, session discovery uses profile candidates such as:

- `TELEGRAM_LEONARDO_CONCIERGE_SESSION`
- `LEONARDO_CONCIERGE_TELEGRAM_SESSION`
- `CONCIERGE_TELEGRAM_SESSION`
- `${PROFILE_HOME}/data/telegram-user/concierge_telegram.session`
- host-local paths declared in a host overlay profile

Copy workflows never fall back to the generic `telegram-watcher.session`; if the Leonardo Concierge session is unavailable, stop before any live send. To adapt another host, edit profile JSON rather than the Python implementation.

It never prints credential values. If credentials or an authorized MTProto session are missing, stop and report the missing runtime prerequisite. Do not request or paste secrets in chat.

## Result JSON Schema

Every dry-run or live call prints a JSON object to stdout. When `--result-json` is provided, the same public payload is written to disk. Private helper fields beginning with `_` are removed from stdout and saved JSON.

Top-level fields:

- `created_at`: UTC ISO timestamp.
- `mode`: `dry-run` or `live`.
- `status`: one of `dry_run_ok`, `sent`, `partial_failure`, or `error`.
- `sender_account`: stable account id, usually `leonardo-concierge` or `default`.
- `sender_account_display`: reader-facing account name, for example `Leonardo Concierge`.
- `recipients`: primary Telegram recipients.
- `copy_to`: copied recipients. Non-owner business sends must include `@callmepafi`.
- `copy_required`: boolean.
- `text`: exact primary text.
- `text_chars`: primary text length.
- `caption`: attachment caption, if any.
- `copy_message_text`: exact copy text template for dry-runs; live multi-recipient sends rebuild this per primary recipient.
- `ledger`: requested result/log paths.
- `attachments`: attachment records with `path`, `filename`, `sha256`, `size`, and `sensitive_hits`.
- `sensitive_attachment_authorization_note`: authorization note supplied for flagged attachments, when used.
- `session_work_preserved`: boolean; true only when the explicit debug preservation flag was used.
- `session_work_preservation_note`: required note for preserved copied session material.
- `session_work_preservation_warning`: live-send warning when copied MTProto work session was intentionally preserved.
- `session_source`: `present` when an MTProto session source was found; otherwise `null`.
- `channel_account`: live account descriptor returned by Telegram, present after a successful authorization lookup.
- `send_results`: live send records, present after live send attempts.
- `cleanup_warnings`: session cleanup warnings, when copied MTProto work-session cleanup could not remove every expected file.
- `error`: failure summary for `partial_failure` or `error`.

Each `send_results` item has:

- `kind`: `primary` or `copy`.
- `recipient`: target handle/chat.
- `text_message_id`: Telegram text message id, when text was sent.
- `document_message_ids`: Telegram attachment message ids.
- `copy_notice_inserted`: true for copy text sends.
- `error`: per-recipient failure text, when that step failed.

Status meanings:

- `dry_run_ok`: validation passed; no network send was attempted.
- `sent`: all primary and copy sends completed.
- `partial_failure`: at least one live send step failed after an earlier step may have succeeded, or a live send completed but copied session cleanup reported warnings. Preserve result JSON and report the failed recipient/copy/cleanup step.
- `error`: validation or runtime prerequisite failed before a completed live send plan.

## Safety Rules

- Never send a live message without `--live`.
- Never use a non-Leonardo sender account when `copy-to` is active.
- Never send a copy message to Pafi without the primary recipient and sender-account notice.
- Never use `--no-copy` for non-owner recipients.
- Never send attachments that contain secrets unless the user explicitly authorized the exact recipient and Pafi copy.
- Never bypass attachment preflight hits without `--allow-sensitive-attachment` and a concrete `--sensitive-authorization-note`.
- Never silently skip Pafi copy on business/outbound communication.
- Never mutate the source Telegram session. Always copy it to a work session and delete that work session after live send, except when the explicit debug flag is used with a preservation note.
- If a live send partially succeeds, preserve the result JSON and ledger; report which recipient/copy step failed.
- For bulk sends, send one recipient at a time unless the user explicitly asks for a broadcast.

## Runtime Notes

The bundled shell wrapper creates or reuses an isolated Python virtual environment for a hash-locked Telethon dependency set under `${XDG_CACHE_HOME:-~/.cache}/telegram-send/venv-<lockhash>` by default. It stores the `requirements.lock` SHA256 and a `.telegram-send-venv` ownership marker beside the venv and refuses to recursively rebuild an unmarked explicit `TELEGRAM_SEND_VENV` path. The Python script itself works with any runtime that can provide Python 3, Telethon, Telegram API credentials, and an authorized MTProto session.

When installing a runtime package manually, keep `SKILL.md` and `scripts/` as siblings. The commands above assume this layout.

No-write syntax verification for restricted audit runtimes:

```bash
python3 -B -c 'import ast, pathlib; [ast.parse(pathlib.Path(p).read_text()) for p in ("scripts/send_telegram.py", "tests/test_send_telegram.py", "tests/no_write_smoke.py")]'
bash -n scripts/run_send_telegram.sh
python3 tests/no_write_smoke.py
```

Full verification for writable runtimes:

```bash
python3 tests/test_send_telegram.py
```

## Test Cases

1. **Dry-run message**: `--dry-run --to @example --text "hello"` returns planned recipients and performs no network send.
2. **Owner self-test**: `--live --to @callmepafi --no-copy --text "Telegram send smoke"` sends one message to Pafi and writes result JSON.
3. **Business outbound**: `--live --to @recipient --text-file message.txt --attach file.zip` sends to the recipient from `Leonardo Concierge` and sends a copy to Pafi that states the recipient and account used, then records all message IDs and SHA256 values.
4. **Copy enforcement**: `--dry-run --to @recipient --no-copy --text "hello"` fails because non-owner no-copy is not allowed.
5. **Copy override enforcement**: `--dry-run --to @recipient --copy-to @someone_else --text "hello"` fails because Pafi is missing from copy recipients.
6. **Sender account enforcement**: `--dry-run --to @recipient --copy-to @callmepafi --sender-account default --text "hello"` fails because copy workflows require `Leonardo Concierge`.
7. **Missing session**: no authorized MTProto session produces a clear failure before any send.
8. **Copy failure**: if primary succeeds and copy fails, result artifacts are preserved and the final report states the copy failure.

