Using Spark

SkillSearch

Use 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.

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.

LevelAllowed operations
read-onlyList, search, and read emails, threads, folders, events, contacts, meetings, teams
triageEverything in read-only plus all write operations: draft, comment, action, contact-action
sendEverything 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

ToolAccessDescription
accountsread-onlyList accounts, calendars, teams, shared inboxes, and access levels
foldersread-onlyList folders/labels with message counts
emailsread-onlyList emails with filters and pagination
searchread-onlyTopic search (full bodies), or list emails by filter across all folders
threadread-onlyRead full thread - headers, bodies, attachments
attachmentread-onlyRead a single email attachment by ID (auto-downloads)
eventsread-onlyList calendar events for a time range
eventsendCreate, update, delete, or RSVP to a calendar event; invite or remove attendees
availabilityread-onlyFind free time slots, optionally with attendees
contactsread-onlySearch contacts by name or email
teamread-onlyShow team info, members, shared inboxes, assignments
meetingsread-onlyList meeting transcripts
meetingread-onlyRead a single meeting transcript
templatesread-onlyList saved message templates (personal and team)
templateread-onlyShow a single template by ID or name with its placeholders
drafttriageCreate or edit a draft (new, reply, reply-all, forward, from template); delete a draft; list account signatures; share with team
commenttriagePost a team comment on a thread
actiontriage (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-actiontriagePerform 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.

ParameterTypeRequiredDescription
accountstringnoFilter 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.

ParameterTypeRequiredDescription
folderstringnoFolder identifier. Unified Inbox if omitted.
filterstringnoGmail-style filter (see operators below).
pageintegernoPage number, 1-based (default 1).
page_sizeintegernoResults per page (default 50).
orderstringnoascending or descending.
new_sendersbooleannoShow 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):

FormatExampleMeaning
Bare nameInbox, ArchiveUnified folder (cross-account)
Emailuser@example.comAccount inbox shorthand
email:Folderuser@example.com:ArchiveSpecific account folder
Team nameMy TeamAll shared threads in a team
shared@email:Inboxshared@co.com:InboxShared inbox open items (conversation view, matches Desktop)
shared@email:Archiveshared@co.com:ArchiveShared inbox done/archived items (conversation view)
shared@email:Foldershared@co.com:LabelOther shared inbox folder / label

Filter operators (combine space-separated inside the filter string):

OperatorExample
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/ddbefore:2026/03/01
after:yyyy/MM/ddafter:2026/01/01
newer_than:Xdnewer_than:7d (also w, m, y)
older_than:Xdolder_than:30d
has:attachmentalso document, spreadsheet, presentation, reminder
is:unreadalso read, starred, pinned, unreplied
is:sharedemails shared to any team (alias for is:shared_email)
is:shared_inbox_openopen items in shared inbox
is:shared_inbox_donecompleted/closed items in shared inbox
category:<name>priority, personal, notification, newsletter, invitation, invitation_response
assigned_to:<who>me, <email>, unassigned, other
assigned_by:meemails you delegated
filename:<name>filename:report.pdf

search

Two modes, selected by whether you pass query:

  • Topic mode (query set): 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 (query omitted): a paged, compact table of every email matching filter/in across all folders in all accounts, newest first (same columns as emails). Trash, Spam, and Blocked are excluded unless in targets one of them. Use this to filter by metadata (especially from:) beyond the Unified Inbox that emails is limited to.
ParameterTypeRequiredDescription
querystringnoSearch topic (keyword + semantic matching). Omit to switch to list mode.
filterstringnoGmail-style filter to narrow results (same operators as emails).
instringnoScope: account, folder, team, or shared inbox. All folders if omitted.
pageintegernoPage number, 1-based (default 1). List mode only.
page_sizeintegernoResults per page (default 50). List mode only.
orderstringnoascending 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.

ParameterTypeRequiredDescription
message_idstringyesMessage ID from emails/search, or a Spark deep link (the Link: line of a previous result).
download_attachmentsbooleannoFetch 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.

ParameterTypeRequiredDescription
idintegeryesAttachment 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/*) - inline image content block, ready for vision.
  • Audio (audio/*) - inline audio content block.
  • Text (text/*, plus JSON/XML/NDJSON) - inline text content block with the file's UTF-8 contents.
  • Anything else (PDF, ZIP, Office documents, etc.) - embedded resource content block with a base64 blob and 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:

  1. thread { "message_id": "<id>" } - find the attachment's ID in the Attachments: table.
  2. attachment { "id": <attachment-id> } - read its bytes.

events

List calendar events for a time range. Defaults to today's remaining events.

ParameterTypeRequiredDescription
todaybooleannoFrom now until end of today.
tomorrowbooleannoTomorrow, full day.
weekbooleannoFrom now until end of this week.
startstringnoStart date (yyyy-MM-dd or yyyy-MM-ddTHH:mm).
endstringnoEnd date (same formats).
instringnoCalendar 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).

ParameterTypeRequiredDescription
modestringyescreate, update, delete, or rsvp.
event_idstringfor update/delete/rsvpFor 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).
statusstringfor rsvpRSVP status: accept, decline, or maybe.
titlestringnoEvent title / summary.
startstringyes (for create)Start date/time (yyyy-MM-dd, dd/MM/yyyy, yyyy-MM-ddTHH:mm, or yyyy-MM-ddTHH:mm:ssXXX).
endstringnoEnd date/time (same formats).
all_daybooleannoMark the event as all-day.
descriptionstringnoEvent description / notes.
alertsstringnoComma-separated alert offsets (Ns for N seconds before, e.g. 300s,600s) or absolute dates.
locationstringnoEvent location.
video_conferencestringnoAttach a video conference: auto (account default), or meet / zoom / teams.
calendarstringnoTarget calendar for create (email@domain.com or email@domain.com:Name).
addarray of stringnoAttendee email address(es) to invite (create / update). New attendees receive an invitation.
removearray of stringnoAttendee 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.

ParameterTypeRequiredDescription
todaybooleannoFrom now until end of today.
tomorrowbooleannoTomorrow, full day.
weekbooleannoFrom now until end of this week.
startstringnoStart date (yyyy-MM-dd or yyyy-MM-ddTHH:mm).
endstringnoEnd date (same formats).
attendeesstringnoComma-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.

ParameterTypeRequiredDescription
querystringyesName 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.

ParameterTypeRequiredDescription
namestringnoTeam name (or partial match). Lists all teams if omitted.
team {}
team { "name": "My Team" }

meetings

List meeting transcripts, newest first. Supports filtering and pagination.

ParameterTypeRequiredDescription
filterstringnoFilter expression (operators below).
pageintegernoPage number, 1-based.
page_sizeintegernoResults 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.

ParameterTypeRequiredDescription
meeting_idstringyesMeeting ID (from meetings) or a Spark deep link.
transcriptbooleannoInclude the full transcript text.
notesbooleannoInclude 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.

ParameterTypeRequiredDescription
personalbooleannoShow only personal templates.
teamstringnoShow only the named team's templates.
pageintegernoPage number, 1-based (default 1).
page_sizeintegernoResults 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.

ParameterTypeRequiredDescription
refstringyesTemplate 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 from to/account when applied. Not overridable via placeholder.
  • [manual] - free-form; must be passed as placeholder: ["<name>=<value>"] to draft.

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