Pingram Gateway for Hermes
Chat with your Hermes agent over SMS and Email, routed through Pingram. Text or email your Pingram number/address and the agent replies on the same channel — including inbound MMS images.
This is a self-contained Hermes platform plugin: one plugin.yaml + one
adapter.py. It registers a single pingram platform that serves both channels
(Pingram uses one API key for both). Inbound messages are received by polling
Pingram's logs API on a timer — there's no public endpoint or webhook to set
up. It requires zero changes to Hermes core.
flowchart LR
human["Human"] -->|"SMS / Email"| pingram["Pingram"]
pingram -->|"logs.getLogs (polled every N seconds)"| adapter["PingramAdapter"]
adapter -->|"MessageEvent"| agent["Hermes agent session"]
agent -->|"reply"| send["PingramAdapter.send()"]
send -->|"Pingram Python SDK"| pingram
pingram -->|"SMS / Email"| human
The channel is encoded in the Hermes chat_id prefix: sms:{phone} and
email:{thread}.
Requirements
- A running Hermes agent (
hermes gateway). - A Pingram account with an API key (
pingram_sk_...). Sender identity is optional — Pingram provides a default SMS number and email sender, so you can start without provisioning your own. - Python packages
pingram-python(the Pingram SDK) andaiohttpavailable to the gateway — installed automatically with thepip installmethod below. - No public host, tunnel, or webhook registration is required — the adapter polls Pingram for new messages.
Install
Option 1: pip (recommended)
Install from PyPI into the same environment as your Hermes agent. This pulls in
the Pingram SDK and aiohttp for you, and registers the plugin via its
hermes_agent.plugins entry point — the gateway discovers it automatically.
pip install hermes-pingram-gateway
hermes-pingram-gatewayis the plugin. It depends onpingram-python, the Pingram SDK (imported aspingram); the two are separate packages.
Option 2: hermes plugins install (from source)
hermes plugins install pingram-io/hermes-gateway
pip install pingram-python aiohttp
hermes plugins install git-clones this repo into ~/.hermes/plugins/pingram/
and the gateway auto-discovers it on next start. Because it doesn't install
Python dependencies, you install the SDK and aiohttp yourself.
Configure
You can configure Pingram two ways — a guided wizard (recommended) or by editing env vars directly.
Option A: Guided setup (recommended)
First enable the plugin so it shows up in the gateway setup menu, then run the wizard:
hermes plugins enable pingram
hermes setup gateway
Select Pingram from the messaging-platforms list and answer the prompts.
The wizard asks for your region and API key, then which channel(s) to enable
(SMS, Email, or both). Sender details are optional — for SMS you can use your
Pingram account's default number or pin a specific one; for Email you can set a
display name and address or leave them blank for Pingram's defaults. It also
asks how often to poll for new messages (default every 15s). It writes
everything to ~/.hermes/.env for you — then just start the gateway.
Option B: Manual env vars
Set these in your Hermes env (~/.hermes/.env) — see .env.example
for the full list. At minimum you need the API key, region, and channel(s).
# Required:
PINGRAM_API_KEY=pingram_sk_...
PINGRAM_REGION=us # us | eu | ca — must match your Pingram account's region
PINGRAM_CHANNELS=sms,email # which channels to enable: sms, email, or both
# Senders are OPTIONAL — leave them unset to use Pingram's account defaults.
#PINGRAM_FROM_SMS=+15551234567 # pin a specific verified Pingram number
#PINGRAM_FROM_EMAIL=agent@yourdomain.com # sender address on a verified domain
#PINGRAM_FROM_NAME=My Bot # email sender display name
# Inbound polling cadence (optional):
#PINGRAM_POLL_INTERVAL=15 # seconds between polls (default 15)
#PINGRAM_POLL_LIMIT=50 # max messages fetched per poll page
# Everything below is optional (defaults shown / commented out):
#PINGRAM_ALLOWED_USERS=+15559876543,you@yourdomain.com
#PINGRAM_ALLOW_ALL_USERS=false # dev only
Set
PINGRAM_REGIONto the region your Pingram account lives in (us,eu, orca). It selects the API endpoint — the wrong value will fail to send.
Enable the platform in your gateway config (~/.hermes/config.yaml):
plugins:
enabled: true
gateway:
platforms:
pingram:
enabled: true
Start (or restart) the gateway:
hermes gateway restart
How inbound messages are received
The adapter polls Pingram's logs API (logs.getLogs) every
PINGRAM_POLL_INTERVAL seconds (default 15). Each cycle it fetches the newest
messages, keeps the ones that arrived since the last poll, and dispatches the
inbound SMS/Email to your agent. A startup watermark means only messages that
arrive after the gateway starts are delivered (history isn't replayed), and
per-message tracking-id deduplication guards against reprocessing.
This means there's nothing to expose to the internet — no public host, tunnel, or webhook registration. Just start the gateway and text/email your Pingram number/address.
Trade-offs versus a push webhook:
- Latency: delivery is delayed by up to one poll interval (lower the interval for snappier replies, at the cost of more API calls).
- Email attachments: inbound email attachments are not downloaded — Pingram's logs API returns attachment metadata only, not content. Inbound SMS/MMS images are fetched and passed to the agent for vision. Outbound file replies (the agent attaching files to an email) still work.
SMS quickstart
- Set
PINGRAM_API_KEY,PINGRAM_REGION, andPINGRAM_CHANNELS=sms. Optionally pinPINGRAM_FROM_SMS; otherwise Pingram uses your default number. - Add your phone to
PINGRAM_ALLOWED_USERS. - Start the gateway.
- Text your Pingram number — the agent replies by SMS within a poll interval. Inbound MMS images are passed to the agent for vision.
Email quickstart
- Set
PINGRAM_API_KEY,PINGRAM_REGION, andPINGRAM_CHANNELS=email. Optionally setPINGRAM_FROM_EMAIL/PINGRAM_FROM_NAME; otherwise Pingram uses its default sender. - Add your email to
PINGRAM_ALLOWED_USERS. - Start the gateway.
- Email your Pingram address — the agent replies in-thread (
Re:subject) within a poll interval. The agent's file replies are sent back as email attachments. (Inbound email attachments are not downloaded when polling.)
Security
The logs API is account-scoped and authenticated by PINGRAM_API_KEY, so there's
no inbound endpoint to authenticate. Defense-in-depth still applies to who the
agent will talk to:
- User allowlist — an inbound message's
frommust be inPINGRAM_ALLOWED_USERSunlessPINGRAM_ALLOW_ALL_USERS=true. With no allowlist andfalse, inbound messages are ignored. - Deduplication — each message is processed once (keyed on its Pingram tracking id), even if it appears across consecutive polls.
- Message bodies and secrets are never logged; phone numbers/emails are redacted.
Configuration reference
| Env var | Required | Default | Description |
|---|---|---|---|
PINGRAM_API_KEY |
yes | — | Pingram API key (pingram_sk_...). |
PINGRAM_REGION |
yes | us |
us | eu | ca. Must match your account's region — selects the API endpoint. |
PINGRAM_CHANNELS |
yes* | inferred | Channels to enable: sms, email, or sms,email. *Optional only if a sender is set (legacy inference). |
PINGRAM_FROM_SMS |
no | Pingram default | Pin a specific verified SMS sender number (E.164). Blank → account default. |
PINGRAM_FROM_EMAIL |
no | noreply@pingram.io |
Email sender address (verified domain). Blank → Pingram default. |
PINGRAM_FROM_NAME |
no | Pingram default | Email sender display name (e.g. My Bot). |
PINGRAM_POLL_INTERVAL |
no | 15 |
Seconds between polls for new inbound messages. |
PINGRAM_POLL_LIMIT |
no | 50 |
Max messages fetched per poll page. |
PINGRAM_ALLOWED_USERS |
no | — | Allowed phones/emails (CSV). |
PINGRAM_ALLOW_ALL_USERS |
no | false |
Allow everyone (dev only). |
PINGRAM_NOTIFICATION_TYPE |
no | hermes_agent_reply |
Pingram notification type. |
Known limitations (V1)
- Inbound email attachments: not downloaded. Polling Pingram's logs API returns attachment metadata only, not content. Inbound SMS/MMS images and outbound email file replies work fully.
- Latency: inbound delivery is delayed by up to one
PINGRAM_POLL_INTERVAL. - Outbound SMS MMS: the Pingram send API has no SMS media field, so the agent can't attach locally-generated images to an SMS. If a public image URL is available it's appended to the message text; otherwise a short note is added.
License
MIT — see LICENSE if present, otherwise this plugin is provided under the MIT License.
Metadata
Release files for hermes-pingram-gateway 1.0.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 | |
|---|---|---|---|
| hermes_pingram_gateway-1.0.0.tar.gz | 19.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hermes_pingram_gateway-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.9 kB
Release files / hermes_pingram_gateway-1.0.0.tar.gz
| Download URL | hermes_pingram_gateway-1.0.0.tar.gz |
|---|---|
| Size | 19.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2536278c68cc81442bb07f4873bd94fe24ff963c10bbbf5e6fb74c7aaaa72722
|
|
BLAKE2b-256 checksum How to use checksums |
a4e40f45c7825441a54ad17b4afce6d068aa9ec3337c55d90df84e3a665bef0f
|
| 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 Jun 2, 2026.
Transparency logRelease files / hermes_pingram_gateway-1.0.0-py3-none-any.whl
| Download URL | hermes_pingram_gateway-1.0.0-py3-none-any.whl |
|---|---|
| Size | 17.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bfba86f466c2f691cb01e0f8198a93810995b9b07189a87b99515586a8bd2baf
|
|
BLAKE2b-256 checksum How to use checksums |
74285c768377d2b984eda9e063e5cc7d067a26fe4d5f6acb24f65b021593465d
|
| 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 Jun 2, 2026.
Transparency log