Automating messages
Agents, Skills and Commands for automating Keynote, Notes, Reminders, Numbers and more using AppleScript and PyXA (JXA too).
npx -y skills add SpillwaveSolutions/automating-mac-apps-plugin --skill automating-messagesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
5.3 KB, as published. Nobody here has run it
Automating Messages (JXA-first with UI/DB fallbacks)
Contents
- Permissions and scope
- Default workflow
- Quick recipe
- Attachments and UI fallback
- Data access and forensics
- Validation Checklist
- When Not to Use
- What to load
Permissions and scope
- Grants needed: Automation + Accessibility; Full Disk Access for any
chat.dbreads. - Keep automation scoped and auditable; avoid unsolicited sends and DB writes.
- Pairs with
automating-mac-appsfor common setup (permissions, osascript invocation, UI scripting basics).
Default workflow (happy path)
- Resolve transport: pick
serviceType(iMessageorSMS) before targeting a buddy. - Identify recipient: filter buddies by
handle(phone/email). Avoid ambiguous names. - Send via app-level
send: pass Buddy object toMessages.send(). - Verify window context: activate Messages when mixing with UI steps.
- Fallbacks: if send/attachments fail, use UI scripting; for history, use SQL.
Quick recipe (defensive send)
const Messages = Application('Messages');
Messages.includeStandardAdditions = true;
function safeSend(text, handle, svcType = 'iMessage') {
const svc = Messages.services.whose({ serviceType: svcType })[0];
if (!svc) throw new Error(`Service ${svcType} missing`);
const buddy = svc.buddies.whose({ handle })[0];
if (!buddy) throw new Error(`Buddy ${handle} missing on ${svcType}`);
Messages.send(text, { to: buddy });
}
- Wrap with
try/catchand log; add small delays when activating UI. - For groups, target an existing chat by GUID or fall back to UI scripting; array sends are unreliable.
Attachments and UI fallback
- Messages lacks a stable JXA attachment API; use clipboard + System Events paste/send.
- Ensure Accessibility permission, bring app forward, paste file, press Enter.
- See
references/ui-scripting-attachments.mdfor 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.dbfor history; JXAchat.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 -jsonfor structured results. - See
references/database-forensics.mdfor 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
launchdnow that on-receive handlers are gone. - Track
rowid, query diffs, dispatch actions, and persist state. - See
references/monitoring-daemons.mdfor the polling pattern and plist notes.
Validation Checklist
- Automation + Accessibility permissions granted
- Service resolves:
Messages.services.whose({ serviceType: 'iMessage' })[0]returns object - Buddy lookup works:
svc.buddies.whose({ handle })[0]returns target - Test send completes without errors
- Full Disk Access granted if using
chat.dbreads
When Not to Use
- For reading message history without Full Disk Access (AppleScript/JXA cannot read messages)
- For cross-platform messaging (use platform APIs or third-party services)
- For business SMS automation (use Twilio or similar APIs)
- When iMessage/SMS features are not available on the target system
- For bulk messaging (rate limits and security restrictions apply)
- When security policy prohibits Full Disk Access grants (required for any read operations)
What to load
- Control plane and send reliability:
references/control-plane.md - UI scripting + attachments fallback:
references/ui-scripting-attachments.md - SQL/history access:
references/database-forensics.md - Polling bots/launchd:
references/monitoring-daemons.md