JMAP Calendars

This document specifies a data model for synchronizing calendar data with a server using JMAP.

Introduction

JMAP ([@!RFC8620] – JSON Meta Application Protocol) is a generic protocol for synchronizing data, such as mail, calendars or contacts, between a client and a server. It is optimized for mobile and web environments, and aims to provide a consistent interface to different data types.

This specification defines a data model for synchronizing calendar data between a client and a server using JMAP. The data model is designed to allow a server to provide consistent access to the same data via CalDAV [@?RFC4791] as well as JMAP, however the functionality offered over the two protocols may differ. Unlike CalDAV, this specification does not define access to tasks or journal entries (VTODO or VJOURNAL iCalendar components in CalDAV).

Notational Conventions

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in BCP 14 [@!RFC2119] [@!RFC8174] when, and only when, they appear in all capitals, as shown here.

Type signatures, examples, and property descriptions in this document follow the conventions established in Section 1.1 of [@!RFC8620]. Data types defined in the core specification are also used in this document.

The LocalDate Data Type

Where LocalDate is given as a type, it means a string in the same format as Date (see [@!RFC8620], Section 1.4), but with the time-offset omitted from the end. The interpretation in absolute time depends upon the time zone for the event, which may not be a fixed offset (for example when daylight saving time occurs). For example, 2014-10-30T14:12:00.

Terminology

The same terminology is used in this document as in the core JMAP specification, see [@!RFC8620], Section 1.6.

The terms ParticipantIdentity, Calendar, CalendarEvent, and CalendarEventNotification (with these specific capitalizations) are used to refer to the data types defined in this document and instances of those data types.

Data Model Overview

An Account (see [@!RFC8620], Section 1.6.2) with support for the calendar data model contains zero or more Calendar objects, which is a named collection of CalendarEvents. Calendars can also provide defaults, such as alerts and a color to apply to events in the calendar. Clients commonly let users toggle visibility of events belonging to a particular calendar on/off. Servers may allow an event to belong to multiple Calendars within an account.

A CalendarEvent is a representation of an event or recurring series of events in JSEvent [@!I-D.ietf-calext-jscalendar] format. Simple clients may ask the server to expand recurrences for them within a specific time period, and optionally convert times into UTC so they do not have to handle time zone conversion. More full-featured clients will want to access the full event information and handle recurrence expansion and time zone conversion locally.

CalendarEventNotification objects keep track of the history of changes made to a calendar by other users, allowing calendar clients to notify the user of changes to their schedule.

The ParticipantIdentity data type represents the identities of the current user within an Account, which determines which events the user is a participant of and possibly their permissions related to that event.

In servers with support for JMAP Sharing [RFC XXX], data may be shared with other users. Sharing permissions are managed per calendar. For example, an individual may have separate calendars for personal and work activities, with both contributing to their free-busy availability, but only the work calendar shared in its entirety with colleagues. Principals may also represent schedulable entities, such as a meeting room.

Users can normally subscribe to any calendar to which they have access. This indicates the user wants this calendar to appear in their regular list of calendars. The separate “isVisible” property stores whether the user would currently like to view the events in a subscribed calendar.

UIDs and CalendarEvent Ids

Each CalendarEvent has a uid property ([@!I-D.ietf-calext-jscalendar], Section 4.1.2), which is a globally unique identifier that identifies the same event in different Accounts, or different instances of the same recurring event within an Account.

An Account MUST NOT contain more than one CalendarEvent with the same uid unless all of the CalendarEvent objects have distinct, non-null values for their recurrenceId property. (This situation occurs if the principal is added to one or more specific instances of a recurring event without being invited to the whole series.)

Each CalendarEvent also has an id, which is scoped to the JMAP Account and used for referencing it in JMAP methods. There is no necessary link between the uid property and the CalendarEvent’s id. CalendarEvents with the same uid in different Accounts MAY have different ids.

Addition to the Capabilities Object

The capabilities object is returned as part of the JMAP Session object; see [@!RFC8620], Section 2. This document defines two additional capability URIs.

urn:ietf:params:jmap:calendars

