Internet-Draft JMAP Undelete October 2026
Coffey Expires 30 April 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-coffey-jmap-undelete-00
Published:
Intended Status:
Standards Track
Expires:
Author:
J. Coffey
Coffey Labs LLC

JMAP Undelete: Recovering Destroyed Objects

Abstract

This document specifies an extension to the JSON Meta Application Protocol (JMAP) that lets a client find and restore objects that were permanently destroyed within a server-defined retention period. It defines a new capability, a DeletedItem data type that describes a destroyed object of any supported data type, and a DeletedItem/restore method that recreates it. Rules are given for Email, ContactCard, CalendarEvent, FileNode, and SieveScript objects. Servers can already keep destroyed data for a time. This extension gives clients an interoperable way to show what can be recovered, for how long, and to recover it.

About This Document

This note is to be removed before publishing as an RFC.

Discussion of this document takes place on the JMAP Working Group mailing list (jmap@ietf.org).

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 30 April 2027.

▲

Table of Contents

1. Introduction

Deleting data by mistake is common. A user empties the Trash mailbox, a filter removes the wrong messages, a client expunges a folder, or a synchronisation error destroys contacts or calendar events. Many servers keep destroyed data for some time so that an administrator can recover it, but there is no standard way for a client to see what is recoverable or to recover it. Each server that offers recovery to end users does so through its own interface.

This document defines that interface for JMAP [RFC8620]. When a server supports it, destroying an object of a supported data type leaves behind a DeletedItem record for a retention period. The DeletedItem describes the destroyed object, gives access to its content as a blob, and can be restored with the DeletedItem/restore method, which recreates the object in its original place where that is still possible. When the retention period ends, or when the user purges the DeletedItem, the object is gone for good.

The extension is generic: any data type registered in the "JMAP Data Types" registry [IANA-JMAP] can take part. This document gives specific rules for Email [RFC8621], ContactCard [RFC9610], CalendarEvent [I-D.ietf-jmap-calendars], FileNode [I-D.ietf-jmap-filenode], and SieveScript [RFC9661].

This extension is about mistakes. It is not a backup mechanism, since it does not protect against loss of the server, and it is not a legal hold, since a user can purge their own deleted items. See Section 8.

1.1. 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.

1.2. Terminology

Destroy:
To remove an object from an account permanently, so that it no longer appears in that data type's /get or /query results. This includes a JMAP /set destroy and removals made through any other protocol, such as an IMAP [RFC9051] EXPUNGE. Moving an object to a mailbox, calendar, or folder with a "trash" role is not destroying it.
Deleted item:
The record that a server keeps of a destroyed object, as described by a DeletedItem object (Section 3).
Container:
The object or objects that hold an object of a given data type: Mailboxes for an Email, AddressBooks for a ContactCard, Calendars for a CalendarEvent, the parent FileNode for a FileNode. A SieveScript has no container.
Retention period:
The length of time for which a deleted item stays restorable.
Purge:
To destroy a deleted item before its retention period ends.

1.3. Addition to the Capabilities Object

The capability "urn:ietf:params:jmap:undelete" is added to the Session object's "capabilities" property. Its value in the "capabilities" property of the Session object is an empty object.

An account supports this extension if this capability is present in the account's "accountCapabilities" property. Its value there is an object with the following properties:

objectTypes: String[]
The names of the data types for which this account keeps deleted items, from the "JMAP Data Types" registry or defined by a private extension. The list MUST NOT be empty. A data type appears here only if the server keeps deleted items for it at the time the Session object is fetched.
retentionPeriod: UnsignedInt|null
The number of seconds a newly destroyed object stays restorable, or null if the server does not keep deleted items for a fixed time (for example, because the period depends on the object). Clients use this to tell the user how long they have to change their mind. The "expiresAt" property of each DeletedItem is authoritative.
mayPurge: Boolean
Whether the user may purge deleted items in this account with DeletedItem/set (Section 3.8).

2. Keeping Deleted Items

While an account advertises this capability, a server MUST create a DeletedItem whenever an object of a data type listed in "objectTypes" is destroyed in that account, whatever protocol or process destroyed it. This includes, for example, an Email destroyed with Email/set, expunged over IMAP, deleted over POP3, removed by the automatic emptying of a Trash mailbox, or destroyed because its only Mailbox was destroyed with "onDestroyRemoveEmails" (Section 2.5 of [RFC8621]).

A server MUST NOT create a DeletedItem:

Modifying an object, including removing it from some but not all of its containers, does not create a DeletedItem. This extension recovers destroyed objects. It does not recover earlier versions of objects that still exist.

