tgpost
Send messages and files to Telegram channels, now or on a schedule.
Disclaimer
Provided AS IS, without warranty of any kind, express or implied. Use entirely at your own risk. The author accepts no liability for data loss, content published to the wrong channel or audience, business interruption or consequential damages.
You are responsible for verifying the target channel, for what you publish, for being authorised to post there, and for complying with the Telegram Terms of Service.
tgpost scheduleandtgpost daemonpublish unattended: a mistaken cron expression can post to a live channel repeatedly until someone stops it. Test against a private channel first.Not certified for regulated, forensic, safety-critical or high-assurance use. There is no delivery guarantee: the Bot API offers no idempotency key, so a send that times out may or may not have arrived.
The MIT LICENSE is the governing text and prevails wherever this summary differs from it. See DISCLAIMER.md for the full text.
Why this exists
Sending a file to a Telegram channel from the command line is well covered already. What is missing is doing it later, reliably.
The Telegram HTTP Bot API has no server-side scheduling: there is no
schedule_date parameter on any send method, so "post this at 9am on weekdays"
has to be solved on your side, and survive a reboot. tgpost is a sender with a
persistent job store attached, so a scheduled post still goes out after the
machine restarts.
If you only need to send something right now, telegram-send and apprise both
do that well.
Install
pip install tgpost # sending only
pip install tgpost[schedule] # sending and scheduling
Requires Python 3.11 or newer.
Getting started
Create a bot with @BotFather, then add it to your channel as an administrator with permission to post messages. A bot cannot post to a channel as an ordinary member.
export TGPOST_BOT_TOKEN="123456789:your-token-here" # Windows: set TGPOST_BOT_TOKEN=...
tgpost targets add release-notes -1001234567890 --description "Build announcements"
tgpost check
tgpost check verifies the token and reports, per target, whether the bot can
actually post. Those two permission failures are the most common cause of a
send failing later, so it is worth running first.
Finding a channel id
A public channel can be addressed as @channelusername. For a private channel,
post any message in it, then read the numeric id (it starts with -100):
tgpost targets add private-chan @temporary_username # if public, then:
tgpost check # prints the numeric id
Sending
tgpost send --to release-notes --text "Build 1.2.3 shipped"
tgpost send --to release-notes --file build.zip --caption "Nightly build"
tgpost send --to release-notes --file a.png --file b.png --album --kind photo
git log --oneline -10 | tgpost send --to release-notes --stdin
tgpost send --to release-notes --text "check this first" --dry-run
Text longer than Telegram's 4096-character limit is split across several messages at paragraph, line or word boundaries.
Scheduling
tgpost schedule --to release-notes --text "Standup" --cron "0 9 * * 1-5" --tz Europe/London
tgpost schedule --to alerts --text "Still alive" --every 30m
tgpost schedule --to release-notes --file report.pdf --at "2026-09-10T14:00"
tgpost schedule --to alerts --text "in two hours" --at 2h
tgpost jobs # what is scheduled, and when each next runs
tgpost history # what actually happened
tgpost cancel a41a4c18
Jobs are stored in SQLite and survive a restart. Something has to run them:
tgpost daemon # long-running: keeps firing jobs until stopped
tgpost run-due # fires everything due, then exits
Use daemon on a machine that stays up. Use run-due from Windows Task
Scheduler or cron if you would rather not keep a process alive:
schtasks /create /tn "tgpost" /tr "tgpost run-due" /sc minute /mo 5
A job whose time passed while nothing was running still fires when the scheduler comes back, as long as it is within the one-hour grace window. Missed repeats are coalesced, so a weekend of downtime sends once rather than fifty times.
Formatting
The default parse mode is HTML, because it only requires &, < and > to
be escaped. MarkdownV2 requires eighteen characters to be escaped, including
., - and !, which appear in ordinary prose, and a single missed escape is
a hard error rather than a cosmetic one.
tgpost send --to alerts --text "<b>Deploy finished</b>" # markup as written
tgpost send --to alerts --text "$RAW" --escape # escape untrusted text
tgpost send --to alerts --text "plain" --parse-mode none
Pass --escape for text you did not write yourself. Without it, the text is
sent as markup and a stray < will be rejected by Telegram.
Configuration
Config lives at %APPDATA%\tgpost\config.toml on Windows, or
~/.config/tgpost/config.toml elsewhere.
[defaults]
parse_mode = "html"
base_url = "https://api.telegram.org"
[targets.release-notes]
chat_id = "-1001234567890"
description = "Build announcements"
[targets.alerts]
chat_id = "@my_public_channel"
parse_mode = "none"
| Variable | Purpose |
|---|---|
TGPOST_BOT_TOKEN |
Bot token. Preferred over storing it in the config file. |
TGPOST_CONFIG |
Path to the config file. |
TGPOST_DB |
Path to the job database. |
The token is read from --token, then TGPOST_BOT_TOKEN, then the config file.
tgpost targets add never writes the token to disk, so a token supplied on the
command line or in the environment is not persisted by accident.
Limits
Set by the Bot API, not by this package:
| Limit | Value |
|---|---|
| Message text | 4096 characters (tgpost splits automatically) |
| Caption | 1024 characters |
| Photo upload | 10 MB |
| Any other file | 50 MB |
| Album | 2 to 10 items |
| Rate | About 1 message per second per chat, 30 per second overall |
tgpost paces sends to stay under the rate limits, and on a 429 waits exactly
the retry_after Telegram asks for rather than guessing.
To send files larger than 50 MB, run a local Bot API server, which raises the limit to 2000 MB, and point tgpost at it:
tgpost --base-url http://127.0.0.1:8081 send --to release-notes --file big.iso
Python API
from tgpost import TelegramClient
with TelegramClient("123456789:your-token") as client:
client.send_message("-1001234567890", "Build finished")
client.send_file("-1001234567890", "report.pdf", caption="Nightly report")
Errors derive from TgPostError: AuthError, ForbiddenError,
BadRequestError, RateLimitError, ServerError, NetworkError,
FileTooLargeError, ConfigError and SchedulerError. Retryable failures are
retried internally; what reaches you is a failure worth acting on.
License
MIT. See LICENSE.
Metadata
Release files for tgpost 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| tgpost-0.1.0.tar.gz | 32.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tgpost-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 68.6 kB
Release files / tgpost-0.1.0.tar.gz
| Download URL | tgpost-0.1.0.tar.gz |
|---|---|
| Size | 32.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
340e93ccab57b32d95882ac621773e4c17a5e1a27054123a90d8ecd57b880fa6
|
|
BLAKE2b-256 checksum How to use checksums |
193704cb762062c9ecea3fbc7bca76d44cc4e8d192223dcba2904de1219773be
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / tgpost-0.1.0-py3-none-any.whl
| Download URL | tgpost-0.1.0-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00aa7b00464de5495c1abb29ea923c29c2307ff5032484669d4e28f12db82744
|
|
BLAKE2b-256 checksum How to use checksums |
82a253e6812a577e4e2dd0a802128f273486b03720c08979b2b9cd3a0cf86121
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for tgpost 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.