This represents support for the Calendar, CalendarEvent, CalendarEventNotification, and ParticipantIdentity data types and associated API methods. The value of this property in the JMAP Session capabilities property is an empty object.

The value of this property in an account’s accountCapabilities property is an object that MUST contain the following information on server capabilities and permissions for that account:

urn:ietf:params:jmap:principals:availability

Represents support for the Principal/getAvailability method. Any account with this capability MUST also have the urn:ietf:params:jmap:principals capability (see [RFC XXX]).

The value of this property in the JMAP Session capabilities property is an empty object.

The value of this property in an account’s accountCapabilities property is an object that MUST contain the following information on server capabilities and permissions for that account:

Principals and Sharing

For systems that also support JMAP Sharing [RFC XXX], the calendars capability is used to indicate that this principal may be used with calendaring. A new method is defined to allow users to query availability when scehduling events.

Principal Capability urn:ietf:params:jmap:calendars

A “urn:ietf:params:jmap:calendars” property is added to the Principal “capabilities” object, the value of which is an object with the following properties:

Principal/getAvailability

This method calculates the availability of the principal for scheduling within a requested time period. It takes the following arguments:

The server will first find all relevant events, expanding any recurring events. Relevant events are ones where all of the following is true:

If an event is in more than one calendar, it is relevant if all of the above are true for any one calendar that it is in.

The server then generates a BusyPeriod object for each of these events. A BusyPeriod object has the following properties:

The server MAY also generate BusyPeriod objects based on other information it has about the principal’s availability, such as office hours.

Finally, the server MUST merge and split BusyPeriod objects where the “event” property is null, such that none of them overlap and either there is a gap in time between any two objects (the utcEnd of one does not equal the utcStart of another) or those objects have a different busyStatus property. If there are overlapping BusyPeriod time ranges with different “busyStatus” properties the server MUST choose the value in the following order: confirmed > unavailable > tentative.

The response has the following argument:

The following additional errors may be returned instead of the “Principal/getAvailability” response:

notFound: No principal with this id exists, or the user does not have permission to see that this principal exists.

forbidden: The user does not have permission to query this principal’s availability.

tooLarge: The duration between utcStart an utcEnd is longer than the server is willing to calculate availability for.

rateLimit: Too many availability requests have been made recently and the user is being rate limited. It may work to try again later.

Participant Identities

A ParticipantIdentity stores information about a URI that represents the user within that account in an event’s participants. It has the following properties:

A participant in an event corresponds to a ParticipantIdentity if any of the method/uri pairs in the sendTo property of the participant are identical to a method/uri pair in the sendTo property of the identity.

The following JMAP methods are supported.

ParticipantIdentity/get

This is a standard “/get” method as described in [@!RFC8620], Section 5.1. The ids argument may be null to fetch all at once.

ParticipantIdentity/changes

This is a standard “/changes” method as described in [@!RFC8620], Section 5.2.

ParticipantIdentity/set

This is a standard “/set” method as described in [@!RFC8620], Section 5.3. The server MAY restrict the uri values the user may claim, for example only allowing mailto: URIs with email addresses that belong to the user. A standard forbidden error is returned to reject non-permissible changes.

Calendars

A Calendar is a named collection of events. All events are associated with at least one calendar.

A Calendar object has the following properties:

A CalendarRights object has the following properties:

The user is an owner for an event if the CalendarEvent object has a “participants” property, and one of the Participant objects both:

a) Has the "owner" role.
b) Corresponds to one of the user's ParticipantIdentity objects in the account.

An event has no owner if its participants property is null or omitted.

Calendar/get

This is a standard “/get” method as described in [@!RFC8620], Section 5.1. The ids argument may be null to fetch all at once.

If mayReadFreeBusy is the only permission the user has, the calendar MUST NOT be returned in Calendar/get and Calendar/query; it must behave as though it did not exist. The data is just used as part of Principal/getAvailability.

Calendar/changes

This is a standard “/changes” method as described in [@!RFC8620], Section 5.2.

Calendar/set

This is a standard “/set” method as described in [@!RFC8620], Section 5.3 but with the following additional request argument:

The “role” and “shareWith” properties may only be set by users that have the mayAdmin right. The value is shared across all users, although users without the mayAdmin right cannot see the value.

