Be honest with yourself about what that means: it is your own WhatsApp account, paired as a linked device. It is powerful and it is not a sanctioned business integration. Everything dangerous in it is fail-closed by design.
What it gives Plexward
- Read-only group digests. For each configured group, only new messages since the last watermark are pulled, bundled, and summarized into a markdown digest written into that entity's brain. The digest pipeline never sends.
- Triage and drafts. An inbound pipeline matches messages against rules and either ignores them, notifies you, or drafts a reply into the entity's brain for review.
- Human-approved outbound. A draft is sent only after you press Approve in Telegram. Edit sends your corrected text. Reject and expiry send nothing.
- Voice profiling. A corpus of your own past messages is used to draft in your voice rather than in a generic assistant register.
- Dashboard surface. Session status, QR pairing, digests and message volume appear in the dashboard's WhatsApp tab.
Layer
service. One gateway process serves the whole deployment, and per-entity configuration lives inside it: each entity maps to a key in the digest config and to the groups that belong to it.
It is not probeable by claude mcp list, so it reports unprobed. That is correct. Its real health check is the gateway's own status endpoint.
Declare it as:
{ "name": "WhatsApp", "layer": "service", "svc": "Digests ยท Approved sends", "account": "acme groups" }
Prerequisites
- Node.js 22 or newer and a process manager.
- A Chrome or Chromium binary, because the gateway drives a real browser.
- A phone with the WhatsApp account you intend to link, physically available for the QR scan.
tg-bridgealready running if you want outbound sends. Without it, sends fail closed, which is the intended behaviour and not a bug.
Setup steps
1. Install and configure the gateway.
cd wa-sender/gateway
npm install
cp .env.example .env.local # token folder, chrome binary path
cd ../integration && npm install
The gateway binds localhost only. Do not put it on a public interface. It holds a live WhatsApp session.
2. Run it.
cd wa-sender
pm2 start ecosystem.config.js && pm2 save
The process manager config resolves paths from its own location plus your deployment env file. If you use a Node version manager, pin the Node binary path there, or the service will not survive a reboot.
3. Pair the session, once.
curl -X POST http://localhost:3000/api/whatsapp # start the session
curl http://localhost:3000/api/whatsapp # status plus QR payload
On your phone: WhatsApp, Menu, Linked Devices, Link a Device, scan. Session tokens persist under tokens/, so restarts reconnect without re-pairing. If the dashboard is running, its WhatsApp tab shows the QR and the status with no curl needed.
4. Configure per-entity digests.
cp digest.config.example.json digest.config.json
Map each entity slug to its groups. This file holds real group ids and is gitignored; it never enters the repo.
5. Configure triage rules in integration/watch.yml. Rules are first-match wins: ignore, notify, or triage. Start with almost everything ignored and widen deliberately.
6. Wire the entity's registry record so the dashboard and the briefings know which digest key belongs to which entity.
Per-entity routing
Routing here is data, not doctrine: an entity's digest key and its group ids are what scope it. Still record it in the brain, because an agent drafting an outbound message needs to know which conversation is in scope:
## Owned Integrations
| Service | Scope | Notes |
|---|---|---|
| WhatsApp (wa-sender) | Acme groups | Digest key `acme`. Read-only pipeline. Outbound only through the Telegram approval gate. |
## NOT This Company
- Any WhatsApp group not listed under the `acme` digest key.
The gateway itself has no notion of entities. If you put another entity's group under the wrong key, its messages land in the wrong brain and nothing will warn you.
Verify
curl -s http://localhost:3000/api/whatsapp | head -c 200 # session status
curl -s http://localhost:3000/api/groups | head -c 300 # group list
./run-digests.sh acme # one entity, manually
Expected: a connected session status, a group list that includes the entity's groups, and a dated digest markdown file appearing in the entity's brain. Then check the dashboard's WhatsApp tab shows the same session as connected.
For outbound, run one approval end to end before you trust it: draft, approve in Telegram, confirm the message arrived. Then run one and reject it, and confirm nothing was sent.
Common failures
- The session drops and stays dropped. WhatsApp Web sessions expire, get logged out from the phone, or break after a long disconnect. Re-pair through the dashboard tab. Build the expectation that this happens occasionally.
- The browser binary moved. A Chrome update or a distro change breaks the path in
.env.local. The gateway fails at startup with a puppeteer error. - A Node version manager path in the process manager config. Works interactively, dead after reboot. Pin the absolute versioned path.
- Digest watermarks make a re-run look empty. The pipeline pulls only new messages. A second run the same hour legitimately produces nothing. That is the watermark working, not a failure.
- The approval gate is down and sends fail. Intended. Fail-closed means a broken gate stops outbound entirely rather than letting an agent send unreviewed. Fix the gate, do not bypass it.
- Real message content on disk. The corpus, transcripts, digest working directories and logs contain real conversations. They are gitignored for a reason. Treat that directory as sensitive personal data, including in backups.
What Plexward does with it once connected
A weekly cron pulls new messages per configured group, bundles them, generates a digest with a headless agent run from a neutral working directory, and writes the result into the entity's brain. The daily brief reads the newest digest and surfaces anything that needs you.
The triage pipeline tails inbound messages, applies your rules, and for matched messages stages a drafted reply in the brain plus a notification in the entity's Telegram topic. Outbound goes through send-approved, which requests an approval, blocks until you press a button, and only then posts the send to the gateway.
Full setup detail lives in wa-sender/QUICK_START.md and the architecture in wa-sender/README.md.