// Automates macOS Messages (iMessage/SMS) via JXA with reliable service→buddy resolution. Use when asked to "automate iMessage", "send Messages via script", "JXA Messages automation", or "read Messages history". Covers send-bug workarounds, UI scripting for attachments, chat.db forensics, and launchd polling bots.
Automates macOS Messages (iMessage/SMS) via JXA with reliable service→buddy resolution. Use when asked to "automate iMessage", "send Messages via script", "JXA Messages automation", or "read Messages history". Covers send-bug workarounds, UI scripting for attachments, chat.db forensics, and launchd polling bots.
allowed-tools
["Bash","Read","Write"]
Automating Messages (JXA-first with UI/DB fallbacks)
See references/ui-scripting-attachments.md for the full flow and ObjC pasteboard snippet.
Data access and forensics
Reading messages limitation: The AppleScript/JXA API for Messages is effectively write-only. While send() works reliably, reading messages via chat.messages() or similar methods is broken/unsupported in modern macOS. The only reliable way to read message history is via direct SQLite access to ~/Library/Messages/chat.db.
Security consideration: Reading chat.db requires Full Disk Access permission, which grants broad filesystem access beyond just Messages. This is a significant security trade-off - granting Full Disk Access to scripts or applications exposes all user data. Consider whether reading message history is truly necessary before enabling this permission.
Use SQL against chat.db for history; JXA chat.messages() is unreliable/non-functional.
Requires: System Settings > Privacy & Security > Full Disk Access for your terminal/script.
Remember Cocoa epoch conversion (nanoseconds since 2001-01-01); use sqlite3 -json for structured results.
See references/database-forensics.md for schema notes, typedstream handling, and export tooling.
Example read query (requires Full Disk Access):
sqlite3 ~/Library/Messages/chat.db "SELECT
CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE 'Them' END as sender,
m.text,
datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') as date
FROM message m
JOIN handle h ON m.handle_id = h.rowid
WHERE h.id LIKE '%PHONE_NUMBER%'
ORDER BY m.date DESC LIMIT 10;"
Bots and monitoring
Implement polling daemons with launchd now that on-receive handlers are gone.
Track rowid, query diffs, dispatch actions, and persist state.
See references/monitoring-daemons.md for the polling pattern and plist notes.
Validation Checklist
Automation + Accessibility permissions granted
Service resolves: Messages.services.whose({ serviceType: 'iMessage' })[0] returns object