When modifying the shareWith property, the user cannot give a right to a principal if the principal did not already have that right and the user making the change also does not have that right. Any attempt to do so must be rejected with a forbidden SetError.

Users can subscribe or unsubscribe to a calendar by setting the “isSubscribed” property. The server MAY forbid users from subscribing to certain calendars even though they have permission to see them, rejecting the update with a forbidden SetError.

The “timeZone”, “includeInAvailability”, “defaultAlertsWithoutTime” and “defaultAlertsWithTime” properties are stored per-user if the calendar account’s “shareesActAs” capability is “self”, and may be set by any user who is subscribed to the calendar. Otherwise, these properties are shared, and may only be set by users that have the mayAdmin right.

The following properties may be set by anyone who is subscribed to the calendar and are all stored per-user:

These properties are initially inherited from the owner’s copy of the calendar, but if set by a sharee that user gets their own copy of the property; it does not change for any other principals. If the value of the property in the owner’s calendar changes after this, it does not overwrite the sharee’s value.

The following extra SetError types are defined:

For “destroy”:

Calendar Events

A CalendarEvent object contains information about an event, or recurring series of events, that takes place at a particular time. It is a JSEvent object, as defined in [@!I-D.ietf-calext-jscalendar], with the following additional properties:

CalendarEvent objects MUST NOT have a “method” property as this is only used when representing iTIP [@!RFC5546] scheduling messages, not events in a data store.

Additional JSCalendar properties

This document defines three new JSCalendar properties.

mayInviteSelf

Type: Boolean (default: false)

If true, any user that has access to the event may add themselves to it as a participant with the “attendee” role. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the master object.

mayInviteOthers

Type: Boolean (default: false)

If true, any current participant with the “attendee” role may add new participants with the “attendee” role to the event. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the master object.

hideAttendees

Type: Boolean (default: false)

If true, only the owners of the event may see the full set of participants. Other sharees of the event may only see the owners and themselves. This property MUST NOT be altered in the recurrenceOverrides; it may only be set on the master object.

Attachments

The Link object, as defined in [@!I-D.ietf-calext-jscalendar] Section 4.2.7, with a “rel” property equal to “enclosure” is used to represent attachments. Instead of mandating an “href” property, clients may set a “blobId” property instead to reference a blob of binary data in the account, as per [@!RFC8620] Section 6.

The server MUST translate this to an embedded data: URL [@!RFC2397] when sending the event to a system that cannot access the blob. Servers that support CalDAV access to the same data are recommended to expose these files as managed attachments [?@RFC8607].

Per-user properties

In shared calendars where the account’s “shareesActAs” capability is “self”, the following properties MUST be stored per-user:

The user may also modify these properties on a per-occurrence basis for recurring events; again, these MUST be stored per-user.

When writing only per-user properties, the “updated” property MUST also be stored just for that user. When fetching the “updated” property, the value to return is whichever is later of the per-user updated time or the updated time of the master event.

Recurring events

Events may recur, in which case they represent multiple occurrences or instances. The data store will either contain a single master event, containing a recurrence rule and/or recurrence overrides; or, a set of individual instances (when invited to specific occurrences only).

The client may ask the server to expand recurrences within a specific time range in “CalendarEvent/query”. This will generate synthetic ids representing individual instances in the requested time range. The client can fetch and update the objects using these ids and the server will make the appropriate changes to the master event. Synthetic ids do not appear in “CalendarEvent/changes” responses; only the ids of events as actually stored on the server.

If the user is invited to specific instances then later added to the master event, “CalendarEvent/changes” will show the ids of all the individual instances being destroyed and the id for the master event being created.

Updating for “this-and-future”

When editing a recurring event, you can either update the master event (affecting all instances unless overriden) or update an override for a specific occurrence. To update all occurrences from a specific point onwards, there are therefore two options: split the event, or update the master and override all occurrences before the split point back to their original values.

Splitting an event

If the event is not scheduled (has no participants), the simplest thing to do is to duplicate the event, modifying the recurrence rules of the original so it finishes before the split point, and the duplicate so it starts at the split point. As per JSCalendar [@!I-D.ietf-calext-jscalendar] Section 4.1.3, a “next” and “first” relation MUST be set on the new objects respectively.