When an account stops advertising a data type in "objectTypes", or stops advertising the capability, deleted items already kept SHOULD remain restorable until their own "expiresAt" time. A server MUST NOT shorten the "expiresAt" of an existing DeletedItem because its retention settings changed. A user who was told that an object could be recovered until a given time should be able to rely on it.

3. DeletedItem

A DeletedItem object describes one destroyed object. It has the following properties:

id: Id (immutable; server-set)
The id of the DeletedItem.
objectType: String (immutable; server-set)
The name of the data type of the destroyed object, as registered in the "JMAP Data Types" registry, for example, "Email". The naming follows the ShareNotification object of [RFC9670].
objectId: Id (immutable; server-set)
The id the destroyed object had. Clients can use this to offer "undo" for an object they have just destroyed (see Section 3.6).
deletedAt: UTCDate (immutable; server-set)
When the object was destroyed.
expiresAt: UTCDate|null (immutable; server-set)
When the deleted item stops being restorable, or null if the server keeps it until it is purged. After this time the server MUST NOT restore it.
size: UnsignedInt (immutable; server-set)
The size, in octets, of the blob referenced by "blobId".
blobId: Id|null (immutable; server-set)
The id of a blob holding the destroyed object's content in the representation given in Section 3.2, or null if the server does not offer one for this data type. The blob can be downloaded without restoring the object, for example to preview it.
snapshot: Object (immutable; server-set)
Some of the destroyed object's properties as they were when it was destroyed, with the names and values the object's own /get method would have returned. The server MUST include the properties listed for the data type in Section 3.1 where the object had them, and MAY include others. The "id" property is not included, because it is given by "objectId".

3.1. Snapshot Properties

The following properties MUST appear in "snapshot" for the data types defined so far. They give a client enough to show a useful list ("what was it, and where was it") without restoring anything.

Table 1: Required Snapshot Properties
objectType Properties
Email mailboxIds, keywords, receivedAt, size, subject, from, preview
ContactCard addressBookIds, name, kind
CalendarEvent calendarIds, title, start, duration, timeZone
FileNode parentId, name, type, size
SieveScript name, isActive

A specification that adds a data type to this extension SHOULD list its required snapshot properties in the same way.

3.2. Blob Representations

When "blobId" is not null, the blob holds the destroyed object in the following representation:

Table 2: Deleted Item Blob Representations
objectType Representation
Email The message as stored, as the Email's own "blobId" referenced it (message/rfc822)
ContactCard The JSContact Card [RFC9553] (application/jscontact+json)
CalendarEvent The JSCalendar Event [RFC8984] (application/jscalendar+json)
FileNode The file's content, as the FileNode's own "blobId" referenced it; a FileNode with no content (a folder) has null
SieveScript The script, as the SieveScript's own "blobId" referenced it (application/sieve)

A server MAY return the same blobId the destroyed object had. The blob MUST remain downloadable for as long as the DeletedItem exists.

3.3. Access in Shared Accounts

In an account the user owns, every DeletedItem is visible to them. In an account shared with the user [RFC9670], a DeletedItem MUST be visible only if, when the object was destroyed, the user had the right to read it in at least one of its containers. A server MAY limit visibility further, for example to the account's owner only.

A DeletedItem that is not visible to the user MUST be treated as if it did not exist by every method in this document.

3.4. DeletedItem/get

This is a standard "/get" method as described in Section 5.1 of [RFC8620].

A DeletedItem past its "expiresAt" time MUST be returned in "notFound", even if the server has not yet removed it.

3.5. DeletedItem/changes

This is a standard "/changes" method as described in Section 5.2 of [RFC8620]. A DeletedItem that is restored, purged, or expires is reported as destroyed. Since DeletedItem objects are immutable, they are never reported as updated.

3.6. DeletedItem/query

This is a standard "/query" method as described in Section 5.5 of [RFC8620].

A FilterCondition object has the following properties, any of which may be omitted:

objectType: String
The "objectType" property must be equal to this.
objectIds: Id[]
The "objectId" property must be in this list.
deletedBefore: UTCDate
The "deletedAt" property must be before this date-time.
deletedAfter: UTCDate
The "deletedAt" property must be the same as or after this date-time.
inContainer: Id
The destroyed object must have been in this container: its snapshot's "mailboxIds", "addressBookIds", or "calendarIds" must contain this id, or its "parentId" must equal it. The container does not have to exist any more.
text: String
Looks for the text in the string values of the snapshot. For an Email, this includes the subject and the names and addresses of the "from" property. A server MAY also search the content of the blob.

If more than one property is given, the deleted item must match all of them.

