Using Spark
SkillSearchUse the Spark MCP extension to access and act on the user's Spark email data - list emails, search by topic, read threads, check calendar events, find availability, look up contacts, view team info, draft messages (from scratch or saved templates), send drafts, share drafts with teammates, post team comments, triage threads, create/update/delete calendar events and manage attendees, and manage contacts. Use when the user asks about their emails, calendar, contacts, meetings, scheduling, or wants to send, reply, archive, snooze, assign, comment, categorize, or schedule.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Using Spark skill
What this skill tells your AI
The instructions your AI receives, as published by readdle/spark-claude-extension in skill/spark/SKILL.md and read by ahel’s review.
The Spark MCP extension exposes the user's mailbox, calendar, contacts, meetings, and team data as tools. Use it to answer questions about their Spark data and, when the account allows it, to act on it - draft messages, post team comments, triage threads, and manage contacts.
Access levels
Each account and shared inbox has its own access level, configured by the user in Spark Desktop under Settings > AI Agents. The accounts tool reports the current level for every account and shared inbox.
| Level | Allowed operations |
|---|---|
| read-only | List, search, and read emails, threads, folders, events, contacts, meetings, teams |
| triage | Everything in read-only plus all write operations: draft, comment, action, contact-action |
| send | Everything in triage plus mail-emitting operations: sending drafts (action with send, including scheduled "Send Later") and the entire event tool (create / update / delete / rsvp, including inviting or removing attendees) |
Shared inboxes can have a different access level than the parent account - for example, a personal account may have triage access while a shared inbox under the same team is read-only or disabled.
If a write tool is invoked against an account with insufficient access, the call returns a descriptive error explaining how to raise the level. Pass that on to the user verbatim and offer to retry once they have updated the setting, or perform the action in Spark Desktop directly.
Calling tools
Each tool is an MCP tool exposed by the spark server. Examples below use the notation:
tool_name { "param": "value", ... }
Map this to your runtime's tool-call syntax - the left side is the MCP tool name, the right side is the JSON arguments object.
Always call accounts once at the start of any non-trivial task to discover available accounts, calendars, teams, shared inboxes, and access levels. Call folders before passing folder identifiers to emails or search.
Tools
| Tool | Access | Description |
|---|---|---|
accounts | read-only | List accounts, calendars, teams, shared inboxes, and access levels |
folders | read-only | List folders/labels with message counts |
emails | read-only | List emails with filters and pagination |
search | read-only | Topic search (full bodies), or list emails by filter across all folders |
thread | read-only | Read full thread - headers, bodies, attachments |
attachment | read-only | Read a single email attachment by ID (auto-downloads) |
events | read-only | List calendar events for a time range |
event | send | Create, update, delete, or RSVP to a calendar event; invite or remove attendees |
availability | read-only | Find free time slots, optionally with attendees |
contacts | read-only | Search contacts by name or email |
team | read-only | Show team info, members, shared inboxes, assignments |
meetings | read-only | List meeting transcripts |
meeting | read-only | Read a single meeting transcript |
templates | read-only | List saved message templates (personal and team) |
template | read-only | Show a single template by ID or name with its placeholders |
draft | triage | Create or edit a draft (new, reply, reply-all, forward, from template); delete a draft; list account signatures; share with team |
comment | triage | Post a team comment on a thread |
action | triage (send for send / unschedule) | Perform actions on emails (archive, pin, snooze, assign, etc.); send a draft or manage a scheduled send at the send level |
contact-action | triage | Perform actions on contacts (block, accept, categorize, etc.) |
accounts
List all configured accounts with their aliases, calendars, teams, and shared inboxes. Output reports each account's and shared inbox's access level - check this before attempting any write tool. Takes no parameters.
accounts {}
Run first to discover what is available and which accounts allow triage actions.
folders
List folders with message counts. Output includes folder identifiers in parentheses - use those as the folder parameter for emails and as scope for search. Mailboxes backed by a Google account show (Gmail labels) on the Email Account or Shared Inbox header. Teams show the team name as a usable identifier for emails.
| Parameter | Type | Required | Description |
|---|---|---|---|
account | string | no | Filter by email address (account or shared inbox). All accounts if omitted. |
folders {}
folders { "account": "user@example.com" }
emails
List emails with metadata (ID, From, Date, Subject, Flags). Supports pagination and Gmail-style filters. Does not return bodies - use search or thread when you need content.
| Parameter | Type | Required | Description |
|---|---|---|---|
folder | string | no | Folder identifier. Unified Inbox if omitted. |
filter | string | no | Gmail-style filter (see operators below). |
page | integer | no | Page number, 1-based (default 1). |
page_size | integer | no | Results per page (default 50). |
order | string | no | ascending or descending. |
new_senders | boolean | no | Show only new-sender emails (GateKeeper). |
emails {} # Unified Inbox
emails { "folder": "user@example.com:Archive" } # specific folder
emails { "folder": "My Team" } # team's shared threads
emails { "filter": "from:alice@co.com is:unread" }
emails { "filter": "newer_than:7d has:attachment" }
emails { "page": 2, "page_size": 20 }
emails { "order": "ascending" }
emails { "new_senders": true }
GateKeeper: When viewing the Inbox with GateKeeper in explicit mode, new-sender emails are filtered out and a "New Senders" count is shown at the top. Set new_senders: true to view them. Use contact-action with acceptContact or blockContact to accept or block a sender (requires triage).
Folder identifier formats (call folders to see available ones):
| Format | Example | Meaning |
|---|---|---|
| Bare name | Inbox, Archive | Unified folder (cross-account) |
user@example.com | Account inbox shorthand | |
email:Folder | user@example.com:Archive | Specific account folder |
| Team name | My Team | All shared threads in a team |
shared@email:Inbox | shared@co.com:Inbox | Shared inbox open items (conversation view, matches Desktop) |
shared@email:Archive | shared@co.com:Archive | Shared inbox done/archived items (conversation view) |
shared@email:Folder | shared@co.com:Label | Other shared inbox folder / label |
Filter operators (combine space-separated inside the filter string):
| Operator | Example |
|---|---|
from:<addr> | from:alice@co.com |
to:<addr> | to:bob@co.com |
cc:<addr> | cc:team@co.com |
subject:<text> | subject:"quarterly report" |
before:yyyy/MM/dd | before:2026/03/01 |
after:yyyy/MM/dd | after:2026/01/01 |
newer_than:Xd | newer_than:7d (also w, m, y) |
older_than:Xd | older_than:30d |
has:attachment | also document, spreadsheet, presentation, reminder |
is:unread | also read, starred, pinned, unreplied |
is:shared | emails shared to any team (alias for is:shared_email) |
is:shared_inbox_open | open items in shared inbox |
is:shared_inbox_done | completed/closed items in shared inbox |
category:<name> | priority, personal, notification, newsletter, invitation, invitation_response |
assigned_to:<who> | me, <email>, unassigned, other |
assigned_by:me | emails you delegated |
filename:<name> | filename:report.pdf |
search
Two modes, selected by whether you pass query:
- Topic mode (
queryset): hybrid keyword + semantic search returning up to 20 emails with full bodies, sorted by relevance. Use this when the user asks about a topic and you need content to answer. - List mode (
queryomitted): a paged, compact table of every email matchingfilter/inacross all folders in all accounts, newest first (same columns asemails). Trash, Spam, and Blocked are excluded unlessintargets one of them. Use this to filter by metadata (especiallyfrom:) beyond the Unified Inbox thatemailsis limited to.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | no | Search topic (keyword + semantic matching). Omit to switch to list mode. |
filter | string | no | Gmail-style filter to narrow results (same operators as emails). |
in | string | no | Scope: account, folder, team, or shared inbox. All folders if omitted. |
page | integer | no | Page number, 1-based (default 1). List mode only. |
page_size | integer | no | Results per page (default 50). List mode only. |
order | string | no | ascending or descending. List mode only. |
search { "query": "quarterly report" }
search { "query": "API integration", "filter": "from:alice@co.com" }
search { "query": "budget", "in": "user@example.com:Archive" }
search { "query": "vacation", "in": "user@example.com" }
search { "filter": "from:alice@co.com" } # list mode: every email from alice, all folders
search { "filter": "is:unread newer_than:7d", "page": 2 }
Use search with a query when the user asks about content (you need bodies). Use search without a query to filter across every folder - emails only sees the Unified Inbox, so it can't answer "every email from alice, anywhere". Use emails for plain browsing of the Inbox or a single folder.
thread
Read every message in a conversation - headers, plain-text bodies, attachment info. After the thread summary line, lists custom (non-system) folder labels once for the whole thread.
| Parameter | Type | Required | Description |
|---|---|---|---|
message_id | string | yes | Message ID from emails/search, or a Spark deep link (the Link: line of a previous result). |
download_attachments | boolean | no | Fetch attachments that aren't yet local. |
thread { "message_id": "1114" }
thread { "message_id": "1114", "download_attachments": true }
A Reply-To: line appears only when that header points somewhere other than From - mailing lists and website contact forms carry the real correspondent there. draft { "reply_to": ... } already addresses the reply to it, so don't pass to yourself.
Each message's Attachments: block is a table with columns ID, Name, Size, and MIME Type. The ID is a stable integer - pass it to the attachment tool to read the file's bytes (images, PDFs, text, audio). Local filesystem paths are deliberately omitted because sandboxed MCP clients can't follow them; the attachment tool is the only path to attachment contents through this connection.
Find IDs with emails or search first, then call thread to read the full conversation.
attachment
Read a single email attachment by its ID and receive the file content directly through MCP - the server reads the bytes off disk and returns them as image, audio, text, or embedded-resource (binary blob) content blocks depending on the MIME type. Auto-downloads the file via IMAP if it isn't cached locally yet, so this works even for attachments whose thread row showed (not downloaded, ...).
This is the sandbox-friendly way to read attachments: clients never need to touch the local filesystem path printed in the thread output.
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | yes | Attachment ID from the thread Attachments table. |
attachment { "id": 42 }
Returns a leading text block summarizing the attachment (name, size, MIME type, message ID), followed by the file content as one of:
- Image (
image/*) - inlineimagecontent block, ready for vision. - Audio (
audio/*) - inlineaudiocontent block. - Text (
text/*, plus JSON/XML/NDJSON) - inlinetextcontent block with the file's UTF-8 contents. - Anything else (PDF, ZIP, Office documents, etc.) - embedded
resourcecontent block with a base64bloband the original MIME type.
Files larger than the configured cap (default 50 MB; override with the SPARK_ATTACHMENT_MAX_BYTES env var on the MCP server) are rejected with a clear error - ask the user to open the attachment in Spark Desktop instead.
Typical flow:
thread { "message_id": "<id>" }- find the attachment's ID in theAttachments:table.attachment { "id": <attachment-id> }- read its bytes.
events
List calendar events for a time range. Defaults to today's remaining events.
| Parameter | Type | Required | Description |
|---|---|---|---|
today | boolean | no | From now until end of today. |
tomorrow | boolean | no | Tomorrow, full day. |
week | boolean | no | From now until end of this week. |
start | string | no | Start date (yyyy-MM-dd or yyyy-MM-ddTHH:mm). |
end | string | no | End date (same formats). |
in | string | no | Calendar account (user@example.com) or specific calendar (user@example.com:Work). |
events {}
events { "tomorrow": true }
events { "week": true }
events { "week": true, "in": "user@example.com" }
events { "week": true, "in": "user@example.com:Work" }
events { "start": "2026-03-16", "end": "2026-03-20" }
Call accounts to see available calendar accounts and calendar names.
event
Requires send access on the target calendar's owning account. Every mode can emit mail through the calendar service (invitations on create/update, iTIP UPDATE/CANCEL on update/delete of attendee-bearing events, an iTIP REPLY on rsvp), so the whole tool sits at the send level.
Create, update, delete, or RSVP to a calendar event, including managing its attendees. Use add / remove on create or update to invite or remove attendees; adding or removing attendees sends invitations / cancellations through the calendar provider (CalDAV / Google / Exchange).
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | string | yes | create, update, delete, or rsvp. |
event_id | string | for update/delete/rsvp | For update/delete: a calendar event ID (use events). For rsvp: a calendar event ID or the message ID of the invitation email (use emails / thread). |
status | string | for rsvp | RSVP status: accept, decline, or maybe. |
title | string | no | Event title / summary. |
start | string | yes (for create) | Start date/time (yyyy-MM-dd, dd/MM/yyyy, yyyy-MM-ddTHH:mm, or yyyy-MM-ddTHH:mm:ssXXX). |
end | string | no | End date/time (same formats). |
all_day | boolean | no | Mark the event as all-day. |
description | string | no | Event description / notes. |
alerts | string | no | Comma-separated alert offsets (Ns for N seconds before, e.g. 300s,600s) or absolute dates. |
location | string | no | Event location. |
video_conference | string | no | Attach a video conference: auto (account default), or meet / zoom / teams. |
calendar | string | no | Target calendar for create (email@domain.com or email@domain.com:Name). |
add | array of string | no | Attendee email address(es) to invite (create / update). New attendees receive an invitation. |
remove | array of string | no | Attendee email address(es) to remove (update). Removed attendees receive a cancellation. The organizer cannot be removed. |
event { "mode": "create", "title": "Sync", "start": "2026-07-01T12:00", "end": "2026-07-01T13:00" }
event { "mode": "create", "title": "OOO", "start": "2026-07-01", "all_day": true }
event { "mode": "create", "title": "Standup", "start": "2026-07-01T09:00", "end": "2026-07-01T09:15", "video_conference": "auto" }
event { "mode": "create", "title": "1:1", "start": "2026-07-01T14:00", "end": "2026-07-01T14:30", "calendar": "user@co.com:Work" }
event { "mode": "create", "title": "Sync", "start": "2026-07-01T12:00", "add": ["alice@co.com", "bob@co.com"] } # create + invite
event { "mode": "update", "event_id": "ABC-123", "title": "New title" }
event { "mode": "update", "event_id": "ABC-123", "video_conference": "meet" }
event { "mode": "update", "event_id": "ABC-123", "add": ["alice@co.com"], "remove": ["bob@co.com"] } # swap attendees
event { "mode": "delete", "event_id": "ABC-123" }
event { "mode": "rsvp", "event_id": "ABC-123", "status": "accept" } # calendar event ID
event { "mode": "rsvp", "event_id": "44268", "status": "maybe" } # invitation email message ID
Attendees (add / remove): valid only on create and update. Combine both on one update to swap attendees (removals run before additions). Adding or removing attendees always emits invitation / cancellation mail; an event with no attendees stays local.
update / delete semantics: an event with no attendees is a local change (no mail). An event with attendees sends an iTIP UPDATE (on update) or CANCEL (on delete) to all attendees. An alerts-only edit is a personal reminder and notifies nobody.
rsvp semantics: sets your own status on an invitation and emits an iTIP REPLY to the organizer. The id can be a calendar event ID or the invitation email's message ID - when a user asks you to respond to an invite, look it up with emails / thread and pass that message ID, since many providers leave an unanswered invite in the inbox without adding it to the calendar.
Typical flow when scheduling a new meeting: event { "mode": "create", ... } to lay down the event, confirm the time with the user, then event { "mode": "update", "event_id": "...", "add": [...] } to push invitations - so the user can review before any external mail goes out. When you already have the attendees, create with add in one step.
Run accounts to see writable calendars and the access level on each account.
availability
Find free time slots. Without attendees, shows the user's own availability. With attendees, computes mutual free windows. Working hours are 08:00-20:00, weekends skipped, events marked "free" are ignored.
| Parameter | Type | Required | Description |
|---|---|---|---|
today | boolean | no | From now until end of today. |
tomorrow | boolean | no | Tomorrow, full day. |
week | boolean | no | From now until end of this week. |
start | string | no | Start date (yyyy-MM-dd or yyyy-MM-ddTHH:mm). |
end | string | no | End date (same formats). |
attendees | string | no | Comma-separated email addresses. |
availability {}
availability { "tomorrow": true }
availability { "week": true, "attendees": "alice@co.com" }
availability { "start": "2026-03-16", "end": "2026-03-20", "attendees": "a@co.com,b@co.com" }
contacts
Search contacts by name or email. Strict match first, then fuzzy fallback.
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Name or email to search for. |
contacts { "query": "john" }
contacts { "query": "example.com" }
team
Show team info - metadata, shared inboxes with members, full member list, assigned emails, and assignment summary. Call without name to list available teams.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | no | Team name (or partial match). Lists all teams if omitted. |
team {}
team { "name": "My Team" }
meetings
List meeting transcripts, newest first. Supports filtering and pagination.
| Parameter | Type | Required | Description |
|---|---|---|---|
filter | string | no | Filter expression (operators below). |
page | integer | no | Page number, 1-based. |
page_size | integer | no | Results per page (default 50). |
meetings {}
meetings { "filter": "newer_than:30d" }
meetings { "filter": "subject:standup", "page_size": 10 }
Filter operators for meetings: subject:<text>, before:yyyy/MM/dd, after:yyyy/MM/dd, newer_than:Xd, older_than:Xd.
meeting
Read a single meeting transcript's summary. Optionally include the full transcript and/or user notes.
| Parameter | Type | Required | Description |
|---|---|---|---|
meeting_id | string | yes | Meeting ID (from meetings) or a Spark deep link. |
transcript | boolean | no | Include the full transcript text. |
notes | boolean | no | Include user notes. |
meeting { "meeting_id": "42" }
meeting { "meeting_id": "42", "transcript": true }
meeting { "meeting_id": "42", "notes": true }
meeting { "meeting_id": "42", "transcript": true, "notes": true }
templates
List saved Spark message templates - the drafts you can apply via draft's template parameter. Personal and team templates both appear; they round-trip from Spark Desktop.
| Parameter | Type | Required | Description |
|---|---|---|---|
personal | boolean | no | Show only personal templates. |
team | string | no | Show only the named team's templates. |
page | integer | no | Page number, 1-based (default 1). |
page_size | integer | no | Results per page (default 50). |
templates {}
templates { "personal": true }
templates { "team": "Marketing" }
templates { "page": 2, "page_size": 20 }
Output columns: ID, Scope (Personal or team name), Name, Subject, Modified. Use the ID or Name with the template tool and with draft's template parameter.
template
Show a single template's full contents and its placeholder requirements. Run this before draft { "template": ... } so you know which placeholders to fill.
| Parameter | Type | Required | Description |
|---|---|---|---|
ref | string | yes | Template ID (numeric) or name (case-insensitive). |
template { "ref": "123" }
template { "ref": "Welcome reply" }
Output includes scope, recipients, subject, body (HTML stripped to text), attachments, and a Placeholders: section listing every placeholder:
[auto]- auto-fillable (recipient/self name), resolved fromto/accountwhen applied. Not overridable viaplaceholder.[manual]- free-form; must be passed asplaceholder: ["<name>=<value>"]todraft.
If a name matches more than one template, the call errors with the matching IDs - disambiguate by ID.
draft
Requires triage access.
Create a new email draft or edit an existing one. The body is markdown and is converted to HTML. Seed a draft from an existing message with reply_to, reply_all, or forward, update an existing draft with edit, apply a saved template with template, list account signatures with mode, delete a draft with delete, and collaborate with teammates with the sharing parameters.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 42
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
spark-readdle- Source
- github.com/readdle/spark-claude-extension