Splitting an event however is problematic in the case of a scheduled event, because the iTIP messages generated make it appear like two unrelated changes, which can be confusing.

Updating the master and overriding previous

For scheduled events, a better approach is to avoid splitting and instead update the master event with the new property value for “this and future”, then create overrides for all occurrences before the split point to restore the property to its previous value. Indeed, this may be the only option the user has permission to do if not an owner of the event.

Clients may choose to skip creating the overrides if the old data is not important, for example if the “alerts” property is being updated, it is probably not important to create overrides for events in the past with the alerts that have already fired.

CalendarEvent/get

This is a standard “/get” method as described in [@!RFC8620], Section 5.1, with three extra arguments:

A CalendarEvent object is a JSEvent object so may have arbitrary properties. If the client makes a “CalendarEvent/get” call with a null or omitted “properties” argument, all properties defined on the JSEvent object in the store are returned, along with the “id”, “calendarIds”, and “isDraft” properties. The “utcStart” and “utcEnd” computed properties are only returned if explicitly requested. If either are requested, the “recurrenceOverrides” property MUST NOT be requested (recurrence overrides cannot be interpreted accurately with just the UTC times).

If specific properties are requested from the JSEvent and the property is not present on the object in the server’s store, the server SHOULD return the default value if known for that property.

A requested id may represent a single instance of a recurring event if the client asked the server to expand recurrences in “CalendarEvent/query”. In such a case, the server will resolve any overrides and set the appropriate “start” and “recurrenceId” properties on the CalendarEvent object returned to the client. The “recurrenceRule” and “recurrenceOverrides” properties MUST be returned as null if requested for such an event.

An event with the same uid/recurrenceId may appear in different accounts. Clients may coalesce the view of such events, but must be aware that the data may be different in the different accounts due to per-user properties, difference in permissions etc.

The “privacy” property of a JSEvent object allows the owner to override how sharees of the calendar see the event. If this is set to “private”, when a sharee fetches the event the server MUST only return the basic time and metadata properties of the JSEvent object as specified in [@!I-D.ietf-calext-jscalendar], Section 4.4.3. If set to “secret”, the server MUST behave as though the event does not exist for all users other than the owner.

This “hideAttendees” property of a JSEvent object allows the owner to reduce the visibility of sharees into the set of participants. If this is true, when a non-owner sharee fetches the event, the server MUST only return participants with the “owner” role or corresponding to the user’s participant identities.

CalendarEvent/changes

This is a standard “/changes” method as described in [@!RFC8620], Section 5.2.

CalendarEvent/set

This is a standard “/set” method as described in [@!RFC8620], Section 5.3, with the following extra argument:

For recurring events, an id may represent the master event or a specific instance. When the id for a specific instance is given, the server MUST process an update as an update to the recurrence override for that instance on the master event, and a destroy as removing just that instance.

Clients MUST NOT send an update/destroy to both the master event and a specific instance in a single “/set” request; the result of this is undefined.

Servers MUST enforce the user’s permissions as returned in the “myRights” property of the Calendar objects and reject changes with a forbidden SetError if not allowed.

The “privacy” property MUST NOT be set to anything other than “public” (the default) for events in a calendar that does not belong to the user (e.g. a shared team calendar). The server MUST reject this with an invalidProperties SetError.

The server MUST reject attempts to add events with a “participants” property where none of the participants correspond to one of the calendar’s participant identities with a forbidden SetError.

If omitted on create, the server MUST set the following properties to an appropriate value:

The “updated” property MUST be set to the current time by the server whenever an event is created or updated. If the client tries to set a value for this property it is not an error, but it MUST be overridden and replaced with the server’s time.

When updating an event, if all of:

The “created” property MUST NOT be updated after creation. The “method” property MUST NOT be set. Any attempt to do these is rejected with a standard invalidProperties SetError.

If “utcStart” is set, this is translated into a “start” property using the server’s current time zone information. It MUST NOT be set in addition to a “start” property and it cannot be set inside “recurrenceOverrides”; this MUST be rejected with an invalidProperties SetError.

