Maton

Fastmail

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/session

Use 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, and https://www.fastmail.com/dev/maskedemail. Calendars, vacation responses, blobs, quotas, principals, and Sieve are not available, and listing one in using fails the whole request.
  • JMAP errors usually arrive inside an HTTP 200: check each method response for an error, and each /set for notCreated, notUpdated, and notDestroyed.
  • Downloading blobs isn't supported through the gateway. Read message content with Email/get and the fetchTextBodyValues or fetchHTMLBodyValues flags. Uploads work.
  • Email/set destroy deletes permanently and skips Trash. To trash a message, move it to the mailbox whose role is trash.
  • 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.

Resources

On this page