| Internet-Draft | JMAP Undelete | October 2026 |
| Coffey | Expires 30 April 2027 | [Page] |
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.¶
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).¶
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.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
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.¶
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 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:¶
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.¶
A DeletedItem object describes one destroyed object. It has the following 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.¶
| objectType | Properties |
|---|---|
| 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.¶
When "blobId" is not null, the blob holds the destroyed object in the following representation:¶
| objectType | Representation |
|---|---|
| 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.¶
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.¶
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.¶
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.¶
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:¶
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:¶
If no sort is given, the results are sorted by "deletedAt", newest first.¶
This is a standard "/queryChanges" method as described in Section 5.6 of [RFC8620].¶
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.¶
This method recreates the objects described by one or more DeletedItems. It takes the following arguments:¶
The response has the following arguments:¶
The following SetError types may be returned in "notRestored":¶
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.¶
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:¶
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.¶
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.¶
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.¶
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.¶
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.¶
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" ]]
¶
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.¶
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.¶
IANA is requested to register the "undelete" JMAP Capability as follows:¶
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.¶
This section is to be removed before publishing as an RFC.¶