Similarly, the “utcEnd” property is translated into a “duration” property if set. It MUST NOT be set in addition to a “duration” property and it cannot be set inside “recurrenceOverrides”; this MUST be rejected with an invalidProperties SetError.

The server does not automatically reset the “partipationStatus” or “expectReply” properties of a Participant when changing other event details. Clients should either be intelligent about whether the change necessitates resending RSVP requests, or ask the user whether to send them.

The server MAY enforce that all events have an owner, for example in team calendars. If the user tries to create an event without participants in such a calendar, the server MUST automatically add a participant with the “owner” role corresponding to one of the user’s ParticipantIdentities (see Section XXX).

When creating an event with participants, or adding participants to an event that previously did not have participants, the server MUST set the “replyTo” property of the event if not present. Clients SHOULD NOT set the replyTo property for events when the user adds participants; the server is better positioned to add all the methods it supports to receive replies.

Patching

The JMAP “/set” method allows you to update an object by sending a patch, rather than having to supply the whole object. When doing so, care must be taken if updating a property of a CalendarEvent where the value is itself a PatchObject, e.g. inside “localizations” or “recurrenceOverrides”. In particular, you cannot add a property with value null to the CalendarEvent using a direct patch on that property, as this is interpreted instead as a patch to remove the property. This is more easily understood with an example. Suppose you have a CalendarEvent object like so:

{
  "id": "123",
  "title": "FooBar team meeting",
  "start": "2018-01-08T09:00:00",
  "recurrenceRules": [{
   "@type": "RecurrenceRule",
   "frequency": "weekly"
  }],
  "replyTo": {
   "imip": "mailto:6489-4f14-a57f-c1@schedule.example.com"
  },
  "participants": {
   "dG9tQGZvb2Jhci5xlLmNvbQ": {
     "@type": "Participant",
     "name": "Tom",
     "email": "tom@foobar.example.com",
     "sendTo": {
       "imip": "mailto:6489-4f14-a57f-c1@calendar.example.com"
     },
     "participationStatus": "accepted",
     "roles": {
       "attendee": true
     }
   },
   "em9lQGZvb2GFtcGxlLmNvbQ": {
     "@type": "Participant",
     "name": "Zoe",
     "email": "zoe@foobar.example.com",
     "sendTo": {
       "imip": "mailto:zoe@foobar.example.com"
     },
     "participationStatus": "accepted",
     "roles": {
       "owner": true,
       "attendee": true,
       "chair": true
     }
   },
   "recurrenceOverrides": {
     "2018-03-08T09:00:00": {
       "start": "2018-03-08T10:00:00",
       "participants/dG9tQGZvb2Jhci5xlLmNvbQ/participationStatus":
                                                           "declined"
     }
   }
  }
}

In this example, Tom is normally going to the weekly meeting but has declined the occurrence on 2018-03-08, which starts an hour later than normal. Now, if Zoe too were to decline that meeting, she could update the event by just sending a patch like so:

[[ "CalendarEvent/set", {
  "accountId": "ue150411c",
  "update": {
    "123": {
      "recurrenceOverrides/2018-03-08T09:00:00/
          participants~1em9lQGZvb2GFtcGxlLmNvbQ~1participationStatus":
              "declined"
    }
  }
}, "0" ]]

This patches the “2018-03-08T09:00:00” PatchObject in recurrenceOverrides so that it ends up like this:

"recurrenceOverrides": {
  "2018-03-08T09:00:00": {
    "start": "2018-03-08T10:00:00",
    "participants/dG9tQGZvb2Jhci5xlLmNvbQ/participationStatus":
                                                       "declined",
    "participants/em9lQGZvb2GFtcGxlLmNvbQ/participationStatus":
                                                       "declined"
  }
}

Now if Tom were to change his mind and remove his declined status override (thus meaning he is attending, as inherited from the top-level event), he might remove his patch from the overrides like so:

[[ "CalendarEvent/set", {
  "accountId": "ue150411c",
  "update": {
    "123": {
      "recurrenceOverrides/2018-03-08T09:00:00/
          participants~1dG9tQGZvb2Jhci5xlLmNvbQ~1participationStatus": null
    }
  }
}, "0" ]]

