Minimal Telegram alerting framework for bots and scripts.
Project description
Pingram
Send Telegram messages with one line of Python. No webhooks. No bloat. Just pings.
Pingram is an ultra-lightweight Python wrapper for sending outbound Telegram messages via your bot. It's designed as a cost-free alternative to email and SMS, focused on one-way "ping"-style messaging — ideal for alerts, reports, logs, and automated notifications.
Looking for a minimal alternative to
python-telegram-bot? Pingram avoids the event loop, handlers, and 7.8MB install size, focusing solely on outbound pings with just one method per use case.
Lightweight by Design
Pingram prioritizes size, speed, and clarity. Designed to be imported and deployed instantly.
| Package | Size |
|---|---|
| Pingram (core) | ~20 KB |
| Pingram + AsyncPingram | ~21 KB |
| Pingram + httpx | ~800 KB |
| python-telegram-bot | ~7.8 MB |
Result:
- Pingram is over 9× smaller than PTB even with
httpxincluded. - Pingram core is ~390× smaller than PTB alone.
- That's a ~90% reduction with dependencies, and ~99.7% without.
Perfect for:
- Minimal Docker containers
- Constrained environments
- Clean, single-purpose automation
Features
- Send messages, photos, documents, audio, and video
- Direct method calls:
bot.message(),bot.send_photo(), etc. - Sync and async clients —
Pingram(sync) +AsyncPingram(async) with identical APIs - Minimalistic architecture, no listeners or webhooks
- Built on
httpx(sync + async) - Retries with backoff on transient failures (429, 5xx, network errors)
- Typed exception hierarchy you can opt into
- No webhook setup required
Who is it for?
- Developers who want zero-setup Telegram alerts
- Sysadmins replacing email/SMS for cron/CI jobs
- Raspberry Pi or IoT projects needing compact tooling
- Traders, scrapers, and bots that need lightweight push
Installation
pip install pingram
Requires Python 3.9 or newer.
Quickstart
from pingram import Pingram
bot = Pingram(token="<BOT_TOKEN>")
bot.me()
A simple method for testing your bot's authentication token. Requires no parameters. Returns basic information about the bot in form of a User object. https://core.telegram.org/bots/api#getme
Since every high-level api function returns a httpx.Response object, you can append the end of a function call using .text to show the raw HTTP response instead of the status code.
bot.message(chat_id=123456789, text="Hello Friend").text
This call returns a success or error message from the Telegram API.
Async usage
For asyncio applications, FastAPI handlers, Jupyter notebooks, or anywhere you want to fire multiple pings concurrently, use AsyncPingram. It mirrors the sync API exactly — same methods, same kwargs, same typed errors.
import asyncio
from pingram import AsyncPingram
async def main():
async with AsyncPingram(token="<BOT_TOKEN>") as bot:
await asyncio.gather(
bot.message(chat_id=123, text="step 1 done"),
bot.message(chat_id=123, text="step 2 done"),
bot.send_photo(chat_id=123, path="chart.png"),
)
asyncio.run(main())
Three supported lifecycle shapes:
# 1. async with (recommended — guarantees client cleanup)
async with AsyncPingram(token="...") as bot:
await bot.message(chat_id=123, text="hi")
# 2. manual aclose (parity with how Pingram is used)
bot = AsyncPingram(token="...")
try:
await bot.message(chat_id=123, text="hi")
finally:
await bot.aclose()
# 3. fire-and-forget (allowed but emits a resource warning at GC time)
bot = AsyncPingram(token="...")
await bot.message(chat_id=123, text="hi")
Retries, typed errors (PingramError, TelegramAPIError, RateLimitError, TransportError), and per-call _raise / _retries overrides all work identically to the sync Pingram. See the Error handling and Retry policy sections below.
Media Examples
All media-sending methods accept both local file paths and direct URLs. Ensure URLs are direct links (i.e. ending in
.jpg,.mp4,Content-Typeheaders.
Send Photo
bot.send_photo(
chat_id=123456789,
path="https://example.com/image.jpg",
caption="Test Photo"
)
From a local file:
bot.send_photo(
chat_id=123456789,
path="photo.jpg",
caption="Local Image"
)
Send Document
bot.send_doc(
chat_id=123456789,
path="https://example.com/file.pdf",
caption="Monthly Report"
)
From a local file:
bot.send_doc(
chat_id=123456789,
path="report.pdf",
caption="Monthly Report"
)
Send Audio
bot.send_audio(
chat_id=123456789,
path="https://www.myinstants.com//media/sounds/hello-friend-mr-robot.mp3",
caption="Greetings."
)
From a local file:
bot.send_audio(
chat_id=123456789,
path="audio.mp3",
caption="Shower Thoughts"
)
Send Video
bot.send_video(
chat_id=123456789,
path="https://yourdomain.com/video.mp4", # must be direct link to .mp4
caption="Security Footage"
)
From a local file:
bot.send_video(
chat_id=123456789,
path="stranger.mp4",
caption="Security Footage"
)
Additional Request Data
Including additional data such as a caption, description or any other key, value types supported by the Telegram API can be passed through any API call simply by including it in the params of the function.
bot.send_video(
chat_id=123456789,
path="hamsters.mp4",
caption="Playful Hamsters",
has_spoiler=True
)
The
has_spoilerparameter is a native Telegram option. It must be passed as a bool.
Error handling
By default, pingram preserves the 0.3.x contract: methods return the final httpx.Response even if it carries a non-2xx status, and transport errors propagate as their underlying httpx exceptions.
Opt into typed exceptions with raise_on_error=True:
from pingram import Pingram, PingramError, RateLimitError, TelegramAPIError, TransportError
bot = Pingram(token="<BOT_TOKEN>", raise_on_error=True, retries=5)
try:
bot.message(chat_id=123, text="hello")
except RateLimitError as exc:
# exc.retry_after carries Telegram's suggested delay if provided
...
except TelegramAPIError as exc:
# non-2xx response that retries couldn't fix
print(exc.status_code, exc.description)
except TransportError as exc:
# network/transport failure after retries exhausted
...
except PingramError:
# catch-all base if you prefer
...
You can also override per call:
bot.message(chat_id=123, text="hello", _raise=True, _retries=0)
_raise and _retries kwargs are stripped before the payload is forwarded to Telegram.
MarkdownV2 escaping
Telegram's MarkdownV2 parse mode needs backslash-escaping for a long list of characters. Pingram ships three small helpers so you don't have to remember which is which:
from pingram import (
escape_markdown_v2,
escape_markdown_v2_code,
escape_markdown_v2_link_url,
)
# Plain text body
bot.message(
chat_id=123,
text=f"⚠️ *{escape_markdown_v2(hostname)}* is down",
parse_mode="MarkdownV2",
)
# Inside an inline code span (only backtick and backslash need escaping)
bot.message(
chat_id=123,
text=f"Run `{escape_markdown_v2_code(command)}` to retry",
parse_mode="MarkdownV2",
)
# Inside a link URL (only `)` and backslash need escaping)
bot.message(
chat_id=123,
text=f"[details]({escape_markdown_v2_link_url(url)})",
parse_mode="MarkdownV2",
)
The helpers are pure functions — no await, no client needed.
Retry policy
| Condition | Behaviour |
|---|---|
| 2xx / 3xx | Return immediately |
| 400, 401, 403, 404 | Fail-fast — no retry |
| 429 | Retry; honour parameters.retry_after if present, else exponential backoff |
| 5xx | Retry with exponential backoff |
| Connection error / timeout | Retry with exponential backoff |
Set retries=0 for pre-0.4.0 behaviour (single attempt, no retries).
Tests
Pingram ships a two-tier test suite:
- Unit tests (
tests/unit/) — fast, deterministic, mocked at the httpx transport layer withrespx. These run on every PR via GitHub Actions across Python 3.9–3.13. - Integration tests (
tests/integration/) — opt-in, real-API tests that send actual messages. Useful for edge-case detection (rate limits, content-type mismatches). RequireBOT_TOKEN+CHAT_IDenv vars and are run frommainonly.
To run them locally:
# Unit tests (no creds required)
pytest
# Integration tests (real-API, .env with BOT_TOKEN + CHAT_ID)
pytest tests/integration -m integration
Roadmap
- Retry and error handling
- Package tests and CI integration
- Async mode (
AsyncPingram) - Message templating engine
- Std input/message collectors
- Webhook-to-Telegram bridge
Maintained — issues and PRs welcome.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pingram-0.5.1.tar.gz.
File metadata
- Download URL: pingram-0.5.1.tar.gz
- Upload date:
- Size: 16.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1013d923b21ad8ed50159197aa5ed0375dbe8dcc9859ada90dd8772e6f210843
|
|
| MD5 |
7d6dc7f6b52b4a65ce0d7bddd342846c
|
|
| BLAKE2b-256 |
bb9eb2981cb95d2800278f3c7b552ba08c69a7932216650bd9ac6b833d3559d2
|
Provenance
The following attestation bundles were made for pingram-0.5.1.tar.gz:
Publisher:
release.yml on zvizr/pingram
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pingram-0.5.1.tar.gz -
Subject digest:
1013d923b21ad8ed50159197aa5ed0375dbe8dcc9859ada90dd8772e6f210843 - Sigstore transparency entry: 1581702830
- Sigstore integration time:
-
Permalink:
zvizr/pingram@37466d940b8e45e11acb0b2fbfbf93d530c84929 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/zvizr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@37466d940b8e45e11acb0b2fbfbf93d530c84929 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pingram-0.5.1-py3-none-any.whl.
File metadata
- Download URL: pingram-0.5.1-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84f4fb1d1174fdb670f03bb0813bdeb7ec7e32deec3bcae01c07b4dc6487c56d
|
|
| MD5 |
4cd9d1cbe1da2621657161149519c2f2
|
|
| BLAKE2b-256 |
807949fb301614abc378c236f45810d524f32a504fdae0903e5defa2e74c5098
|
Provenance
The following attestation bundles were made for pingram-0.5.1-py3-none-any.whl:
Publisher:
release.yml on zvizr/pingram
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pingram-0.5.1-py3-none-any.whl -
Subject digest:
84f4fb1d1174fdb670f03bb0813bdeb7ec7e32deec3bcae01c07b4dc6487c56d - Sigstore transparency entry: 1581702975
- Sigstore integration time:
-
Permalink:
zvizr/pingram@37466d940b8e45e11acb0b2fbfbf93d530c84929 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/zvizr
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@37466d940b8e45e11acb0b2fbfbf93d530c84929 -
Trigger Event:
push
-
Statement type: