Messaging
C
03 · Tech Stack (Rawclaw)

Messaging Protocol

Matrix-only, never Beeper.

Last synced 2026-04-28

Messaging Protocol — Self-Hosted Matrix Stack

HARD RULE: All platform messaging (WhatsApp, Instagram, Slack, iMessage, Telegram, Signal, Discord) goes through the self-hosted Matrix stack. Never use the Beeper desktop app. Never use a third-party messaging MCP.


Infrastructure

| Component | URL | Notes | |-----------|-----|-------| | Matrix homeserver (Synapse) | http://localhost:8008 | Server name: rawgrowth.local | | WhatsApp bridge (mautrix-whatsapp) | http://localhost:29318 | Logged in as +19493743016 | | Meta bridge (Instagram/Facebook) | http://localhost:29319 | — | | Slack bridge | http://localhost:29320 | — | | Twitter bridge | http://localhost:29321 | — | | Discord bridge | http://localhost:29322 | — |

All containers in /Users/scanbot/RawgrowthOS/engineering/apps/comms-agent/docker-compose.yml. They restart automatically (restart: unless-stopped), but require the Docker daemon (Colima) to be running.

Recovery procedure if Matrix is unreachable on localhost:8008:

  1. colima start — if it fails with "disk in use by instance", run colima stop --force, then pkill -9 limactl, then retry.
  2. cd /Users/scanbot/RawgrowthOS/engineering/apps/comms-agent && docker compose up -d
  3. Wait ~10s, verify: curl -sS -o /dev/null -w "%{http_code}\n" http://localhost:8008/_matrix/client/versions should return 200.
  4. Verify token: curl -sS -H "Authorization: Bearer $MATRIX_TOKEN" http://localhost:8008/_matrix/client/v3/account/whoami returns the user ID.

Agent Bot Credentials

| Field | Value | |-------|-------| | User ID | @agentbot:rawgrowth.local | | Access token | syt_YWdlbnRib3Q_sXJzNktpYuSUJkCIufqd_2IFHMI | | Password (for re-login if token expires) | RawgrowthAgentBot2026!! | | Chris's Matrix user | @chris:rawgrowth.local | | WhatsApp login ID | 19493743016 | | WhatsApp management room | !rfEnUovczRzyOpqPYE:rawgrowth.local |

Token expiry: If the token ever returns M_UNKNOWN_TOKEN, re-login:

curl -s http://localhost:8008/_matrix/client/v3/login \
  -H "Content-Type: application/json" \
  -d '{"type":"m.login.password","user":"agentbot","password":"RawgrowthAgentBot2026!!","device_id":"agentbot-persistent"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"

Sending a Message (Any Platform)

MATRIX_TOKEN="syt_YWdlbnRib3Q_sXJzNktpYuSUJkCIufqd_2IFHMI"
ROOM_ID="!ROOM_ID_HERE:rawgrowth.local"
TXN_ID="agent_$(date +%s%N)"
MSG="Your message here"

curl -s -X PUT \
  "http://localhost:8008/_matrix/client/v3/rooms/$(python3 -c "import urllib.parse; print(urllib.parse.quote('$ROOM_ID'))")/send/m.room.message/$TXN_ID" \
  -H "Authorization: Bearer $MATRIX_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"msgtype\":\"m.text\",\"body\":$(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$MSG")}"

Starting a New WhatsApp DM (no existing room)

CHRIS_TOKEN="syt_Y2hyaXM_QLaSjjFZzVvWvQXWgvUF_0kMdq0"
MGMT_ROOM="!rfEnUovczRzyOpqPYE:rawgrowth.local"
PHONE="19493743016"   # WhatsApp login ID (no +)
TARGET="995595025574" # Target number — NO leading +, digits only

# Step 1: Resolve (verify on WhatsApp)
curl -s -X PUT \
  "http://localhost:8008/_matrix/client/v3/rooms/$(python3 -c "import urllib.parse; print(urllib.parse.quote('$MGMT_ROOM'))")/send/m.room.message/txn_resolve_$(date +%s%N)" \
  -H "Authorization: Bearer $CHRIS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"msgtype\":\"m.text\",\"body\":\"resolve-identifier $PHONE $TARGET\"}"

sleep 3

# Step 2: Create the chat room
curl -s -X PUT \
  "http://localhost:8008/_matrix/client/v3/rooms/$(python3 -c "import urllib.parse; print(urllib.parse.quote('$MGMT_ROOM'))")/send/m.room.message/txn_start_$(date +%s%N)" \
  -H "Authorization: Bearer $CHRIS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"msgtype\":\"m.text\",\"body\":\"start-chat $PHONE $TARGET\"}"

sleep 4

# Step 3: Read the bot's reply to get the new room ID
curl -s "http://localhost:8008/_matrix/client/v3/rooms/$(python3 -c "import urllib.parse; print(urllib.parse.quote('$MGMT_ROOM'))")/messages?dir=b&limit=3" \
  -H "Authorization: Bearer $CHRIS_TOKEN" | python3 -c "
import sys, json
d = json.load(sys.stdin)
for e in d.get('chunk', []):
    body = e.get('content', {}).get('body', '')
    if 'Created chat' in body or 'matrix.to' in body:
        print(body)
"

⚠️ Phone Number Format for WhatsApp

| Format | Works? | |--------|--------| | 19493743016 | ✅ US (no +) | | 995595025574 | ✅ Georgia (no +) | | +99595025574 | ❌ Fails | | +995595025574 | ✅ Resolves but use without + for start-chat |

Rule: Always strip the leading + when sending to the bridge.


Checking for Unread Messages

import json, urllib.request, urllib.parse, time

URL = "http://localhost:8008"
TOKEN = "syt_YWdlbnRib3Q_sXJzNktpYuSUJkCIufqd_2IFHMI"
CHRIS_ID = "@chris:rawgrowth.local"
CUTOFF = int((time.time() - 14 * 86400) * 1000)  # 14 days back

def api(path):
    req = urllib.request.Request(f"{URL}{path}", headers={"Authorization": f"Bearer {TOKEN}"})
    try:
        return json.loads(urllib.request.urlopen(req, timeout=5).read())
    except:
        return {}

# Get Chris's rooms (agentbot must be joined, or use Chris's token)
rooms = api("/_matrix/client/v3/joined_rooms").get("joined_rooms", [])

for room_id in rooms:
    msgs = api(f"/_matrix/client/v3/rooms/{urllib.parse.quote(room_id)}/messages?dir=b&limit=5")
    last = next((m for m in msgs.get("chunk", []) if m.get("type") == "m.room.message"), None)
    if not last:
        continue
    if last.get("sender") == CHRIS_ID:
        continue  # Chris already replied
    if last.get("origin_server_ts", 0) < CUTOFF:
        continue
    print(room_id, last.get("content", {}).get("body", "")[:80])

Bridge Bot IDs (for filtering rooms by platform)

| Platform | Bridge Bot User ID | |----------|--------------------| | WhatsApp | @whatsappbot:rawgrowth.local | | Instagram/Facebook | @metabot:rawgrowth.local | | Slack | @slackbot:rawgrowth.local | | Discord | @discordbot:rawgrowth.local | | Twitter | @twitterbot:rawgrowth.local |


Bridge Status Check

cd /Users/scanbot/BusinessOS/apps/comms-agent && docker compose ps

If any bridge is down:

cd /Users/scanbot/BusinessOS/apps/comms-agent && docker compose restart bridge-whatsapp

WhatsApp reconnect (if TRANSIENT_DISCONNECT): The bridge auto-reconnects. Check status with list-logins in the management room.