Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions plugins/osaurus.mail.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,43 @@
"capabilities": {
"tools": [
{
"name": "list_mailboxes",
"description": "List all mailboxes in Apple Mail. Returns a flat list of mailbox_path identifiers (e.g. 'iCloud/INBOX', 'Gmail/[Gmail]/All Mail') that are used as the mailbox_path parameter in other tools. Call this first to discover available mailboxes."
"widget": true,
"description": "List all mailboxes in Apple Mail. Returns a flat list of mailbox_path identifiers (e.g. 'iCloud/INBOX', 'Gmail/[Gmail]/All Mail') that are used as the mailbox_path parameter in other tools. Call this first to discover available mailboxes.",
"name": "list_mailboxes"
},
{
"name": "list_messages",
"description": "List message summaries in a mailbox. Returns subject, sender, date, and flags — but NOT the message body. Use read_message to get the body of a specific message. Supports filtering and pagination via offset. Messages are returned newest-first."
"widget": true,
"description": "List message summaries in a mailbox. Returns subject, sender, date, and flags — but NOT the message body. Use read_message to get the body of a specific message. Supports filtering and pagination via offset. Messages are returned newest-first.",
"name": "list_messages"
},
{
"name": "read_message",
"description": "Read the full content of a specific email message. Use this after list_messages or search_messages to get the message body. Returns plain text body by default."
"description": "Read the full content of a specific email message. Use this after list_messages or search_messages to get the message body. Returns plain text body by default.",
"name": "read_message"
},
{
"name": "search_messages",
"description": "Search for messages by keyword across subject and sender. Returns the same message summary format as list_messages (no body — use read_message for that). Searches subject line and sender name/address only; body-text search is not supported. Use this instead of list_messages when you need to find messages by subject or sender rather than browsing a specific mailbox."
"description": "Search for messages by keyword across subject and sender. Returns the same message summary format as list_messages (no body — use read_message for that). Searches subject line and sender name/address only; body-text search is not supported. Use this instead of list_messages when you need to find messages by subject or sender rather than browsing a specific mailbox.",
"name": "search_messages"
},
{
"name": "compose_message",
"description": "Compose a new email. By default opens as a draft for the user to review (send: false). Set send: true to send immediately. Provide either body (plain text) or html_body (rich HTML) or both — if both are provided, html_body is used. At least one of body or html_body is required."
"description": "Compose a new email. By default opens as a draft for the user to review (send: false). Set send: true to send immediately. Provide either body (plain text) or html_body (rich HTML) or both — if both are provided, html_body is used. At least one of body or html_body is required.",
"name": "compose_message"
},
{
"name": "reply_to_message",
"description": "Reply to an existing email. By default opens as a draft for user review. The original message is quoted automatically by Mail. Provide either body or html_body for the reply content. At least one of body or html_body is required."
"description": "Reply to an existing email. By default opens as a draft for user review. The original message is quoted automatically by Mail. Provide either body or html_body for the reply content. At least one of body or html_body is required.",
"name": "reply_to_message"
},
{
"name": "move_message",
"description": "Move a message to a different mailbox. Use list_mailboxes to get valid destination mailbox_path values. The destination must be in the same account as the message."
"description": "Move a message to a different mailbox. Use list_mailboxes to get valid destination mailbox_path values. The destination must be in the same account as the message.",
"name": "move_message"
},
{
"name": "set_message_status",
"description": "Update the read, flagged, or junk status of a message. Only the provided fields are changed — omitted fields are left as-is."
"description": "Update the read, flagged, or junk status of a message. Only the provided fields are changed — omitted fields are left as-is.",
"name": "set_message_status"
},
{
"name": "get_thread",
"description": "Get all messages in a conversation thread, ordered chronologically (oldest first). Provide any message_id from the thread. Returns summaries by default — set include_bodies to true to include message bodies. Thread detection is based on subject matching and In-Reply-To/References headers (best-effort)."
"widget": true,
"description": "Get all messages in a conversation thread, ordered chronologically (oldest first). Provide any message_id from the thread. Returns summaries by default — set include_bodies to true to include message bodies. Thread detection is based on subject matching and In-Reply-To/References headers (best-effort).",
"name": "get_thread"
}
],
"skills": [
Expand Down Expand Up @@ -90,6 +93,22 @@
}
}
]
},
{
"version": "1.0.3",
"release_date": "2026-05-11",
"artifacts": [
{
"os": "macos",
"arch": "arm64",
"url": "https://github.com/osaurus-ai/osaurus-mail/releases/download/1.0.3/osaurus.mail-1.0.3-macos-arm64.zip",
"sha256": "ff689065c48c5d44d5cf17764566b0a1b71659f675deceb59450b406dde9d763",
"min_macos": "15.0",
"minisign": {
"signature": "untrusted comment: signature from minisign secret key\nRWTmCafy0+6VidqkL/nPSo1cp+sUTtzJp++L1uct0+gBKWfAzLQLFhKX2o0phjMkE0ZoLQ3/6m9H8RUswtBZ/FWW3Vwh5hCoYw0=\ntrusted comment: timestamp:1778495002\tfile:osaurus.mail-1.0.3-macos-arm64.zip\nc/BRXQIqEk+1XFChruQ1+VI5Jwf12Svrh/05eXY+ALq89MaziCxhR6ZluwezHcD41ttOJsB06Q/u+j9qD0aGAA=="
}
}
]
}
],
"skill": "---\nname: osaurus-mail\ndescription: Read, search, compose, and manage Apple Mail emails using the Osaurus Mail plugin. Use when the user asks to check email, read messages, send replies, search their inbox, triage mail, summarize threads, or manage mailboxes.\nmetadata:\n author: osaurus\n version: \"0.1.0\"\n---\n\n# Osaurus Mail\n\nRead, search, compose, and manage emails in Apple Mail. All operations happen locally via AppleScript — no data leaves the device.\n\n## Workflow\n\nAlways follow this sequence when working with email:\n\n1. **Discover mailboxes.** Call `list_mailboxes` first to get the available `mailbox_path` values. Never guess mailbox names.\n2. **Browse or search.** Use `list_messages` to browse a specific mailbox, or `search_messages` to find messages by keyword. Both return summaries without bodies.\n3. **Read full content.** Call `read_message` with a `message_id` to get the body text. Only read messages the user actually needs — don't bulk-read unnecessarily.\n4. **Act.** Use `compose_message`, `reply_to_message`, `move_message`, or `set_message_status` to take action. Default to drafts (`send: false`) for compose and reply so the user can review.\n5. **Paginate.** When `has_more` is `true`, call again with `offset: next_offset` to get the next page. Don't assume all results fit in one call.\n\nNever skip `list_mailboxes`. Never guess a `mailbox_path` or `message_id`. Always use values returned by a previous tool call.\n\n## Identifiers\n\nTwo identifier types are used across all tools. Always pass them verbatim — never construct or modify them.\n\n### Mailbox Path\n\nFormat: `<account>/<mailbox>[/<child>]*`\n\n```\niCloud/INBOX\niCloud/Sent\niCloud/Archive\nGmail/INBOX\nGmail/Work/Projects\nGmail/[Gmail]/All Mail\n```\n\nReturned by `list_mailboxes`. Used as the `mailbox_path` parameter in `list_messages`, `search_messages`, and `move_message`.\n\n### Message ID\n\nRFC 2822 Message-ID header. Globally unique, survives Mail.app restarts.\n\n```\n<CABx+XYZ123@mail.gmail.com>\n<abc-def-456@icloud.com>\n```\n\nReturned in every message summary by `list_messages`, `search_messages`, and `get_thread`. Used as the `message_id` parameter in `read_message`, `reply_to_message`, `move_message`, `set_message_status`, and `get_thread`.\n\n## Pagination\n\n`list_messages` and `search_messages` return paginated results:\n\n```json\n{\n \"messages\": [...],\n \"total\": 342,\n \"has_more\": true,\n \"next_offset\": 20\n}\n```\n\n- `total` is the count of all messages matching the current filters.\n- When `has_more` is `true`, call again with `offset` set to `next_offset`.\n- Default limit is 20 for `list_messages`, 10 for `search_messages`. Max is 50.\n- Messages are returned newest-first.\n\n## Drafts vs Sending\n\n`compose_message` and `reply_to_message` both default to `send: false`, which opens a draft in Mail's compose window for the user to review before sending.\n\n- **Always default to drafts** unless the user explicitly asks to send immediately.\n- When drafting, tell the user: \"I've opened a draft in Mail for your review.\"\n- Only set `send: true` when the user says something like \"send it\" or \"send immediately.\"\n\n## Tool Reference\n\n### `list_mailboxes`\n\n- No parameters. Returns all mailboxes across all configured accounts.\n- Call this first in every email session to discover available mailbox paths.\n- Returns `mailbox_path`, `unread_count`, and `message_count` for each mailbox.\n\n### `list_messages`\n\n- Requires `mailbox_path`. Browse a specific mailbox with optional filters.\n- Filters: `unread_only`, `since`, `before`, `from_contains`. All are optional.\n- Returns summaries: subject, sender, date, flags. **No body** — use `read_message` for that.\n- Dates use ISO 8601 format: `\"2026-02-01T00:00:00Z\"`.\n- `from_contains` is case-insensitive and matches against both sender name and email address.\n\n### `read_message`\n\n- Requires `message_id`. Returns the full message body, recipients, attachments, and mailbox path.\n- `max_length` (default 10000) truncates long bodies. If `body_truncated` is `true`, call again with a higher `max_length`.\n- Set `include_headers: true` only when the user needs raw email headers.\n\n### `search_messages`\n\n- Requires `query`. Searches subject, sender, and body text.\n- Optionally scope to a single mailbox with `mailbox_path`.\n- Returns the same summary format as `list_messages` — no bodies.\n- Use this instead of `list_messages` when looking for specific content rather than browsing.\n\n### `compose_message`\n\n- Requires `to` (array) and `subject`.\n- Provide `body` (plain text) or `html_body` (HTML). At least one is required. If both are given, `html_body` takes precedence.\n- Optional: `cc`, `bcc`, `from_account` (account name like `\"iCloud\"` or `\"Gmail\"`).\n- `send: false` (default) opens a draft. `send: true` sends immediately.\n\n### `reply_to_message`\n\n- Requires `message_id`. Provide `body` or `html_body` for the reply content. At least one is required.\n- `reply_all: true` replies to all recipients. Default is `false` (reply to sender only).\n- Mail automatically quotes the original message.\n- `send: false` (default) opens a draft.\n\n### `move_message`\n\n- Requires `message_id` and `destination_mailbox_path`.\n- **Source and destination must be in the same account.** You cannot move from `iCloud/INBOX` to `Gmail/Archive`. Use `list_mailboxes` to find valid destinations.\n- Common patterns: move to `Account/Archive`, `Account/Trash`, or a custom folder.\n\n### `set_message_status`\n\n- Requires `message_id`. Provide any combination of `is_read`, `is_flagged`, `is_junk`.\n- Only provided fields are changed. Omitted fields are left as-is.\n- Returns the full current status after applying changes.\n\n### `get_thread`\n\n- Requires `message_id` (any message in the thread). Returns all related messages sorted oldest-first.\n- `include_bodies: false` (default) returns summaries only. Set `true` to include bodies (truncated to `max_body_length`, default 5000).\n- Thread detection is best-effort based on subject matching. It strips Re:/Fwd: prefixes and searches the same account.\n\n## Common Patterns\n\n### Summarize unread emails\n\n```\n1. list_mailboxes()\n2. list_messages(mailbox_path=\"iCloud/INBOX\", unread_only=true, limit=10)\n3. read_message(message_id=...) for each message\n4. Summarize all messages for the user\n```\n\n### Reply to a specific person's email\n\n```\n1. search_messages(query=\"Sarah\", limit=5)\n2. Show summaries to user, confirm which message\n3. read_message(message_id=...) for full context\n4. Draft reply text\n5. reply_to_message(message_id=..., body=\"...\", send=false)\n6. Tell user: \"Draft opened in Mail for review.\"\n```\n\n### Archive old messages from a sender\n\n```\n1. list_mailboxes() → find archive mailbox\n2. list_messages(mailbox_path=\"iCloud/INBOX\", from_contains=\"notifications@github.com\", before=\"2026-02-06T00:00:00Z\", limit=50)\n3. move_message(message_id=..., destination_mailbox_path=\"iCloud/Archive\") for each\n4. If has_more, paginate with next_offset and repeat\n```\n\n### Summarize a conversation thread\n\n```\n1. search_messages(query=\"Q4 budget\", limit=1)\n2. get_thread(message_id=..., include_bodies=true)\n3. Summarize the full thread for the user\n```\n\n### Triage inbox (mark read, flag important, archive noise)\n\n```\n1. list_mailboxes()\n2. list_messages(mailbox_path=\"iCloud/INBOX\", unread_only=true, limit=20)\n3. For each message, read the summary and decide:\n - Important → set_message_status(message_id=..., is_flagged=true, is_read=true)\n - Noise → move_message(message_id=..., destination_mailbox_path=\"iCloud/Archive\")\n - Needs reply → leave unread, tell user\n```\n\n## Limitations\n\n- **Apple Mail must be running.** If Mail is closed, all tools return a `mail_not_running` error.\n- **Automation permission required.** The user must grant Osaurus access to control Mail in System Settings → Privacy & Security → Automation.\n- **No attachment content.** `read_message` returns attachment names and sizes, but cannot read attachment file contents.\n- **Thread detection is best-effort.** Based on subject matching, not server-side thread IDs. Threads with heavily modified subjects may not group perfectly.\n- **Cross-account moves fail.** A message in iCloud cannot be moved to a Gmail mailbox. The error message will suggest the correct account.\n- **Rate limits on large mailboxes.** `whose` clauses in AppleScript can be slow on mailboxes with thousands of messages. Use filters (`since`, `before`, `from_contains`, `unread_only`) to narrow results.",
Expand Down
Loading