iMessage is personal — family texts, dinner plans, group chats full of years of inside jokes. Unlike Slack (built for bots) or Telegram (welcomes automation), iMessage was never designed for third-party integrations. Apple doesn’t provide an official API and actively discourages automation.
But it’s technically possible. This guide shows you how to connect OpenClaw to iMessage using a Mac as a bridge — reading messages from iMessage’s SQLite database and sending replies via AppleScript.
Why iMessage Integration Is Different (And Controversial)
Official API: none. Unofficial methods:
- ●SQLite database access — iMessage stores messages in ~/Library/Messages/chat.db
- ●AppleScript — macOS scripting can control the Messages app
- ●Accessibility APIs — simulate clicks/typing (too fragile, not covered)
The Legal Grey Zone
- ●Reading your own messages on your own computer — legal
- ●Automating Messages on your own Mac — probably legal
- ●Reverse-engineering iMessage server protocol — violates ToS
This guide uses database access + AppleScript, which is legally defensible but technically fragile.
Requirements
- ●Mac (any model 2015+), always on, signed into iMessage
- ●macOS 10.14 (Mojave) or newer
- ●Xcode Command Line Tools (for SQLite)
- ●Node.js and OpenClaw installed
Architecture Overview
- 1.Watch the chat.db database for new messages
- 2.Parse incoming messages from the SQLite tables
- 3.Send the message text to OpenClaw for processing
- 4.Generate a reply with your AI agent
- 5.Send the reply through Messages via AppleScript
Limitations
- ●Text only — no photos, videos, or stickers
- ●Can only reply to existing conversations
- ●No read receipts or typing indicators
- ●Breaks if Apple changes the database schema
- ●Mac must be unlocked with Messages running
Step 1: Enable Full Disk Access for Terminal
macOS protects the iMessage database behind Full Disk Access:
- 1.System Preferences → Privacy & Security → Full Disk Access
- 2.Click the lock, enter password
- 3.Click + and add /Applications/Utilities/Terminal.app (or iTerm2)
Verify access:
sqlite3 ~/Library/Messages/chat.db "SELECT COUNT(*) FROM message"
Step 2: Understand iMessage Database Structure
Everything lives in SQLite at ~/Library/Messages/chat.db. Key tables:
- ●message — ROWID, text, handle_id, is_from_me, date (ns since 2001-01-01)
- ●chat — ROWID, chat_identifier (phone/email), display_name
- ●handle — ROWID, id (phone or email)
- ●chat_message_join — links chats to messages
Query Recent Messages
SELECT message.ROWID, message.text, message.is_from_me,
message.date, handle.id AS sender
FROM message
LEFT JOIN handle ON message.handle_id = handle.ROWID
WHERE message.text IS NOT NULL
ORDER BY message.date DESC
LIMIT 10;Messages In a Specific Conversation
SELECT message.text, message.is_from_me,
datetime(message.date/1000000000 + strftime('%s', '2001-01-01'),
'unixepoch', 'localtime') AS date_formatted
FROM message
JOIN chat_message_join ON message.ROWID = chat_message_join.message_id
JOIN chat ON chat_message_join.chat_id = chat.ROWID
WHERE chat.chat_identifier = '+15551234567'
ORDER BY message.date DESC
LIMIT 20;Step 3: Create the iMessage Bridge
Create adapters/imessage-bridge.js:
const { exec } = require('child_process');
const { promisify } = require('util');
const Database = require('better-sqlite3');
const fs = require('fs');
const os = require('os');
const execAsync = promisify(exec);
class iMessageBridge {
constructor(config) {
this.config = config;
this.dbPath = `${os.homedir()}/Library/Messages/chat.db`;
this.db = null;
this.lastMessageId = 0;
this.pollInterval = config.pollInterval || 2000;
}
async initialize() {
if (!fs.existsSync(this.dbPath)) {
throw new Error('iMessage database not found. Is Messages enabled?');
}
this.db = new Database(this.dbPath, { readonly: true });
const row = this.db.prepare('SELECT MAX(ROWID) AS maxId FROM message').get();
this.lastMessageId = row.maxId || 0;
console.log('[iMessage] Bridge initialized, last ID:', this.lastMessageId);
setInterval(() => this.checkNewMessages(), this.pollInterval);
}
async checkNewMessages() {
const rows = this.db.prepare(`
SELECT message.ROWID, message.text, handle.id AS sender,
chat.chat_identifier
FROM message
LEFT JOIN handle ON message.handle_id = handle.ROWID
LEFT JOIN chat_message_join ON message.ROWID = chat_message_join.message_id
LEFT JOIN chat ON chat_message_join.chat_id = chat.ROWID
WHERE message.ROWID > ?
AND message.text IS NOT NULL
AND message.is_from_me = 0
ORDER BY message.ROWID ASC
`).all(this.lastMessageId);
for (const msg of rows) {
if (this.config.ignoreGroupChats && msg.chat_identifier?.startsWith('chat')) continue;
const response = await this.processWithOpenClaw(msg.sender, msg.text);
await this.sendMessage(msg.sender, response);
this.lastMessageId = Math.max(this.lastMessageId, msg.ROWID);
}
}
async sendMessage(recipient, text) {
const safeText = text.replace(/"/g, '\"');
const safeTo = recipient.replace(/"/g, '\"');
const script = `
tell application "Messages"
set targetService to 1st account whose service type = iMessage
set targetBuddy to participant "${safeTo}" of targetService
send "${safeText}" to targetBuddy
end tell`;
await execAsync(`osascript -e '${script}'`);
}
}
module.exports = iMessageBridge;Install the SQLite dep:
npm install better-sqlite3
Step 4: Configure OpenClaw
Update .env:
IMESSAGE_ENABLED=true IMESSAGE_POLL_INTERVAL=2000 IMESSAGE_IGNORE_GROUP_CHATS=true
And config/channels.yml:
channels:
imessage:
enabled: true
adapter: adapters/imessage-bridge.js
pollInterval: 2000
ignoreGroupChats: true
maxMessagesPerMinute: 10
allowedContacts: [] # empty = all contactsStep 5: Test the Bridge
Start OpenClaw with npm start. From another device, send yourself an iMessage like “Test message for OpenClaw”. You should see logs like:
[iMessage] Bridge initialized, last ID: 123456 [iMessage] New message from +15551234567: Test message for OpenClaw [iMessage] Sent message to +15551234567
Troubleshooting
- ●No messages detected — verify Full Disk Access, Messages running, db path
- ●Can’t send — open Messages and send one manually first, check Automation permissions
- ●Wrong recipient — verify phone format (+1…) and active Apple ID
AppleScript Deep Dive
Basic send:
tell application "Messages" set targetService to 1st account whose service type = iMessage set targetBuddy to participant "+15551234567" of targetService send "Hello from OpenClaw" to targetBuddy end tell
Email-based contacts work too — swap the participant string for an email. AppleScript cannot send attachments, read messages, return delivery status, or reliably create new conversations. GUI scripting (simulated keystrokes) exists as a fallback but breaks easily and isn’t recommended.
Safety and Rate Limiting
iMessage has no official rate limits (it assumes human use). Abuse it and Apple may flag your Apple ID. Add an in-process limiter:
const rateLimiter = {
sent: [],
maxPerMinute: 10,
canSend() {
const now = Date.now();
this.sent = this.sent.filter(t => now - t < 60000);
return this.sent.length < this.maxPerMinute;
},
recordSend() { this.sent.push(Date.now()); },
};- ●Max 10 messages/minute (safe)
- ●Max 100 messages/day (conservative)
- ●Don’t auto-reply to groups
- ●Whitelist contacts for business use
Privacy and Security Considerations
The iMessage database contains all your messages (including soft-deleted ones), contact info, attachment metadata, and timestamps. Mitigate the risk:
- ●Open the DB read-only — never write to it
- ●Only grant Full Disk Access to your terminal, not random apps
- ●Encrypt OpenClaw’s memory store if it persists conversations
- ●Enable FileVault and require a password after sleep
Legal and ToS Considerations
Personal use — automating your own Messages on your own Mac is legally defensible. Business use — automated outreach via iMessage violates Apple’s ToS, which prohibits commercial use and automated messaging.
Safe Uses
- ●Personal AI assistant for your own messages
- ●Vacation auto-responder
- ●Message organization or archiving
Avoid
- ●Marketing messages to customers
- ●Cold outreach
- ●Operating a bot service for others
The Maintenance Reality
This integration is inherently fragile. Things that break it:
- ●macOS updates change the DB schema or AppleScript behavior
- ●Messages app updates alter behavior
- ●Security updates tighten Full Disk Access
- ●Mac sleeps — polling pauses and messages are missed
- ●Messages app crashes — AppleScript fails
The PaioClaw Alternative
DIY setup takes ~60–90 minutes plus ongoing maintenance every time Apple ships an update. PaioClaw can’t bypass Apple’s restrictions, but it does provide better error handling (auto-recovery when the Mac wakes), database-schema adaptation, safer rate limiting to avoid Apple ID flags, and multi-device coordination across Macs.
If iMessage isn’t critical, prefer Telegram, Slack, or WhatsApp Cloud API — they have official APIs and far better stability. Starts FREE, Smart $15/month, Genius $25/month.
The Bottom Line
iMessage integration works, but it’s a hack — you’re reverse-engineering Apple’s closed ecosystem. It needs an always-on Mac, Full Disk Access, and acceptance that Apple may break it at any time. Use it for personal automation only; don’t bet business-critical workflows on it. iMessage is the only major messaging platform without an official API, and this is the best we can do with what Apple gives us.

