# OpenClaw → iMessage Pipeline — Setup Guide

> **Version:** 2.0
> **Last updated:** 2026-03-04
> **Source:** [cristiandominguez.com/imessageflow/v2](https://cristiandominguez.com/imessageflow/v2/)
> **License:** Use freely. Credit appreciated.

---

## What This Does

Sets up a pipeline where you command your AI assistant via **Discord** (or Telegram), and it sends messages — text or AI-generated voice — to your contacts via **iMessage**. Everything runs locally on a Mac. No cloud TTS, no third-party message routing.

```
You (phone)          Your Mac (always-on)              Recipient
┌──────────┐    ┌──────────────────────────┐    ┌──────────┐
│ Discord / │───→│ OpenClaw Gateway         │    │          │
│ Telegram  │    │  ├─ AI Agent (Claude)    │    │ iMessage │
│           │←───│  ├─ Qwen3-TTS (local)   │───→│ (iPhone) │
│           │    │  ├─ imsg CLI             │    │          │
│           │    │  └─ ffmpeg              │    │          │
└──────────┘    └──────────────────────────┘    └──────────┘
```

---

## Prerequisites

- **Mac** (Apple Silicon recommended) — Mac mini, MacBook, Mac Studio, etc.
- **macOS 15+** with Messages.app signed into an Apple ID
- **Apple ID** — ideally a dedicated one (not your personal account)
- **OpenClaw** installed and running ([openclaw.ai](https://openclaw.ai))
- **Python 3.11+** with pip
- **Homebrew** (`brew.sh`)
- **Discord server** or **Telegram bot** configured as an OpenClaw channel

---

## Step 1: Install imsg CLI

`imsg` is a command-line tool that automates Messages.app on macOS.

```bash
brew install imsg
```

Verify:
```bash
imsg chats --limit 5 --json
```

> **Important:** The process running OpenClaw needs **Full Disk Access** (FDA) in System Settings → Privacy & Security → Full Disk Access. Add your terminal app or OpenClaw.app.

---

## Step 2: Configure iMessage Channel in OpenClaw

Add iMessage as a channel in your `openclaw.json` (or via `openclaw config`):

```json5
{
  "channels": {
    "imessage": {
      "enabled": true,
      "dmPolicy": "allowlist",
      "allowFrom": ["<your-phone-or-apple-id>"],
      "groupPolicy": "disabled"
    }
  }
}
```

**Key settings:**
- `dmPolicy: "allowlist"` — only messages from allowlisted senders are processed. Everyone else is silently dropped (no error, no response, no "OpenClaw" branding exposed).
- `groupPolicy: "disabled"` — all group iMessages silently dropped.
- Add your own phone/Apple ID to `allowFrom` so you can message the assistant directly on iMessage too.

---

## Step 3: Install Qwen3-TTS (Local Voice Generation)

Qwen3-TTS is an open-source TTS model from Alibaba. It supports voice cloning from a 3-second sample, natural language voice control, and 10 languages.

### Option A: Python package (recommended)
```bash
# Create a virtual environment
python3 -m venv ~/tts-env
source ~/tts-env/bin/activate

# Install Qwen3-TTS
pip install qwen3-tts

# Download model (0.6B recommended for speed, 1.7B for quality)
python3 -c "from qwen3_tts import Qwen3TTS; Qwen3TTS.from_pretrained('Qwen/Qwen3-TTS-0.6B')"
```

### Option B: Via Ollama (if you prefer)
```bash
ollama pull qwen3-tts:0.6b
```

### Verify TTS works
```bash
source ~/tts-env/bin/activate
python3 -c "
from qwen3_tts import Qwen3TTS
import soundfile as sf

model = Qwen3TTS.from_pretrained('Qwen/Qwen3-TTS-0.6B')

# Voice design (describe the voice you want)
samples, sr = model.generate(
    text='Hello, this is a test of the voice generation system.',
    voice_description='A warm, friendly male voice with a slight British accent. Speaks at a moderate pace with natural intonation.',
    language='en'
)
sf.write('test_output.wav', samples, sr)
print('Generated test_output.wav')
"
```

### Install ffmpeg (for format conversion)
```bash
brew install ffmpeg
```

---

## Step 4: Create Contact Profiles

Create a directory for contact profiles:
```bash
mkdir -p ~/.openclaw/workspace/memory/contacts
```

Create a file per contact (`~/.openclaw/workspace/memory/contacts/<name>.md`):

```markdown
# María García

## Contact
- **Phone:** +34 612 345 678
- **Channels:** iMessage

## Communication
- **Language:** Castilian Spanish (distinción, vosotros)
- **Tone:** Warm, close friend, humor welcome
- **Topics to avoid:** None

## Voice
- **Method:** voice_design
- **Description:** "A warm female voice speaking Castilian Spanish. Natural, conversational pace. Friendly but not overly enthusiastic."
- **Language:** es
- **Speed:** 1.0

## Context
- Close friend since university
- Lives in Madrid
- Prefers voice messages over text
```

### Voice options in profiles:

**Voice design** (describe what you want):
```yaml
- Method: voice_design
- Description: "A deep, calm male voice. Professional but warm."
```

**Voice clone** (from a sample):
```yaml
- Method: voice_clone
- Reference: ~/.openclaw/workspace/voices/maria-sample.wav
- Note: 3-10 seconds of clear speech, minimal background noise
```

---

## Step 5: Create Permissions File

Create `~/.openclaw/workspace/PERMISSIONS.md`:

```markdown
# Outbound Messaging Permissions

## Default Policy
ALL outbound messages require explicit owner approval (Tier 3).

## Approved Contacts (iMessage)
- María García (+34 612 345 678) — text, voice
- Dad (+1 310 555 1234) — text, voice

## Blocked
- (none)

## Tier Definitions
- **Tier 1 (Autonomous):** Read messages, triage, 2FA for own accounts only
- **Tier 2 (Pre-approved):** Specific contacts + message types (configure below)
- **Tier 3 (Approval required):** Draft → preview → approve → send (DEFAULT)

## Pre-approved Patterns (Tier 2)
(none configured — all messages require approval)
```

---

## Step 6: Add Instructions to Your Agent

Add these rules to your agent's `SOUL.md` or `AGENTS.md`:

```markdown
## Outbound Messaging Rules

### Before ANY outbound message:
1. Read `PERMISSIONS.md` — verify the contact is approved
2. Read `memory/contacts/<name>.md` — load their profile
3. Compose the message respecting their language, tone, and preferences
4. Send a draft preview to the owner for approval
5. Wait for explicit "yes" / "send it" before sending
6. NEVER send without approval unless Tier 1 or Tier 2 pre-approved

### Sending text via iMessage:
```
imsg send --to "<phone>" --text "<message>" --json
```

### Sending voice via iMessage:
```
# 1. Generate speech
source ~/tts-env/bin/activate
python3 generate_voice.py --text "<text>" --voice-desc "<description>" --lang es --out /tmp/msg.wav

# 2. Convert to M4A
ffmpeg -y -i /tmp/msg.wav -c:a aac -b:a 128k /tmp/msg.m4a

# 3. Send
imsg send --to "<phone>" --file /tmp/msg.m4a --json
```

### After sending:
- Log to `memory/outbound-log.md`: `[timestamp] iMessage | TO: name | SUMMARY: ... | APPROVAL: ref`
- Confirm delivery to owner on Discord/Telegram

### Monitoring replies:
- Periodically check `imsg chats --limit 10 --json` for new activity
- Read recent messages: `imsg history --chat-id <N> --limit 5 --json`
- Triage: URGENT → relay immediately, NOTABLE → relay on next check, SKIP → ignore
```

---

## Step 7: Create Voice Generation Script

Save as `~/.openclaw/workspace/scripts/generate_voice.py`:

```python
#!/usr/bin/env python3
"""Generate speech using Qwen3-TTS with contact voice profiles."""
import argparse
import json
import soundfile as sf
from pathlib import Path

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--text', required=True, help='Text to speak')
    parser.add_argument('--voice-desc', help='Voice description (for voice design)')
    parser.add_argument('--voice-ref', help='Path to reference audio (for voice clone)')
    parser.add_argument('--lang', default='en', help='Language code')
    parser.add_argument('--speed', type=float, default=1.0)
    parser.add_argument('--out', default='/tmp/tts_output.wav')
    parser.add_argument('--model', default='Qwen/Qwen3-TTS-0.6B')
    args = parser.parse_args()

    from qwen3_tts import Qwen3TTS
    model = Qwen3TTS.from_pretrained(args.model)

    if args.voice_ref:
        # Voice cloning mode
        samples, sr = model.generate(
            text=args.text,
            reference_audio=args.voice_ref,
            language=args.lang,
            speed=args.speed
        )
    elif args.voice_desc:
        # Voice design mode
        samples, sr = model.generate(
            text=args.text,
            voice_description=args.voice_desc,
            language=args.lang,
            speed=args.speed
        )
    else:
        # Default voice
        samples, sr = model.generate(
            text=args.text,
            language=args.lang,
            speed=args.speed
        )

    sf.write(args.out, samples, sr)
    print(json.dumps({"status": "ok", "file": args.out, "duration": len(samples) / sr}))

if __name__ == '__main__':
    main()
```

---

## Step 8: Set Up Audit Logging

Create `~/.openclaw/workspace/memory/outbound-log.md`:

```markdown
# Outbound Message Log
<!-- Every non-owner outbound message is logged here. -->
<!-- Format: [timestamp] channel | TO: name | SUMMARY: ... | APPROVAL: ref -->
```

---

## Step 9: Test the Pipeline

### Test 1: Text message
Tell your assistant (on Discord or Telegram):
```
Send María a text saying "Hey, are we still on for coffee tomorrow?"
```

Expected flow:
1. Assistant reads María's profile
2. Checks permissions
3. Composes message in Castilian Spanish
4. Sends you a draft for approval
5. You say "yes"
6. Sends via `imsg send --to "+34..." --text "..."`
7. Logs and confirms

### Test 2: Voice message
```
Send Dad a voice message wishing him happy birthday. Make it warm and heartfelt.
```

Expected flow:
1-4: Same as above
5. You approve
6. Generates speech with Dad's voice profile
7. Converts to M4A
8. Sends via `imsg send --to "+1..." --file /tmp/msg.m4a`
9. Logs and confirms

---

## Security Checklist

- [ ] **Dedicated Apple ID** (not your personal one)
- [ ] **Full Disk Access** granted only to OpenClaw.app
- [ ] **FileVault** enabled (full disk encryption)
- [ ] **macOS Firewall** enabled
- [ ] **PERMISSIONS.md** created with explicit contact allowlist
- [ ] **dmPolicy: "allowlist"** on iMessage channel
- [ ] **groupPolicy: "disabled"** on iMessage channel
- [ ] **Audit log** at `memory/outbound-log.md`
- [ ] **Tailscale** for remote access (not port forwarding)

---

## Troubleshooting

**"imsg: permission denied"**
→ Grant Full Disk Access to your terminal or OpenClaw.app in System Settings.

**TTS generates but sounds robotic**
→ Try the 1.7B model: `--model Qwen/Qwen3-TTS-1.7B`. Also try more descriptive voice prompts.

**iMessage not delivering**
→ Verify the recipient's phone is registered with iMessage. Use `imsg send --service sms` for SMS fallback.

**Voice clone sounds off**
→ Use 5-10 seconds of clear, single-speaker audio. Minimize background noise. Try multiple reference clips.

---

## Architecture Reference

Full interactive diagrams: [cristiandominguez.com/imessageflow/v2](https://cristiandominguez.com/imessageflow/v2/)

Questions or improvements? Open an issue or reach out.
