Access the Fastmail JMAP API with managed API token authentication. Search and read mail, manage mailboxes, send email, and work with contacts and masked email addresses.
Reference
Fastmail uses JMAP, not REST. Almost every operation is one POST to /fastmail/jmap/api/ carrying a batch of method calls, each written as [methodName, arguments, callId]. using lists the capabilities the calls need.
Get Session
GET /fastmail/jmap/sessionUse primaryAccounts["urn:ietf:params:jmap:mail"] as the accountId for every method call. Ignore the apiUrl, uploadUrl, downloadUrl, and eventSourceUrl fields: they point at Fastmail's own hosts, so always call through /fastmail/... instead.
List Mailboxes
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
["Mailbox/get", { "accountId": "{accountId}", "ids": null }, "c0"]
]
}Find mailboxes by role (inbox, drafts, sent, trash, ...), not by name, which users can edit.
Search Messages
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
["Email/query", {
"accountId": "{accountId}",
"filter": { "inMailbox": "{mailboxId}" },
"sort": [{ "property": "receivedAt", "isAscending": false }],
"limit": 20
}, "q"],
["Email/get", {
"accountId": "{accountId}",
"#ids": { "resultOf": "q", "name": "Email/query", "path": "/ids" },
"properties": ["id", "threadId", "subject", "from", "receivedAt", "preview"]
}, "g"]
]
}Email/query returns only IDs, so pair it with Email/get in the same request using a #ids back-reference.
Get Message Body
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
["Email/get", {
"accountId": "{accountId}",
"ids": ["{emailId}"],
"properties": ["id", "subject", "from", "to", "textBody", "htmlBody", "attachments", "bodyValues"],
"fetchTextBodyValues": true
}, "c0"]
]
}Update Messages
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
["Email/set", {
"accountId": "{accountId}",
"update": {
"{emailId}": {
"keywords/$seen": true,
"mailboxIds/{targetMailboxId}": true,
"mailboxIds/{sourceMailboxId}": null
}
}
}, "c0"]
]
}true adds and null removes, so adding one mailbox and removing another moves the message.
Create Draft
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
"methodCalls": [
["Email/set", {
"accountId": "{accountId}",
"create": {
"d1": {
"mailboxIds": { "{draftsMailboxId}": true },
"keywords": { "$draft": true },
"from": [{ "email": "user@fastmail.com" }],
"to": [{ "email": "recipient@example.com" }],
"subject": "Hello",
"bodyStructure": { "type": "text/plain", "partId": "body" },
"bodyValues": { "body": { "value": "Message text here." } }
}
}
}, "c0"]
]
}Send Email
Get an identityId from Identity/get first, then submit the draft. onSuccessUpdateEmail files the message into Sent in the same request.
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
"methodCalls": [
["EmailSubmission/set", {
"accountId": "{accountId}",
"create": {
"s1": { "emailId": "{draftEmailId}", "identityId": "{identityId}" }
},
"onSuccessUpdateEmail": {
"#s1": {
"mailboxIds/{sentMailboxId}": true,
"mailboxIds/{draftsMailboxId}": null,
"keywords/$draft": null
}
}
}, "c0"]
]
}Add "sendAt" (UTC, ISO 8601) to the submission to schedule it.
Search Contacts
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:contacts"],
"methodCalls": [
["ContactCard/query", { "accountId": "{accountId}", "filter": { "text": "ada" }, "limit": 20 }, "q"],
["ContactCard/get", {
"accountId": "{accountId}",
"#ids": { "resultOf": "q", "name": "ContactCard/query", "path": "/ids" },
"properties": ["id", "name", "emails", "phones"]
}, "g"]
]
}Contacts are JSContact ContactCard objects. Address books are read-only.
List Masked Email Addresses
POST /fastmail/jmap/api/
Content-Type: application/json
{
"using": ["urn:ietf:params:jmap:core", "https://www.fastmail.com/dev/maskedemail"],
"methodCalls": [
["MaskedEmail/get", { "accountId": "{accountId}", "ids": null }, "c0"]
]
}Upload Attachment
POST /fastmail/jmap/upload/{accountId}/
Content-Type: application/pdf
<binary data>Returns a blobId. Reference it from a draft's bodyStructure with "disposition": "attachment".
Notes:
- Supported capabilities are
urn:ietf:params:jmap:core,mail,submission,contacts, andhttps://www.fastmail.com/dev/maskedemail. Calendars, vacation responses, blobs, quotas, principals, and Sieve are not available, and listing one inusingfails the whole request. - JMAP errors usually arrive inside an HTTP
200: check each method response for anerror, and each/setfornotCreated,notUpdated, andnotDestroyed. - Downloading blobs isn't supported through the gateway. Read message content with
Email/getand thefetchTextBodyValuesorfetchHTMLBodyValuesflags. Uploads work. Email/setdestroydeletes permanently and skips Trash. To trash a message, move it to the mailbox whoseroleistrash.- A request can carry up to 50 method calls.
- Connections use a Fastmail API token. Create one in Fastmail under Settings → Privacy & Security → Integrations → API tokens, granting only the scopes you need.