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.
Metadata
Release files for pingram 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pingram-0.5.1.tar.gz | 16.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pingram-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.6 kB
Release files / pingram-0.5.1.tar.gz
| Download URL | pingram-0.5.1.tar.gz |
|---|---|
| Size | 16.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1013d923b21ad8ed50159197aa5ed0375dbe8dcc9859ada90dd8772e6f210843
|
|
BLAKE2b-256 checksum How to use checksums |
bb9eb2981cb95d2800278f3c7b552ba08c69a7932216650bd9ac6b833d3559d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency logRelease files / pingram-0.5.1-py3-none-any.whl
| Download URL | pingram-0.5.1-py3-none-any.whl |
|---|---|
| Size | 15.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
84f4fb1d1174fdb670f03bb0813bdeb7ec7e32deec3bcae01c07b4dc6487c56d
|
|
BLAKE2b-256 checksum How to use checksums |
807949fb301614abc378c236f45810d524f32a504fdae0903e5defa2e74c5098
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency log