07f0a26419d9cb13ed380feaefb5329d62e1fb18
📡 Warelay — WhatsApp Relay CLI (Twilio)
Small TypeScript CLI to send, monitor, and webhook WhatsApp messages via Twilio. Supports Tailscale Funnel and config-driven auto-replies.
Setup
pnpm install- Copy
.env.exampleto.envand fill inTWILIO_ACCOUNT_SID,TWILIO_AUTH_TOKEN, andTWILIO_WHATSAPP_FROM(use your approved WhatsApp-enabled Twilio number, prefixed withwhatsapp:).- Alternatively, use API keys:
TWILIO_API_KEY+TWILIO_API_SECRETinstead ofTWILIO_AUTH_TOKEN. - Optional:
TWILIO_SENDER_SIDto skip auto-discovery of the WhatsApp sender in Twilio.
- Alternatively, use API keys:
- (Optional) Build:
pnpm build(scripts run directly via tsx, no build required for local use)
Commands
- Send:
pnpm warelay send --to +15551234567 --message "Hello" --wait 20 --poll 2--waitseconds (default 20) waits for a terminal delivery status; exits non-zero on failed/undelivered/canceled.--pollseconds (default 2) sets the polling interval while waiting.
- Monitor (polling):
pnpm warelay monitor(defaults: 5s interval, 5m lookback)- Options:
--interval <seconds>,--lookback <minutes>
- Options:
- Webhook (push, works well with Tailscale):
pnpm warelay webhook --port 42873 --reply "Got it!"- Points Twilio’s “Incoming Message” webhook to
http://<your-host>:42873/webhook/whatsapp - With Tailscale, expose it:
tailscale serve tcp 42873 127.0.0.1:42873and use your tailnet IP. - Customize path if desired:
--path /hooks/wa - If no
--reply, auto-reply can be configured via~/.warelay/warelay.json(JSON5)
- Points Twilio’s “Incoming Message” webhook to
- Webhook/funnel “up”:
pnpm warelay up --port 42873 --path /webhook/whatsapp- Validates Twilio env, confirms
tailscalebinary, enables Tailscale Funnel, starts the webhook, and sets the Twilio incoming webhook to your Funnel URL. - Requires Tailscale Funnel to be enabled for your tailnet/device (admin setting). If it isn’t enabled, the command will exit with instructions; alternatively expose the webhook via your own tunnel and set the Twilio URL manually.
- Validates Twilio env, confirms
- Polling mode (no webhooks/funnel):
pnpm warelay poll --interval 5 --lookback 10 --verbose- Useful fallback if Twilio webhook can’t reach you.
- Still runs config-driven auto-replies (including command-mode/Claude) for new inbound messages.
- Status:
pnpm warelay status --limit 20 --lookback 240- Lists recent sent/received WhatsApp messages (merged and sorted), defaulting to 20 messages from the past 4 hours. Add
--jsonfor machine-readable output.
- Lists recent sent/received WhatsApp messages (merged and sorted), defaulting to 20 messages from the past 4 hours. Add
Config-driven auto-replies
Put a JSON5 config at ~/.warelay/warelay.json. Examples:
{
inbound: {
// Static text reply with templating
reply: { mode: 'text', text: 'Echo: {{Body}}' }
}
}
// Command-based reply (stdout becomes the reply)
{
inbound: {
reply: {
mode: 'command',
command: ['bash', '-lc', 'echo "You said: {{Body}} from {{From}}"']
}
}
}
Options reference (JSON5)
inbound.allowFrom?: string[]— optional allowlist of E.164 numbers (nowhatsapp:prefix). If set, only these senders trigger auto-replies.inbound.reply.mode: "text" | "command"text— sendinbound.reply.textafter templating.command— runinbound.reply.command(argv array) after templating; trimmed stdout becomes the reply.
inbound.reply.text?: string— used whenmodeistext; supports{{Body}},{{From}},{{To}},{{MessageSid}}.inbound.reply.command?: string[]— argv for the command to run; templated per element.inbound.reply.template?: string— optional string prepended as the second argv element (handy for adding a prompt prefix).inbound.reply.bodyPrefix?: string— optional string prepended toBodybefore templating (useful to add system instructions, e.g.,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).
Example with an allowlist and Claude CLI one-shot (uses a sample number):
{
inbound: {
allowFrom: ["+15551230000"],
reply: {
mode: "command",
command: [
"claude",
"--print",
"--output-format",
"text",
"--dangerously-skip-permissions",
"--system-prompt",
"You are an auto-reply bot on WhatsApp. Respond concisely.",
"{{Body}}"
]
}
}
}
During dev you can run without building: pnpm dev -- <subcommand> (e.g. pnpm dev -- send --to +1...). Auto-replies apply in webhook and polling modes.
Notes
- Monitor uses polling; webhook mode is push (recommended).
- Stop monitor/webhook with
Ctrl+C. - When an auto-reply is triggered (text or command mode), warelay immediately posts a WhatsApp typing indicator tied to the inbound
MessageSidso the user sees “typing…” while your handler runs.
Description
Languages
TypeScript
82.5%
Swift
13.5%
Kotlin
1.9%
Shell
0.8%
CSS
0.5%
Other
0.8%