Give any AI agent (LangChain or anything else) a real messaging channel: send texts, read incoming screenshots, PDFs and voice notes, react with tapbacks, show typing indicators, send tappable buttons, and schedule cron check-ins. One interface, six swappable hosted providers — no Mac required.
Build things like an accountability coach that texts you to hit the gym, checks in on schedule, and judges your workout screenshots.
your phone ⇄ iMessage / Telegram / WhatsApp ── webhook/WS ──▶ FastAPI (Railway) ──▶ your agent
▲ │
└──────────── send / media / react / buttons ◀────────────┘
Set TEXTDM_PROVIDER and the matching credentials in .env (see
.env.example). Switching providers changes nothing above the transport.
| Provider | Channel | Free tier | Notes |
|---|---|---|---|
telegram |
Telegram | Free, unlimited | Official Bot API. No approval, no per-message cost, no sending window. Easiest way to run this end to end. |
whapi |
Free dev sandbox | Connected account — QR-pair your own number. No Meta verification, no templates, no 24h window, native groups. ~$12–35/mo. Drives WhatsApp Web, so outside WhatsApp's terms; keep volume human-shaped. | |
messagesdev |
iMessage | 50 msgs/day sandbox (your own paired number) | Best iMessage API: reply threading, audio transcription, HMAC webhooks. $99/mo per production line. |
sendblue |
iMessage | Free API plan / AI-agent plan | Proven; tapbacks + typing verified. Inbound tapbacks are heuristic (arrive as Loved "..." text). |
loopmessage |
iMessage | Sandbox: 5 contacts, unlimited msgs | Contact must text the sandbox number first (24h reply windows). $59.99/mo Light. |
clawmessenger |
iMessage | 7-day trial, $5/mo for 250 msgs | Text send is REST; media/reactions/typing/receiving are WebSocket (background listener, no webhook needed). |
Why not Meta's WhatsApp Cloud API? It's the official path, but since July 2025 it bills per message and only allows free-form replies inside 24 hours of the user's last message. A scheduled 6pm nudge lands outside that window and needs a paid pre-approved template — which breaks the whole point of the scheduler. A connected account has no such window. Add the Cloud API adapter if you need an official business identity.
python3 -m venv .venv && .venv/bin/pip install -e ".[langchain,media,clawmessenger]"
cp .env.example .env # fill in provider credentials
.venv/bin/python test_textdm.py # sanity check, no networkFastest path to a working agent — Telegram, about two minutes:
- Message @BotFather,
/newbot, copy the token intoTELEGRAM_BOT_TOKEN. - Set
TELEGRAM_WEBHOOK_SECRETto any random string andTELEGRAM_WEBHOOK_URLtohttps://<your-host>/webhooks/telegram. - Deploy (or
ngrok http 8000) and start the app — it registers the webhook on boot.
Registering the webhook is what enables reaction events: Telegram only
delivers them when message_reaction is in allowed_updates, which
setup_webhook() handles. Skip it and reactions silently never arrive.
Run the example gym-coach agent:
TEXTDM_PROVIDER=sendblue uvicorn examples.gym_coach:app --port 8000Point the provider's webhook at https://<host>/webhooks/<provider>.
On Railway: deploy this repo, add env vars, use the generated domain.
from textdm import create_app, get_transport, to_content_blocks, InboundMessage
from textdm.langchain_tools import build_tools
transport = get_transport("telegram")
tools = build_tools(transport, scheduler) # send / media / react / buttons / schedule
async def on_message(msg: InboundMessage):
blocks = await to_content_blocks(msg, transport) # text + images + PDFs + transcripts
... # hand `blocks` to your agent as the user turn
app = create_app(on_message, [transport]) # FastAPI; run with uvicornEvery provider normalizes into the same InboundMessage: text, attachments,
tapback reactions, button taps (callback_data), group flag, and the raw
payload if you need provider-specific fields.
An attachment URL is useless to a model — it can't open one, and Telegram and
WhatsApp don't even hand out fetchable URLs (you get an opaque media id that
needs an authenticated call). to_content_blocks() closes that gap: it pulls
the bytes through the transport and returns Anthropic content blocks.
| Sent to your agent | What it becomes |
|---|---|
| Screenshot / photo | image block the model actually sees (>5 MB degrades to a note) |
document block, read natively |
|
| Voice note | transcribed to text — via the provider when it does that itself (messages.dev, LoopMessage), otherwise Deepgram nova-3 |
| Button tap | noted in the lead text block as [tapped button <id>] |
Claude has no speech-to-text, so voice needs the transcription hop. Set
DEEPGRAM_API_KEY and install the media extra; without it everything else
still works and voice notes come through as a short note instead.
textdm/
models.py # InboundMessage / Attachment / Reaction / SendResult
media.py # attachments -> content blocks; Deepgram transcription
server.py # FastAPI webhook receiver + dedup + WS listener startup
scheduler.py # cron pushes that invoke your agent with a prompt
langchain_tools.py # ready-made LangChain tools
transports/ # base.py + one adapter per provider
examples/gym_coach.py # end-to-end accountability-coach agent
- Adapters are written against each provider's current docs; only webhook parsing is unit-tested. First live send per provider needs real credentials. Deepgram transcription is verified end to end against a real key.
- Telegram reactions on bot-sent messages in 1:1 chats are unconfirmed.
Telegram's docs and grammY say private chats deliver
message_reaction; one field report says reactions on bot-authored messages in DMs produce no event. Reactions on the user's own messages are fine. Verify with a live bot before building on the tapback flow. - Tapbacks have no cross-platform equivalent. Telegram accepts only a fixed
emoji allow-list for bots, with no
‼️and no❓, soEMPHASIZEmaps to 🔥 andQUESTIONto 🤔. WhatsApp takes any emoji and uses the literal ones. - Whapi doesn't sign webhooks. Set
WHAPI_WEBHOOK_SECRETand configure the same value as anAuthorizationheader on their webhook, or the endpoint is unauthenticated. - Telegram caps bot file downloads at 20 MB; larger attachments can't be read.
- Sendblue inbound-tapback detection is undocumented behavior — verify with a live tapback before relying on it.
- Claw Messenger webhooks are unadvertised/undocumented; its adapter uses their WebSocket instead.
- Dedup is in-memory (fine for one replica; move to Postgres to scale out).