TextMe
Overview
njerschow/textme is a local daemon that bridges inbound iMessages (via Sendblue) to a Claude Code session on the user's machine. Whitelisted phone numbers can text, send voice notes, send images, and drive Claude through filesystem operations, code execution, and cd-based directory navigation — turning the user's phone into a remote control for Claude on their laptop. This is the inbound counterpart to outbound notification patterns ([[sendblue-notify]]): textme is phone → Claude; sendblue-notify is Claude → phone.
When to Use This Skill
- Use when the user says "text Claude", "text my laptop", "drive Claude from my phone", "I want to send iMessages to Claude", or "let me code from my phone".
- Use when the user is heads-down away from their desk and wants to kick off, supervise, or interrupt a Claude session via SMS/iMessage.
- Use when setting up a long-running headless workstation that the user wants to remote-control while travelling or away from the keyboard.
- Pair with [[sendblue-notify]] for bidirectional flow — outbound completion pings + inbound commands on the same Sendblue account.
- Do not use for outbound-only "text me when X finishes" patterns. That is [[sendblue-notify]] and does not need a daemon.
Prerequisites
- macOS or Linux host that stays online (the daemon polls Sendblue continuously).
- Node.js 18+.
- An active Sendblue account with API credentials and a provisioned iMessage number — set up via [[sendblue-cli]] (
sendblue setup, thensendblue show-keysto surface API key/secret). - Claude Code installed and authenticated on the host (
npm install -g @anthropic-ai/claude-code). - Optional: an OpenAI API key for Whisper voice-note transcription.
How It Works
Step 1: Install the daemon
git clone https://github.com/njerschow/textme.git
cd textme/daemon
npm install
npm run build
mkdir -p ~/.config/claude-imessage
Step 2: Configure credentials and the whitelist
Create ~/.config/claude-imessage/config.json:
{
"sendblue": {
"apiKey": "YOUR_SENDBLUE_API_KEY",
"apiSecret": "YOUR_SENDBLUE_API_SECRET",
"phoneNumber": "+1SENDBLUE_NUMBER"
},
"whitelist": ["+1YOUR_PHONE"],
"pollIntervalMs": 5000,
"conversationWindowSize": 20
}
The whitelist is the only authorization gate between an inbound iMessage and code execution on the host. Treat it as a security boundary, not a UX preference. Add only phone numbers the user controls; never add a shared, work, or family number "just in case".
For voice transcription, optionally add to .env in the daemon directory:
OPENAI_API_KEY=sk-...
Step 3: Run the daemon
For a quick test run:
cd textme/daemon
npm start
For persistent operation (recommended once the user has verified behavior):
pm2 start dist/index.js --name textme
pm2 save
pm2 startup
Or, on macOS, install the launchd service:
./scripts/install-launchd.sh
Step 4: Drive Claude from iMessage
Once the daemon is running, an iMessage from a whitelisted number to the Sendblue phone number reaches Claude. Built-in commands:
| Command | Effect |
|---|---|
? | List available commands |
status | Show current daemon status and working directory |
queue | Show messages queued for processing |
history | Recent message history |
home | cd back to home directory |
reset | Return home and clear conversation history |
cd /path | Change working directory |
stop | Cancel the current Claude task |
yes / no | Approve or reject the pending action |
Anything else is treated as a Claude prompt and routed to the active session.
Step 5: Verify before relying on it
Before using textme in unattended workflows, the user must:
- Send
statusfrom the whitelisted phone — should get a directory + state reply. - Send a benign command (
pwd,ls) and confirm output arrives. - Send something from a non-whitelisted number and confirm it is ignored, not echoed.
- Pull the plug: kill the daemon and confirm messages stop being processed (no zombie process).
If any of these fail, do not enable launchd / pm2 auto-start.
Examples
Example 1: Initial setup walk-through
# 1. Make sure sendblue CLI is set up and creds work
sendblue whoami
# 2. Grab Sendblue API key & secret (these are NOT the CLI's bearer token)
sendblue show-keys
# 3. Clone + build the daemon
git clone https://github.com/njerschow/textme.git
cd textme/daemon && npm install && npm run build
# 4. Fill in ~/.config/claude-imessage/config.json with the values from step 2
# and YOUR personal phone number as the only whitelist entry
# 5. Start, send "?" from your phone, confirm response
npm start
Example 2: Composing with sendblue-notify
Wire outbound completion pings via [[sendblue-notify]] and inbound control via textme — they share the same Sendblue account but solve opposite problems:
- Claude finishes a long task → texts user via [[sendblue-notify]] (
Stophook). - User replies "look at the diff" → textme routes that into Claude → Claude responds back via Sendblue.
Example 3: Tail the daemon log
# pm2
pm2 logs textme
# Standalone
tail -f ~/.local/log/claude-imessage.log
Example 4: MCP-only alternative (no daemon)
If the user wants Sendblue messaging available to Claude Code as tools but does not want a polling daemon listening for inbound commands, they can register Sendblue as an MCP server instead:
claude mcp add sendblue_api \
--env SENDBLUE_API_API_KEY=your-api-key \
--env SENDBLUE_API_API_SECRET=your-api-secret \
-- npx -y sendblue-api-mcp --client=claude-code --tools=all
This gives Claude outbound Sendblue tools inside a session but does not open the inbound phone-controls-Claude channel that textme provides. Pick textme when "text Claude from anywhere" is the goal; pick MCP when Claude only needs to send.
Best Practices
- ✅ Whitelist exactly one phone number to start. The whitelist is the security boundary; expand it slowly and only to numbers the user controls.
- ✅ Run the daemon as a regular user, never as root or via
sudo. - ✅ Start in a sandbox directory for first tests (
cd ~/textme-sandbox), not in~or a real repo. - ✅ Verify the non-whitelist ignore path before enabling auto-start. A daemon that processes any sender is an open shell on the host.
- ✅ Keep
pollIntervalMs≥ 5000 unless the user understands the Sendblue rate limits and cost implications. - ❌ Don't share the Sendblue number publicly. Even with a whitelist, the host is doing per-message work; a flood from an unknown sender still costs polling cycles.
- ❌ Don't store
config.jsonin a repo, dotfiles backup, or cloud sync — it contains API credentials and the user's phone number. - ❌ Don't run the daemon on a shared machine without considering what every other user of that machine can now reach by sending an SMS.
Limitations
- Outbound-only flows do not need this skill. For "text me when X finishes" use [[sendblue-notify]]; running a daemon is overkill.
- Voice transcription requires a separate OpenAI API key. Without it, voice notes are dropped or surfaced as un-transcribed audio depending on daemon version.
- The daemon polls on an interval — there is no push delivery. Expect single-digit-second latency between message receipt and Claude response.
- One conversation, one machine. This is a per-host daemon, not a multi-tenant service. Two daemons sharing one Sendblue number will both try to handle every inbound message.
- Sendblue free-plan verification still applies. The user's phone must have texted the Sendblue number once before outbound responses from Claude reach the user (see [[sendblue-cli]] limitations).
Security & Safety Notes
textme is a **remote code execution