However, if you instead want to remove Tom from this instance altogether, you could not send this patch:

[[ "CalendarEvent/set", {
  "accountId": "ue150411c",
  "update": {
    "123": {
      "recurrenceOverrides/2018-03-08T09:00:00/
          participants~1dG9tQGZvb2Jhci5xlLmNvbQ": null
    }
  }
}, "0" ]]

This would mean remove the “participants/dG9tQGZvb2Jhci5xlLmNvbQ” property at path “recurrenceOverrides” -> “2018-03-08T09:00:00” inside the object; but this doesn’t exist. We actually we want to add this property and make it map to null. The client must instead send the full object that contains the property mapping to null, like so:

[[ "CalendarEvent/set", {
  "accountId": "ue150411c",
  "update": {
    "123": {
      "recurrenceOverrides/2018-03-08T09:00:00": {
        "start": "2018-03-08T10:00:00",
        "participants/em9lQGZvb2GFtcGxlLmNvbQ/participationStatus":
                                                       "declined"
        "participants/dG9tQGZvb2Jhci5xlLmNvbQ": null
      }
    }
  }
}, "0" ]]

Sending invitations and responses

If “sendSchedulingMessages” is true, the server MUST send appropriate iTIP [@!RFC5546] scheduling messages after successfuly creating, updating or destroying a calendar event.

When determining which scheduling messages to send, the server must first establish whether it is the source of the event. The server is the source if it will receive messages sent to any of the methods specified in the “replyTo” property of the event.

Messages are only sent to participants with a “scheduleAgent” property set to “server” or omitted. If the effective “scheduleAgent” property is changed:

The server may send the scheduling message via any of the methods defined on the sendTo property of a participant (if the server is the source) or the replyTo property of the event (otherwise) that it supports. If no supported methods are available, the server MUST reject the change with a noSupportedScheduleMethods SetError.

If the server is the source of the event it MUST NOT send messages to any participant corresponding to a ParticipantIdentitity in that account (see Section XXX).

If sending via iMIP [@?RFC6047], the server MAY choose to only send updates it deems “essential” to avoid flooding the recipient’s email with changes they do not care about. For example, changes to the participationStatus of another participant, or changes to events solely in the past may be omitted.

REQUEST

When the server is the source for the event, a REQUEST message ([@!RFC5546], Section 3.2.2) is sent to all current participants if:

Note, if the only change is adding an additional instance (not generated by the event’s recurrence rule) to the recurrenceOverrides, this MAY be handled via sending an ADD message ([@!RFC5546], Section 3.2.4) for the single instance rather than a REQUEST message for the master. However, for interoperability reasons this is not recommended due to poor support in the wild for this type of message.

The server MUST ensure participants are only sent information about recurrence instances they are added to when sending scheduling messages for recurring events. If the participant is not invited to the master recurring event but only individual instances, scheduling messages MUST be sent for just those expanded occurrences individually. If a participant is invited to a recurring event, but removed via a recurrence override from a particular instance, any scheduling messages to this participant MUST return the instance as “excluded” (if it matches a recurrence rule for the event) or omit the instance entirely (otherwise).

If the event’s “hideAttendees” property is set to true, the recipient MUST be the only attendee in the message; all others are omitted.

CANCEL

When the server is the source for the event, a CANCEL message ([@!RFC5546], Section 3.2.5) is sent if:

In each of the latter 3 cases, the message is sent to all participants.

REPLY

When the server is not the source for the event, a REPLY message ([@!RFC5546], Section 3.2.3) is sent for any participant corresponding to one of the user’s ParticipantIdentitities in the account if:

A reply is not sent when deleting an event where the current status is “needs-action” as if a junk calendar event gets added by an automated system, the user MUST be able to delete the event without sending a reply.

CalendarEvent/copy

This is a standard “/copy” method as described in [@!RFC8620], Section 5.4.

CalendarEvent/query

This is a standard “/query” method as described in [@!RFC8620], Section 5.5, with two extra arguments:

If expandRecurrences is true, a separate id will be returned for each instance of a recurring event that matches the query. This synthetic id is opaque to the client, but allows the server to resolve the id + recurrence id for “/get” and “/set” operations. Otherwise, a single id will be returned for matching recurring events that represents the entire event.

There is no necessary correspondence between the ids of different instances of the same expanded event.

The following additional error may be returned instead of the “CalendarEvent/query” response:

cannotCalculateOccurrences: the server cannot expand a recurrence required to return the results for this query.

Filtering

A FilterCondition object has the following properties:

If expandRecurrences is true, all conditions must match against the same instance of a recurring event for the instance to match. If expandRecurrences is false, all conditions must match, but they may each match any instance of the event.

If zero properties are specified on the FilterCondition, the condition MUST always evaluate to true. If multiple properties are specified, ALL must apply for the condition to be true (it is equivalent to splitting the object into one-property conditions and making them all the child of an AND filter operator).

The exact semantics for matching String fields is deliberately not defined to allow for flexibility in indexing implementation, subject to the following:

Sorting

The following properties MUST be supported for sorting:

The following properties SHOULD be supported for sorting:

CalendarEvent/queryChanges

This is a standard “/queryChanges” method as described in [@!RFC8620], Section 5.6.

Examples

TODO: Add example of how to get event by uid: query uid=foo and backref. Return multiple with recurrenceId set (user invited to specific instances of recurring event).

Alerts

Alerts may be specified on events as described in [@!I-D.ietf-calext-jscalendar], Section 4.5.

Alerts MUST only be triggered for events in calendars where the user is subscribed and either the user owns the calendar or the calendar account’s “shareesActAs” capability is “self”.

When an alert with an “email” action is triggered, the server MUST send an email to the user to notify them of the event. The contents of the email is implementation specific. Clients MUST NOT perform an action for these alerts.

When an alert with a “display” action is triggered, clients SHOULD display an alert in a platform-appropriate manner to the user to remind them of the event. Clients with a full offline cache of events may choose to calculate when alerts should trigger locally. Alternatively, they can subscribe to push events from the server.

Default alerts

If the “useDefaultAlerts” property of an event is true, the alerts are taken from the “defaultAlertsWithTime” or “defaultAlertsWithoutTime” property of all Calendars the event is in, as described in Section XXX, rather than the “alerts” property of the CalendarEvent.

When using default alerts, the “alerts” property of the event is ignored except for the following:

Acknowledging an alert

To dismiss an alert, clients set the “acknowledged” property of the Alert object to the current date-time. If the alert was a calendar default, it may need to be added to the event at this point in order to acknowledge it. When other clients fetch the updated CalendarEvent they SHOULD automatically dismiss or suppress duplicate alerts (alerts with the same alert id that triggered on or before the “acknowledged” date-time) and alerts that have been removed from the event.

Setting the “acknowledged” property MUST NOT create a new recurrence override. For a recurring calendar object, the “acknowledged” property of the parent object MUST be updated, unless the alert is already overridden in the “recurrenceOverrides” property.

Snoozing an alert

Users may wish to dismiss an alert temporarily and have it come back after a specific period of time. To do this, clients MUST:

  1. Acknowledge the alert as described in Section XXX.
  2. Add a new alert to the event with an AbsoluteTrigger for the date-time the alert has been snoozed until. Add a “relatedTo” property to the new alert, setting the “parent” relation to point to the original alert. This MUST NOT create a new recurrence override; it is added to the same “alerts” property that contains the alert that was acknowledged in step 1.

When acknowledging a snoozed alert (i.e. one with a parent relatedTo pointing to the original alert), the client SHOULD delete the alert rather than setting the “acknowledged” property.

Push events

Servers that support the urn:ietf:params:jmap:calendars capability MUST support registering for the pseudo-type “CalendarAlert” in push subscriptions and event source connections, as described in [@!RFC8620], Sections 7.2 and 7.3.

If requested, a CalendarAlert notification will be pushed whenever an alert is triggered for the user. For Event Source connections, this notification is pushed as an event called “calendarAlert”.

A CalendarAlert object has the following properties:

Calendar Event Notifications

The CalendarEventNotification data type records changes made by external entities to events in calendars the user is subscribed to. Notifications are stored in the same Account as the CalendarEvent that was changed.

