Messages are messy. Sometimes they just need a Messi assist.
Messistant is a self-hosted WhatsApp assistant built around reusable assists. It observes a linked WhatsApp Web session, keeps a privacy-minded activity ledger, and dispatches commands or automatic events to independently configured assists.
This is an MVP, but not a proof of concept: configuration is persistent, credentials are protected, access is explicit, message events are deduplicated, and the admin console handles the complete WhatsApp QR flow.
- Explains Argentine Spanish with
!!!, using a quoted or recent message. - Conjugates verbs with
!conj <verb> [tense/person]. - Automatically transcribes allowlisted incoming voice notes with OpenAI, or
transcribes any quoted voice note when the owner replies with
!stt. - Reports private activity with
!stats todayor!stats weekin your self-chat. - Collects message metadata continuously for durable statistics.
- Provides a password-protected admin console for WhatsApp, OpenAI, retention, assists, and access policies.
Messistant never hardcodes phone numbers or assist audiences. Each assist can be disabled, restricted to the self-chat, restricted to the owner, attached to reusable access groups, assigned one-off chat IDs, or made available everywhere.
WhatsApp Web
│
▼
normalized message event
│
├── deduplicated metadata ledger (SQLite)
│
▼
assist dispatcher
├── per-chat execution queue
├── assist-specific access policy
└── reusable access groups
│
├── !!! explanation
├── !conj
├── voice-note transcription
└── private messaging stats
Message bodies and downloaded media are not stored in the activity ledger. Statistics retain identifiers, timestamps, direction, chat type, message type, and voice-note duration. The retention window is configurable.
- Node.js 22 or newer
- A machine that can run continuously
- A WhatsApp account capable of linking a WhatsApp Web device
- An OpenAI API key for AI-backed assists
whatsapp-web.js drives WhatsApp Web through Chromium. It is not the official
WhatsApp Business API, so WhatsApp Web changes can occasionally require a
dependency update.
npm install
npm run devOpen http://127.0.0.1:3000.
- Sign in with username
adminand passwordadmin. - Change the default password immediately.
- Scan the QR code from WhatsApp → Linked devices.
- Open Settings and save an OpenAI API key.
- Create reusable audiences under Access.
- Assign an access policy to every assist.
Automatic voice transcription is disabled by default. When enabled, it runs
only according to the selected policy and allowlist. In any other conversation,
the owner can reply to a voice note with !stt for a one-off transcription.
Transcript text is not retained locally.
Build and start the compiled service:
npm run build
npm startRun that command through the process supervisor native to the host—launchd,
systemd, Docker, or another restart-aware service manager. Keep the data/
directory on persistent storage and include it in protected backups.
The service handles SIGINT and SIGTERM gracefully, closing the admin server,
WhatsApp client, and SQLite database in order.
Environment variables are intended for bootstrap configuration:
| Variable | Default | Purpose |
|---|---|---|
MESSISTANT_HOST |
127.0.0.1 |
Admin server bind address |
MESSISTANT_PORT |
3000 |
Admin server port |
MESSISTANT_DATA_DIR |
./data |
SQLite, vault key, and WhatsApp session |
MESSISTANT_LOG_LEVEL |
info |
Structured log level |
OPENAI_API_KEY |
— | Optional environment-provided OpenAI credential |
PUPPETEER_NO_SANDBOX |
false |
Chromium fallback for restricted containers |
For a local environment file:
cp .env.example .env
node --env-file=.env dist/main.jsAn API key entered through the admin console is encrypted with AES-256-GCM.
The encryption key is generated locally at data/master.key with restrictive
permissions. If OPENAI_API_KEY exists in the environment, it takes precedence
over the encrypted local value.
The default models are:
- Text:
gpt-5.6-luna - Transcription:
whisper-1
Both model identifiers are editable in the admin console.
| Policy | Meaning |
|---|---|
| Disabled | The assist never runs |
| Self-chat only | Runs only in WhatsApp’s “message yourself” conversation |
| Owner, from any chat | Runs only on messages sent by the linked account |
| Allowlist only | Runs only in selected chats or reusable access groups |
| Owner or allowlist | Owner commands everywhere plus selected chats |
| Everyone | Runs in any observed eligible chat |
For voice transcription, this policy controls the automatic mode. The
owner-only !stt command remains available in any conversation while the
assist is enabled.
Self-chat-only checks the actual conversation, not merely whether a message was sent by the owner. This prevents private commands such as statistics from running accidentally inside somebody else’s conversation.
!!!
!!! -2
!!! che boludo
Replying to a message with !!! explains the quoted message. Otherwise !!!
uses the immediately preceding message. A negative number selects an older
message, while free text tells Messistant which words to focus on.
!conj tener
!conj tener present
!conj tener vos
Automatic voice transcription must first be enabled under Assists. It then runs only according to the selected policy and allowlist.
For a one-off transcription anywhere else, reply to the voice note with:
!stt
Only the linked account can use !stt. The command does not change the
conversation’s automatic transcription access. The default reply prefix is
📝 Transcript by Messistant:.
Send these commands inside your self-chat:
!stats today
!stats week
Statistics start when Messistant starts observing messages. Brief disconnects can create gaps if WhatsApp Web never replays the missed events.
- The admin server binds to localhost by default.
- The initial password is intentionally known and is flagged until changed.
- Passwords are hashed with scrypt.
- Sessions are random, server-side, expiring, HTTP-only, and same-site.
- Mutating forms require a session-bound CSRF token.
- Login attempts are rate-limited.
- QR values stay in memory.
- API keys, cookies, message bodies, transcripts, and QR values are redacted from structured logs.
- WhatsApp browser/session data is never served by the web application.
If binding beyond localhost, change the password first and place Messistant behind HTTPS with an authenticated private network or trusted reverse proxy.
npm run typecheck
npm test
npm run buildThe project is TypeScript ESM and uses Fastify, SQLite, the official OpenAI Node
SDK, and whatsapp-web.js.