The following values for the "property" field of a Comparator object MUST be supported for sorting:

  • "deletedAt"
  • "expiresAt"
  • "size"

If no sort is given, the results are sorted by "deletedAt", newest first.

3.7. DeletedItem/queryChanges

This is a standard "/queryChanges" method as described in Section 5.6 of [RFC8620].

3.8. DeletedItem/set

This is a standard "/set" method as described in Section 5.3 of [RFC8620], used only to purge deleted items. DeletedItem objects cannot be created or updated with this method: the server MUST reject every create and every update with a "forbidden" SetError.

Destroying a DeletedItem purges it. The server MUST then make the object unrecoverable through this extension and MUST release the blob as for any destroyed object. If the account's "mayPurge" is false, or the user does not have the right to remove the object from the containers it was in, the server MUST reject the destroy with a "forbidden" SetError.

3.9. DeletedItem/restore

This method recreates the objects described by one or more DeletedItems. It takes the following arguments:

accountId: Id
The id of the account to use.
ids: Id[]
The ids of the DeletedItems to restore. The number of ids MUST NOT exceed the "maxObjectsInSet" limit of the core capability.
ifInState: String|null
As for /set (Section 5.3 of [RFC8620]), against the DeletedItem state.

The response has the following arguments:

accountId: Id
The id of the account used for the call.
oldState: String|null
The DeletedItem state before the call.
newState: String
The DeletedItem state after the call.
restored: Id[Object]|null
A map from the id of each DeletedItem that was restored to an object with two properties: "objectType", the data type of the object, and "object", an object holding the "id" of the recreated object and any other properties whose values differ from the snapshot (for example, the containers it was restored to, or a new name). This is null if nothing was restored.
notRestored: Id[SetError]|null
A map from the id of each DeletedItem that could not be restored to a SetError, or null if there were none.

The following SetError types may be returned in "notRestored":

  • "notFound": The DeletedItem does not exist, is not visible to the user, or is past its "expiresAt" time.
  • "forbidden": The user may not create the object in any container it could be restored to.
  • "overQuota": Restoring the object would take the account past a quota.
  • "tooLarge": The object is larger than the server now accepts for that data type.

Restoring is atomic for each DeletedItem: either the object is recreated and the DeletedItem is removed, or neither happens. If the same id appears more than once in "ids", or a restore of the same DeletedItem is already in progress, the object is restored once.

A restored object is a new object of its data type, created by the server. It gets a new id, which may or may not equal "objectId". The data type's state changes as for any created object, so clients learn about it through the usual /changes calls and push notifications. A client MUST NOT assume that the restored object has its old id.

