Skip to Content
ReferenceActionsJMAP

JMAP Action

The JMAP action calls JMAP  methods (RFC 8620 , RFC 8621 ). It fetches the session, fills in the account of each call, sends the calls in one request, and returns the responses by call id, with the method errors gathered in one list.

It is an external action, published in mozership/probe-jmap . Probe downloads it the first time a workflow uses it, and runs the executable whose SHA-256 the action.yml at the pinned commit names. It needs Probe v1.21.0 or later, which reads the guard and the params its action.yml declares.

Basic Syntax

A step pins the action by a full commit SHA. The notes of each release  start with the uses line to copy.

steps: - name: Read the latest message uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0 with: url: https://jmap.example.com basic_auth: username: "{{vars.user}}" password: "{{vars.pass}}" calls: - method: Email/query id: latest args: sort: [{property: receivedAt, isAscending: false}] limit: 1 - method: Email/get id: email args: "#ids": {resultOf: latest, path: /ids} properties: [subject, from, receivedAt] test: status == 0 && len(res.results.email.list) == 1 echo: "Latest: {{res.results.email.list[0].subject}}"

Parameters

ParameterTypeRequiredDefaultDescription
urlStringYes-The server, http or https. A URL with no path is taken as the server, whose session is at /.well-known/jmap; a URL with a path is the session URL itself
callsArrayYes-The method calls, sent in one request in this order
usingArrayNoinferredThe capabilities the request uses. By default the core capability and those that define the methods in calls
account_idStringNoprimary accountThe account of each call whose args give no accountId
basic_authObjectNo-username and password for HTTP Basic authentication
headersObjectNo-Request headers, such as Authorization: Bearer <token>
timeoutDurationNo30sTime limit for the session and the API request together, as 10s or a number of seconds. 0 removes it

A call in calls takes:

FieldRequiredDescription
methodYesThe method name, such as Email/get
argsNoThe arguments of the method
idNoThe call id, which a back-reference and res.results use. Defaults to c and the position of the call, from c0

A call whose args give neither accountId nor #accountId is made in account_id, or else in the primary account the session names for the capability of the method. Core/echo is made in no account.

A back-reference, an argument whose name starts with #, may leave out name: it is given the method of the call that resultOf names. A back-reference takes the place of the whole argument, so it cannot be put inside one, such as inside filter.

Capabilities

using is inferred from the type in the method name:

TypeCapability
Coreurn:ietf:params:jmap:core
Mailbox, Thread, Email, SearchSnippeturn:ietf:params:jmap:mail
Identity, EmailSubmissionurn:ietf:params:jmap:submission
VacationResponseurn:ietf:params:jmap:vacationresponse
MDNurn:ietf:params:jmap:mdn
Bloburn:ietf:params:jmap:blob
Quotaurn:ietf:params:jmap:quota
SieveScripturn:ietf:params:jmap:sieve
AddressBook, ContactCardurn:ietf:params:jmap:contacts

A method of any other type needs using. When using is given, it is sent as written.

A key of with that is not one of the parameters above fails the step before anything is sent. Its action.yml declares the parameters as params, so probe check reports such a key with its line.

Response Object

PropertyTypeDescription
res.codeIntegerHTTP status code
res.statusStringHTTP status line, such as "200 OK"
res.headersObjectResponse headers, keyed by canonical name
res.sessionObjectThe session object as the server sent it, or null when there is none
res.resultsObjectThe arguments of each method response, keyed by call id. A method error is there too, as the error object
res.responsesArrayEvery method response in order, as name, args and id
res.errorsArrayWhat failed, described below; empty when nothing did
res.bodyAnyThe whole response body, parsed when it is JSON, otherwise the raw string
res.rawbodyStringThe unparsed body, present when the body is JSON
reqObjectThe session_url, the API url, using, the calls as sent and the headers
rtDurationTime for the session and the API request together
statusInteger0 when the API answered 2xx with method responses and res.errors is empty; 1 otherwise

A JMAP server answers a method it could not run with HTTP 200, so res.code alone does not tell that the calls succeeded. res.errors gathers every failure, each the error object the server sent with these fields added:

kindWhat failedAdded fields
methodA method call, answered with an error responseid, method
notCreated, notUpdated, notDestroyedA record of a /set callid, method, key (the creation id or the record id)
requestThe whole request, answered with a problem details object-
sessionThe session, which the server did not givedescription

When the server gives no session, the method calls are not sent, and res is the response to the session request.

Any response the server sends is a result, so a test can assert on a method error or a 401. Only a request that gets no response, such as a refused connection or a timeout, fails the step as an error.

Examples

Sending a Message and Waiting for It

Email/set makes the message and EmailSubmission/set sends it, naming the message by its creation id #msg. The steps that follow wait for it with retry.

steps: - name: Send a message to bob uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0 with: url: "{{vars.url}}" basic_auth: {username: "{{vars.alice}}", password: "{{vars.alice_pass}}"} calls: - method: Email/set id: create args: create: msg: mailboxIds: "{{outputs.alice.sent}}": true from: [{email: "{{vars.alice}}"}] to: [{email: "{{vars.bob}}"}] subject: "{{vars.subject}}" bodyValues: {body: {value: Sent by probe.}} textBody: [{partId: body, type: text/plain}] - method: EmailSubmission/set id: submit args: create: sub: identityId: "{{outputs.alice.identity}}" emailId: "#msg" test: status == 0 && res.results.submit.created.sub.id != nil - name: Wait for it in bob's mailbox uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0 with: url: "{{vars.url}}" basic_auth: {username: "{{vars.bob}}", password: "{{vars.bob_pass}}"} calls: - method: Email/query id: query args: filter: {subject: "{{vars.subject}}"} - method: Email/get id: get args: "#ids": {resultOf: query, path: /ids} properties: [subject, receivedAt] retry: max_attempts: 30 interval: 1s test: status == 0 && len(res.results.get.list) == 1

Asserting on a Failure

steps: - name: A record that cannot be created uses: github.com/mozership/probe-jmap@52000030f1bd839677830d1ab07a024183f581b7 # v0.2.0 with: url: "{{vars.url}}" basic_auth: {username: "{{vars.user}}", password: "{{vars.pass}}"} calls: - method: Email/set args: create: bad: mailboxIds: {no-such-mailbox: true} test: | status == 1 && res.errors[0].kind == "notCreated" && res.errors[0].key == "bad"

Guard

Under --read-only, a step whose calls include a method other than /get, /query, /changes, /queryChanges, /lookup and /echo is refused before anything is sent. A host that --allow-host does not allow is refused, for the session, the API URL and any redirect. Its action.yml declares guard: [read-only, allow-host], so Probe runs it under either without --allow-action. See Guard.

See Also

  • External Actions - How Probe resolves and checks an external action
  • IMAP - Mailboxes over IMAP
  • SMTP - Delivering mail to a server
  • GraphQL - The other external action published alongside Probe
Updated at