Notifications are only created by the server; users cannot create them directly. Clients SHOULD present the list of notifications to the user and allow them to dismiss them. To dismiss a notification you use a standard “/set” call to destroy it.

The server SHOULD create a CalendarEventNotification whenever an event is added, updated or destroyed by another user or due to receiving an iTIP [@?RFC5546] or other scheduling message in a calendar this user is subscribed to. The server SHOULD NOT create notifications for events implicitly deleted due to the containing calendar being deleted.

The CalendarEventNotification does not have any per-user data. A single instance may therefore be maintained on the server for all sharees of the notification. The server need only keep track of which users have yet to destroy the notification.

Auto-deletion of Notifications

The server MAY limit the maximum number of notifications it will store for a user. When the limit is reached, any new notification will cause the previously oldest notification to be automatically deleted.

The server MAY coalesce events if appropriate, or remove events that it deems are no longer relevant or after a certain period of time. The server SHOULD automatically destroy a notification about an event if the user updates or destroys that event (e.g. if the user sends an RSVP for the event).

Object Properties

The CalendarEventNotification object has the following properties:

To reduce data, if the change only affects a single instance of a recurring event, the server MAY set the event and eventPatch properties for the instance; the calendarEventId MUST still be for the master event.

CalendarEventNotification/get

This is a standard “/get” method as described in [@!RFC8620], Section 5.1.

CalendarEventNotification/changes

This is a standard “/changes” method as described in [@!RFC8620], Section 5.2.

CalendarEventNotification/set

This is a standard “/changes” method as described in [@!RFC8620], Section 5.3.

Only destroy is supported; any attempt to create/update MUST be rejected with a forbidden SetError.

CalendarEventNotification/query

This is a standard “/query” method as described in [@!RFC8620], Section 5.5.

Filtering

A FilterCondition object has the following properties:

Sorting

The “created” property MUST be supported for sorting.

CalendarEventNotification/queryChanges

This is a standard “/queryChanges” method as described in [@!RFC8620], Section 5.6.

Security Considerations

All security considerations of JMAP [@!RFC8620] and JSCalendar [@!I-D.ietf-calext-jscalendar] apply to this specification. Additional considerations specific to the data types and functionality introduced by this document are described in the following subsections.

Denial-of-service Expanding Recurrences

Recurrence rules can be crafted to occur as frequently as every second. Servers MUST be careful to not allow resources to be exhausted when expanding. Equally, rules can be generated that never create any occurrences at all. Servers MUST be careful to limit the work spent iterating in search of the next occurrence.

Privacy

TODO.

IANA Considerations

JMAP Capability Registration for “calendars”

IANA will register the “calendars” JMAP Capability as follows:

Capability Name: urn:ietf:params:jmap:calendars

Specification document: this document

Intended use: common

Change Controller: IETF

Security and privacy considerations: this document, Section XXX

JSCalendar Property Registrations

IANA will register the following additional properties in the JSCalendar Properties Registry.

id

Property Name: id

Property Type: Id

Property Context: JSEvent, JSTask

Intended Use: Reserved

calendarIds

Property Name: calendarIds

Property Type: Id[Boolean]

Property Context: JSEvent, JSTask

Intended Use: Reserved

isDraft

Property Name: isDraft

Property Type: Boolean

Property Context: JSEvent, JSTask

Intended Use: Reserved

utcStart

Property Name: utcStart

Property Type: UTCDateTime

Property Context: JSEvent, JSTask

Intended Use: Reserved

utcEnd

Property Name: utcEnd

Property Type: UTCDateTime

Property Context: JSEvent, JSTask

Intended Use: Reserved

mayInviteSelf

Property Name: mayInviteSelf

Property Type: Boolean (default: false)

Property Context: JSEvent, JSTask

Reference: This document, Section XXX.

Intended Use: Common

mayInviteOthers

Property Name: mayInviteOthers

Property Type: Boolean (default: false)

Property Context: JSEvent, JSTask

Reference: This document, Section XXX.

Intended Use: Common

hideAttendees

Property Name: hideAttendees

Property Type: Boolean (default: false)

Property Context: JSEvent, JSTask

Reference: This document, Section XXX.

Intended Use: Common