A restored object MUST have the property values in its snapshot, and the server SHOULD restore the other properties it had, except where the following sections say otherwise. Server-set properties (such as an Email's "threadId") are set as for a new object.

3.9.1. Containers

An object is restored to the containers it was in when it was destroyed, leaving out any that no longer exist or that the user may not add objects to. If none remain, it is restored to the default container for its type:

  • Email: the Mailbox with the "inbox" role.
  • ContactCard: the AddressBook with "isDefault" true.
  • CalendarEvent: the Calendar with "isDefault" true.
  • FileNode: the root of the file tree.

If the user may not add objects to the default container either, the restore fails with "forbidden".

An Email that was only in the Mailbox with the "trash" role when it was destroyed is restored there. An Email that was in the Trash and other Mailboxes is restored to the other Mailboxes only. This follows what the user most likely intends: the message was emptied from the Trash, or deleted from a folder it was filed in.

3.9.2. Email

A restored Email MUST have the "keywords" it had when destroyed, including "$seen", and the same "receivedAt". It joins a thread according to the server's normal threading rules, so it will usually rejoin its earlier conversation. A restored Email is not new mail: the server MUST NOT run delivery-time processing (such as Sieve [RFC5228] scripts, spam filtering, or vacation responses) on it.

3.9.3. FileNode and SieveScript

If a FileNode's name is already in use in the folder it is restored to, or a SieveScript's name is already in use, the server MUST choose a new name for the restored object and report it in the "object" of the "restored" entry. How the new name is formed is up to the server.

A folder FileNode is restored without its content. Its children that were destroyed with it have DeletedItems of their own, and a client restores them by restoring each one, parents first. A client can find them with the "inContainer" filter.

A restored SieveScript MUST NOT be active, even if it was active when destroyed. Activating a script changes how incoming mail is handled, so the user must do it on purpose.

4. Expiry

When a DeletedItem's "expiresAt" time passes, the server MUST stop offering it through every method in this document at once, and SHOULD destroy it and release its blob soon after. It is reported as destroyed by DeletedItem/changes no later than the next state change after its expiry.

5. Quotas

A server decides whether deleted items count toward an account's quotas. Counting them is NOT RECOMMENDED, since a user who deletes data to free space would otherwise gain nothing until the retention period ends. A server that does count them and supports JMAP Quotas [RFC9425] SHOULD include "DeletedItem" in the "types" of each Quota they count toward.

A restored object counts toward quotas as a new object would, whether or not the DeletedItem did.

6. Examples

A client shows a "Recently deleted" view of email:

[[ "DeletedItem/query", {
  "accountId": "u1",
  "filter": { "objectType": "Email" },
  "sort": [{ "property": "deletedAt", "isAscending": false }],
  "limit": 50
}, "0" ],
[ "DeletedItem/get", {
  "accountId": "u1",
  "#ids": {
    "resultOf": "0",
    "name": "DeletedItem/query",
    "path": "/ids"
  }
}, "1" ]]

The server responds:

[[ "DeletedItem/query", {
  "accountId": "u1",
  "queryState": "q73",
  "canCalculateChanges": true,
  "position": 0,
  "ids": [ "d42" ]
}, "0" ],
[ "DeletedItem/get", {
  "accountId": "u1",
  "state": "s9",
  "list": [{
    "id": "d42",
    "objectType": "Email",
    "objectId": "M8f2",
    "deletedAt": "2026-10-26T18:03:11Z",
    "expiresAt": "2026-11-25T18:03:11Z",
    "size": 48211,
    "blobId": "G3a91",
    "snapshot": {
      "mailboxIds": { "mb-inbox": true, "mb-invoices": true },
      "keywords": { "$seen": true, "$flagged": true },
      "receivedAt": "2026-10-20T09:15:00Z",
      "size": 48211,
      "subject": "Invoice 2026-118",
      "from": [{
        "name": "Accounts",
        "email": "accounts@example.com"
      }],
      "preview": "Please find attached the invoice for October..."
    }
  }],
  "notFound": []
}, "1" ]]

The user restores it. The "invoices" Mailbox has since been destroyed, so the message goes back to the Inbox only:

[[ "DeletedItem/restore", {
  "accountId": "u1",
  "ids": [ "d42" ]
}, "0" ]]
[[ "DeletedItem/restore", {
  "accountId": "u1",
  "oldState": "s9",
  "newState": "s10",
  "restored": {
    "d42": {
      "objectType": "Email",
      "object": {
        "id": "M9c07",
        "mailboxIds": { "mb-inbox": true }
      }
    }
  },
  "notRestored": null
}, "0" ]]

A client that has just destroyed an Email with id "M8f2" offers "Undo" by looking up its DeletedItem:

[[ "DeletedItem/query", {
  "accountId": "u1",
  "filter": { "objectIds": [ "M8f2" ] }
}, "0" ]]

7. Security Considerations

All security considerations of JMAP [RFC8620] and of the specifications defining each supported data type apply.

Deleted items extend the time for which data stays reachable through the account. Anyone who gains access to an account, through stolen credentials or a delegated session, can read recently destroyed data as well as current data. Deleting something sensitive does not protect it from such a person until it has been purged. Clients SHOULD make purging easy to find.

In shared accounts, deleted items can disclose what another user deleted and when. The visibility rule in Section 3.3 limits this to people who could already read the object. A server that cannot track this reliably SHOULD show deleted items only to the account's owner.

Restoring an object recreates content that may have been destroyed because it was harmful, such as a phishing message removed by an administrator or by a filter. Servers MAY refuse to keep deleted items for objects removed for such reasons, and MAY refuse to restore them with "forbidden".

Restoring a SieveScript must not change how incoming mail is handled without the user's deliberate action. This is why a restored script is always inactive (Section 3.9.3).

DeletedItem/restore can create many objects in one call. Servers apply the "maxObjectsInSet" limit and their normal rate limits to it, as they would to /set.

8. Privacy Considerations

Users expect deletion to remove data. Keeping deleted data, even for a short time, has to be visible to the user and under their control. This extension does both: "retentionPeriod" and "expiresAt" say how long data is kept, and DeletedItem/set lets the user purge it at once when "mayPurge" is true. Servers SHOULD set "mayPurge" to true for the account's owner, so that a user can make a deletion final.

Operators may have legal or contractual reasons to keep data longer than the user can see it, such as a litigation hold. Such retention is outside the scope of this extension. A server that keeps data after a purge or after expiry MUST NOT offer it through this extension, and operators are expected to tell users of such policies through other means.

The "snapshot" and the blob hold the same personal data as the destroyed object. Data protection rules that apply to the object apply equally to them.

9. IANA Considerations

9.1. JMAP Capability Registration for "undelete"

IANA is requested to register the "undelete" JMAP Capability as follows:

Capability Name:
urn:ietf:params:jmap:undelete
Specification document:
this document
Intended use:
common
Change Controller:
IETF
Security and privacy considerations:
this document, Section 7 and Section 8

9.2. JMAP Data Type Registration for "DeletedItem"

IANA is requested to register "DeletedItem" in the "JMAP Data Types" registry as follows:

Type Name:
DeletedItem
Can Reference Blobs:
Yes
Can Use for State Change:
Yes
Capability:
urn:ietf:params:jmap:undelete
Reference:
this document

10. Implementation Status

This section is to be removed before publishing as an RFC.

This section records the status of known implementations of the protocol defined by this specification at the time of posting of this Internet-Draft, and is based on a proposal described in [RFC7942].

inbuxa server, an open-source (AGPL-3.0) mail and collaboration server from Coffey Labs, keeps and restores deleted Email, ContactCard, CalendarEvent, FileNode, and SieveScript objects with the restoration rules described here, through its own interface. The JMAP interface in this document is not implemented yet. An implementation is planned.

11. References

11.1. Normative References

[I-D.ietf-jmap-calendars]
Jenkins, N. and M. Douglass, "JSON Meta Application Protocol (JMAP) for Calendars", Work in Progress, Internet-Draft, draft-ietf-jmap-calendars-33, , <https://datatracker.ietf.org/doc/html/draft-ietf-jmap-calendars-33>.
[I-D.ietf-jmap-filenode]
Gondwana, B., "JMAP File Storage extension", Work in Progress, Internet-Draft, draft-ietf-jmap-filenode-19, , <https://datatracker.ietf.org/doc/html/draft-ietf-jmap-filenode-19>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8620]
Jenkins, N. and C. Newman, "The JSON Meta Application Protocol (JMAP)", RFC 8620, DOI 10.17487/RFC8620, , <https://www.rfc-editor.org/info/rfc8620>.
[RFC8621]
Jenkins, N. and C. Newman, "The JSON Meta Application Protocol (JMAP) for Mail", RFC 8621, DOI 10.17487/RFC8621, , <https://www.rfc-editor.org/info/rfc8621>.
[RFC8984]
Jenkins, N. and R. Stepanek, "JSCalendar: A JSON Representation of Calendar Data", RFC 8984, DOI 10.17487/RFC8984, , <https://www.rfc-editor.org/info/rfc8984>.
[RFC9553]
Stepanek, R. and M. Loffredo, "JSContact: A JSON Representation of Contact Data", RFC 9553, DOI 10.17487/RFC9553, , <https://www.rfc-editor.org/info/rfc9553>.
[RFC9610]
Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) for Contacts", RFC 9610, DOI 10.17487/RFC9610, , <https://www.rfc-editor.org/info/rfc9610>.
[RFC9661]
Murchison, K., "The JSON Meta Application Protocol (JMAP) for Sieve Scripts", RFC 9661, DOI 10.17487/RFC9661, , <https://www.rfc-editor.org/info/rfc9661>.
[RFC9670]
Jenkins, N., Ed., "JSON Meta Application Protocol (JMAP) Sharing", RFC 9670, DOI 10.17487/RFC9670, , <https://www.rfc-editor.org/info/rfc9670>.

11.2. Informative References

[IANA-JMAP]
IANA, "JSON Meta Application Protocol (JMAP)", <https://www.iana.org/assignments/jmap>.
[RFC5228]
Guenther, P., Ed. and T. Showalter, Ed., "Sieve: An Email Filtering Language", RFC 5228, DOI 10.17487/RFC5228, , <https://www.rfc-editor.org/info/rfc5228>.
[RFC7942]
Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, , <https://www.rfc-editor.org/info/rfc7942>.
[RFC9051]
Melnikov, A., Ed. and B. Leiba, Ed., "Internet Message Access Protocol (IMAP) - Version 4rev2", RFC 9051, DOI 10.17487/RFC9051, , <https://www.rfc-editor.org/info/rfc9051>.
[RFC9425]
Cordier, R., Ed., "JSON Meta Application Protocol (JMAP) for Quotas", RFC 9425, DOI 10.17487/RFC9425, , <https://www.rfc-editor.org/info/rfc9425>.

Appendix A. Open Issues

This section is to be removed before publishing as an RFC.

Author's Address

John Coffey
Coffey Labs LLC
Arizona
United States of America