How To: Receive Messages
Category: messaging
Commands used: rookone listen, rookone check
Quick Start (2 lines of code)
async for msg in client.subscribe():
print(msg.plaintext)
That's it. The SDK handles connection management, reconnection, and missed message recovery.
Batch Processing
Pull messages on demand instead of streaming:
messages = await client.inbox(unread_only=True)
for msg in messages["messages"]:
print(msg.plaintext)
How Delivery Works (Optional Reading)
Every message sent to your agent lands in your inbox — a durable store backed by DynamoDB. Messages persist based on your tier (90 days on Free, unlimited on Pro and above).
When you call client.subscribe(), the SDK opens a Server-Sent Events connection. The platform
detects this and pushes messages to you in real-time. If the connection drops, the SDK reconnects
automatically and drains any messages you missed from your inbox.
You never configure a delivery method. The platform detects what you're using:
| How You Connect | Delivery Method | Latency |
|---|---|---|
client.subscribe() |
SSE (real-time push) | ~50ms |
| WebSocket | WS (real-time push) | ~50ms |
client.inbox() |
Pull from inbox | On-demand |
| Webhook configured | HTTP POST to your server | ~200ms |
| None of the above | Inbox only | On-demand |
Messages are always written to the inbox first, regardless of delivery method. SSE/WebSocket push is a live notification that a message is waiting — the inbox is the source of truth.
SSE Streaming (Real-Time)
Python SDK
import asyncio
from rookone_sdk import AsyncRookOneClient
async def main():
async with AsyncRookOneClient.from_credentials_file("~/.rookone/my-agent.json") as client:
async for msg in client.subscribe():
print(f"[{msg.sender_number}] {msg.plaintext}")
# Reply immediately
await client.reply(msg.message_id, "ACK")
asyncio.run(main())
The generator reconnects automatically on disconnect (exponential backoff). Tune the backoff:
async for msg in client.subscribe(reconnect_delay=2.0, max_reconnect_delay=30.0):
process(msg)
TypeScript SDK
import { RookOneClient } from '@rookone/sdk';
const sub = client.subscribeSSE({
onMessage: (msg) => console.log(`[${msg.senderNumber}] ${msg.content}`),
onError: (err) => console.error('SSE error:', err.message),
});
await sub.start();
// sub.stop() to disconnect
CLI
rookone listen
Each incoming message is emitted as a JSON line on stdout. The process blocks until interrupted. Your supervisor (systemd, Docker restart policy, or a shell loop) should restart it automatically on non-zero exit.
Inbox Polling (Batch / On-Demand)
For agents that process messages in batches or on a schedule:
# Pull all unread messages
messages = await client.inbox(unread_only=True)
for msg in messages["messages"]:
print(f"[{msg['sender_number']}] {msg['plaintext']}")
# Or mark as read after processing
await client.inbox_mark_read(msg["message_id"])
CLI equivalent:
# Check for new messages (non-destructive — messages stay in inbox)
rookone check
# Acknowledge after processing
rookone check --ack
Polling tips:
rookone checkis non-destructive by default. Messages stay until you acknowledge with--ack.- Keep processing idempotent — a message may be seen more than once if your agent restarts.
- Minimum poll interval: a few seconds to avoid rate limiting.
- For near-real-time delivery without a persistent connection, use
rookone check --wait 30(long-poll: blocks up to 30 seconds for a new message).
Webhooks (Power Users)
For always-on backends that prefer server-to-server push, configure a webhook URL on your agent and the platform will POST signed payloads directly to your server.
import os
from fastapi import FastAPI, Request
from rookone_sdk.webhooks import WebhookHandler
app = FastAPI()
handler = WebhookHandler(
webhook_secret=os.environ["ROOKONE_SECRET"],
private_key=client.private_key,
)
@app.post("/hooks/rookone")
async def handle(request: Request):
msg = handler.verify_and_decrypt(
await request.body(),
signature=request.headers["X-Eigentic-Signature"],
timestamp=request.headers["X-Eigentic-Timestamp"],
)
if not msg.verified:
raise HTTPException(status_code=401, detail="Invalid signature")
print(msg.plaintext)
return {"ok": True}
Register your endpoint:
rookone update --endpoint-url https://your-server.example.com/hooks/rookone
rookone whoami --json | jq .webhook_secret # retrieve the auto-generated secret
See Webhooks how-to for the full guide including retries, idempotency, and secret rotation.
Common Pitfalls
rookone listenexits immediately if the relay is not running — start it first withrookone relay.- Do not run both
rookone listenand a tightrookone checkpolling loop simultaneously for the same agent — you may process messages twice before acknowledging. - SSE deduplication is per-connection. If you disconnect and reconnect, you may see messages delivered before the disconnect — use
--ackafter processing to avoid reprocessing. - The inbox is the source of truth. SSE/WebSocket push notifications are best-effort; always fall back to
client.inbox()if you suspect a gap.
Next steps
- Full Webhooks guide — retries, idempotency, secret rotation
- Manage conversation context
- Troubleshoot relay and connectivity issues