f88b3ceb7aa3f808d5484180dbd1bef6d6ac08c7
📡 Warelay — WhatsApp Relay CLI (Twilio)
Small TypeScript CLI to send, receive, auto-reply, and inspect WhatsApp messages via Twilio. Works in polling mode or webhook mode (with Tailscale Funnel helper).
You can also use a personal WhatsApp Web session (QR login) via --provider web for direct sends alongside the Twilio flow.
Quick Start
- Install:
pnpm install - Configure
.env(see.env.example): setTWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN(orTWILIO_API_KEY/TWILIO_API_SECRET), andTWILIO_WHATSAPP_FROM=whatsapp:+15551234567. Optional:TWILIO_SENDER_SIDif you don’t want auto-discovery. - Send a test:
pnpm warelay send --to +15551234567 --message "Hi from warelay" - Run auto-replies in polling mode (no public URL needed):
pnpm warelay poll --interval 5 --lookback 10 --verbose - Prefer webhooks? Launch everything in one step (webhook + Tailscale Funnel + Twilio callback):
pnpm warelay up --port 42873 --path /webhook/whatsapp --verbose
Modes at a Glance
- Polling (
monitor/poll): Periodically fetch inbound messages to your WhatsApp number. Easiest to start; no ingress needed. Auto-replies still run. - Webhook (
webhook/up): Push delivery from Twilio.webhookruns the server locally;upalso enables Tailscale Funnel and points the Twilio sender/webhook to your public Funnel URL (with fallbacks to phone number and messaging service).
Providers (choose per command)
- Twilio (default) — full feature set: send, wait/poll delivery, status, inbound polling/webhook, auto-replies. Requires
.envTwilio creds and a WhatsApp-enabled number (TWILIO_WHATSAPP_FROM). - Web (
--provider web) — uses your personal WhatsApp Web session via QR. Currently send-only (no inbound/auto-reply/status yet) and returns immediately without delivery polling. Setup:pnpm warelay web:loginthen send with--provider web. Session data lives in~/.warelay/waweb/; if logged out, rerunweb:login. Use at your own risk (personal-account automation can be rate-limited or logged out by WhatsApp).
Common Commands
- Send:
pnpm warelay send --to +15551234567 --message "Hello" --wait 20 --poll 2 - Send via personal WhatsApp Web: first
pnpm warelay web:login(scan QR), thenpnpm warelay send --provider web --to +15551234567 --message "Hi" - Poll (lightweight):
pnpm warelay poll --interval 5 --lookback 10 --verbose - Webhook only:
pnpm warelay webhook --port 42873 --path /webhook/whatsapp --verbose - Webhook + Funnel + Twilio update:
pnpm warelay up --port 42873 --path /webhook/whatsapp --verbose - Status (recent sent/received):
pnpm warelay status --limit 20 --lookback 240(add--jsonfor machine-readable)
Auto-Reply Config (JSON5 at ~/.warelay/warelay.json)
Claude-style example (your current setup)
{
inbound: {
allowFrom: ["***REMOVED***"], // optional allowlist (E.164, no whatsapp: prefix)
reply: {
mode: "command",
bodyPrefix: "You are a helpful assistant running on the user's Mac. User writes messages via WhatsApp and you respond. You want to be concise in your responses, at most 1000 characters.\n\n",
command: [
"claude",
"-p",
"--dangerously-skip-permissions",
"{{Body}}"
]
}
}
}
Simple text echo
{
inbound: {
reply: { mode: "text", text: "Echo: {{Body}}" }
}
}
Notes:
- Templates support
{{Body}},{{From}},{{To}},{{MessageSid}}. - When an auto-reply starts (text or command), warelay sends a WhatsApp typing indicator tied to the inbound
MessageSid.
Troubleshooting Delivery
- Auto-reply send failures now print in red with Twilio code/status and the response body (e.g., policy violation 63112). Watch terminal output when running
poll,webhook, orup. - Check recent messages:
pnpm warelay status --limit 20 --lookback 240. - If you must resend while a reply is long-running, keep messages <1600 chars (WhatsApp limit) and avoid restricted content/templates.
Options Reference
| Field | Type / Values | Default | Description |
|---|---|---|---|
inbound.allowFrom |
string[] |
empty | Allowlist of E.164 numbers (no whatsapp:). If set, only these trigger auto-replies. |
inbound.reply.mode |
"text" | "command" |
— | Auto-reply type. |
inbound.reply.text |
string |
— | Reply body for text mode; templated. |
inbound.reply.command |
string[] |
— | Argv to run for command mode; templated per element. Stdout (trimmed) is sent. |
inbound.reply.template |
string |
— | Optional string inserted as second argv element (prompt prefix). |
inbound.reply.bodyPrefix |
string |
— | Prepends to Body before templating (ideal for system instructions). |
inbound.reply.timeoutSeconds |
number |
600 | Command timeout. |
Dev Notes
- During dev you can run without building:
pnpm dev -- <subcommand>(e.g.,pnpm dev -- send --to +1...). - Stop polling/webhook with
Ctrl+C. CLI usespnpmandtsx; no build required for local runs.
Description
Languages
TypeScript
82.5%
Swift
13.5%
Kotlin
1.9%
Shell
0.8%
CSS
0.5%
Other
0.8%