@mulmobridge/twilio-sms
v0.1.1
Published
Twilio SMS bridge for MulmoBridge — inbound SMS via webhook, outbound via Twilio REST API
Readme
@mulmobridge/twilio-sms
Experimental — please test and report issues.
SMS bridge for MulmoClaude via Twilio Programmable Messaging. Every phone on Earth can text your AI agent — no app install needed.
Public URL required (Twilio posts a webhook every time an SMS arrives on your number).
Setup
1. Get a Twilio number
- Sign up at twilio.com (trial includes credits).
- Phone Numbers → Buy a Number → pick one with SMS capability.
- Note the Account SID + Auth Token (Twilio console top-right / Settings → General).
2. Expose the bridge with a tunnel
ngrok http 3010
# copy the https URL — e.g. https://abcd.ngrok-free.app3. Configure the Twilio number
In the Twilio console, open the number → Messaging → A Message Comes In → Webhook, method HTTP POST → URL https://<your-tunnel>/sms.
4. Run the bridge
TWILIO_ACCOUNT_SID=AC... \
TWILIO_AUTH_TOKEN=... \
TWILIO_FROM_NUMBER=+15551234567 \
TWILIO_PUBLIC_URL=https://abcd.ngrok-free.app \
npx @mulmobridge/twilio-smsText the Twilio number — you'll get a reply.
Environment variables
| Variable | Required | Default | Description |
|--------------------------|-------------|---------|-------------|
| TWILIO_ACCOUNT_SID | yes | — | Twilio Account SID |
| TWILIO_AUTH_TOKEN | yes | — | Twilio Auth Token (used for REST + signature verification) |
| TWILIO_FROM_NUMBER | yes | — | Your Twilio number in E.164, e.g. +15551234567 |
| TWILIO_WEBHOOK_PORT | no | 3010 | HTTP port |
| TWILIO_PUBLIC_URL | yes (prod) | — | Full public URL the bridge is reachable at (e.g. https://abcd.ngrok-free.app, including any query string Twilio signs). Required to verify Twilio's X-Twilio-Signature. The bridge refuses to start without it unless TWILIO_ALLOW_UNVERIFIED=1 is also set. |
| TWILIO_ALLOW_UNVERIFIED| no | — | Set to 1 to skip signature verification (local testing only). Prints a loud warning and leaves /sms wide open. Do not set in production. |
| TWILIO_ALLOWED_NUMBERS | no | (all) | CSV of sender E.164 numbers allowed (empty = accept every number) |
| MULMOCLAUDE_AUTH_TOKEN | no | auto | MulmoClaude bearer token override |
| MULMOCLAUDE_API_URL | no | http://localhost:3001 | MulmoClaude server URL |
How it works
- Twilio posts form-encoded
From,To,Body,MessageSidto/smsevery time an SMS arrives. - The bridge verifies
X-Twilio-Signature(HMAC-SHA1 over the full URL — including query string — + sorted form params) using the auth token.TWILIO_PUBLIC_URLmust match the URL Twilio sees (scheme + host + optional path prefix); the request's actual query string is read fromreq.originalUrl. - We ACK
204immediately so Twilio doesn't retry, then (asynchronously) forward the trimmed body to MulmoClaude keyed by the sender's number. - The reply is sent back via
POST /2010-04-01/Accounts/{SID}/Messages.jsonwith Basic auth; long replies are chunked at 1 600 chars (Twilio's concatenated-SMS ceiling).
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 at Twilio side | Signature verification failed | Verify TWILIO_PUBLIC_URL matches the URL Twilio actually hits (scheme + host + path, no trailing /) |
| No reply delivered | Messages.json REST call failing | docker logs / npx output will show [twilio-sms] send failed: …. Common cause: trial account can only message verified numbers |
| Duplicate replies | Twilio retried before ACK | Ensure reachable https:// endpoint (not HTTP) and the bridge responds 2xx under 15 s |
Security notes
- The auth token is equivalent to root credentials on your Twilio account. Rotate in the console if leaked.
TWILIO_PUBLIC_URLis strongly recommended — without it, anyone who finds your webhook can impersonate Twilio and converse with your agent.- Trial Twilio accounts can only SMS pre-verified numbers. Upgrade to production before real use.
- SMS is plaintext — don't discuss secrets over it. Use Signal / WhatsApp / Matrix instead for sensitive content.
Ecosystem
Part of the @mulmobridge/* package family.
Shared libraries:
@mulmobridge/client— socket.io client library used by every bridge below@mulmobridge/protocol— wire types and constants@mulmobridge/chat-service— server-side relay + session store@mulmobridge/relay— Cloudflare Workers webhook proxy@mulmobridge/mock-server— mock server for local bridge development
Bridges (one npm package per platform):
@mulmobridge/bluesky— Bluesky DMs over atproto@mulmobridge/chatwork— Chatwork (Japanese business chat)@mulmobridge/cli— interactive terminal bridge@mulmobridge/discord— Discord bot via Gateway@mulmobridge/email— IMAP poll + SMTP reply, threading preserved@mulmobridge/google-chat— Google Chat via MulmoBridge relay@mulmobridge/irc— IRC (Libera, Freenode, custom)@mulmobridge/line— LINE Messaging API via MulmoBridge relay@mulmobridge/line-works— LINE Works (enterprise LINE)@mulmobridge/mastodon— Mastodon DMs + mentions@mulmobridge/matrix— Matrix / Element@mulmobridge/mattermost— Mattermost@mulmobridge/messenger— Facebook Messenger via MulmoBridge relay@mulmobridge/nostr— Nostr NIP-04 encrypted DMs@mulmobridge/rocketchat— Rocket.Chat@mulmobridge/signal— Signal via signal-cli-rest-api@mulmobridge/slack— Slack Socket Mode@mulmobridge/teams— Microsoft Teams via Bot Framework@mulmobridge/telegram— Telegram bot@mulmobridge/twilio-sms— SMS via Twilio Programmable Messaging ← this package@mulmobridge/viber— Viber Public Account bots@mulmobridge/webhook— generic HTTP webhook bridge@mulmobridge/whatsapp— WhatsApp Cloud API via MulmoBridge relay@mulmobridge/xmpp— XMPP / Jabber@mulmobridge/zulip— Zulip
