- JavaScript 97.3%
- Nix 2.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
send_email now takes an attachments array ({path} read server-side,
{data,name} inline base64, or {blobId,...} to re-send an existing blob
without re-uploading) plus oneEmailPerAttachment, which duplicates the
message once per attachment for one-document-per-email processing queues.
Path reads can be confined with FASTMAIL_ATTACHMENT_ROOTS; uploads are
capped by the session's maxSizeUpload. Also fixed the stdin-close handler
to let in-flight requests flush their replies before exiting.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
| fastmail-send-mcp.mjs | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
fastmail-send-mcp
A tiny stdio MCP server that fills the gaps in the official Fastmail MCP:
send_email sends mail immediately (the official one deliberately only
drafts), and list_attachments/get_attachment retrieve attachment payloads
(the official read_email stops at body text).
It also carries its own search_email and read_email: email ids from the
official Fastmail MCP are not usable here (they don't resolve against this
server's JMAP session), so attachment retrieval starts from this server's own
search results instead.
Zero dependencies — plain Node (18+, needs global fetch), newline-delimited
JSON-RPC over stdin/stdout.
Auth
The server reads FASTMAIL_API_TOKEN from its environment; it never handles
secrets itself, on the assumption that an MCP proxy injects the token. Create
the token at Fastmail → Settings → Privacy & Security → API tokens, with the
Email and Email Submission scopes.
Optional: FASTMAIL_SESSION_URL overrides the JMAP session endpoint
(defaults to https://api.fastmail.com/jmap/session),
FASTMAIL_MAX_ATTACHMENT_BYTES caps how large an attachment may be returned
inline (default 10 MiB), and FASTMAIL_ATTACHMENT_ROOTS (colon-separated
directories) confines which paths send_email may attach from. Leaving the
latter unset allows any file the server process can read — note that anything
readable then becomes mailable, so set it on machines with other tenants.
Running
FASTMAIL_API_TOKEN=fmu1-... node fastmail-send-mcp.mjs
Example proxy/client config:
{
"mcpServers": {
"fastmail-send": {
"command": "node",
"args": ["/home/deprekated/Projects/home/fastmail-send-extension/fastmail-send-mcp.mjs"],
"env": { "FASTMAIL_API_TOKEN": "<injected by proxy>" }
}
}
}
The tools
send_email arguments:
| field | required | notes |
|---|---|---|
to |
yes | array of a@b.com or Name <a@b.com> |
subject |
yes | |
body |
one of | plain-text body |
html |
one of | HTML body; may be combined with body for multipart/alternative |
cc, bcc |
no | same format as to |
from |
no | must match one of the account's identities; defaults to the primary identity |
inReplyTo |
no | Message-ID being replied to, for threading |
attachments |
no | array of attachment specs, see below |
oneEmailPerAttachment |
no | fan a batch of attachments out as individual emails |
On success the sent message is filed into the Sent mailbox with $draft
cleared. Failures (bad identity, rejected submission, API errors) come back as
tool errors with the JMAP error type and description.
Attachments
Each entry in attachments is one of:
{ "path": "/abs/path/file.pdf" }— read from the filesystem of the machine running this server (not the MCP client's).namedefaults to the basename andtypeis guessed from the extension; both can be overridden. Subject toFASTMAIL_ATTACHMENT_ROOTSif set.{ "data": "<base64>", "name": "file.pdf" }— inline payload;nameis required,typeguessed from it if omitted.{ "blobId": "...", "name": "file.pdf", "type": "application/pdf" }— an existing JMAP blob from this server'slist_attachments/get_attachment, re-sent without re-uploading (i.e. forwarding an attachment).
Uploads are capped by the JMAP server's advertised maxSizeUpload (~50 MB on
Fastmail).
With oneEmailPerAttachment: true, one copy of the email is sent per
attachment — identical recipients, subject, and body, each carrying exactly
one attachment. This suits processing queues that expect one document per
message. Copies go out sequentially; on failure the error reports which copies
were already sent, so retries can resend just the remainder.
search_email filters by any combination of query (full-text), from,
to, subject, mailbox (role or name, e.g. inbox), after/before
(UTC dates), and hasAttachment, returning up to limit (default 20,
max 100) results sorted newest-first, each with its JMAP id, addresses,
subject, date, preview, and hasAttachment flag.
read_email takes an emailId from search_email and returns full headers
(including messageId, handy as inReplyTo for threaded replies via
send_email), the body (plain text preferred, raw HTML with
bodyType: "html" for HTML-only mail, truncated past 256 KiB per part), and
attachment metadata.
list_attachments takes an emailId (from search_email) and returns each
attachment's name, MIME type, size, index, and blobId.
get_attachment takes an emailId plus — when the email has more than one
attachment — a selector (name, index, or blobId), downloads the blob
server-side, and returns {name, type, size, blobId, encoding: "base64", data}. Payloads are inlined as base64 because raw JMAP blob URLs require the
bearer token, which clients behind the auth proxy don't hold. Attachments
larger than the configured cap are refused rather than truncated.