<?xml version='1.0' encoding='utf-8'?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" version="3" category="std" ipr="trust200902" submissionType="IETF" consensus="true" docName="draft-coffey-jmap-undelete-00" tocInclude="true" sortRefs="true" symRefs="true">
  <front>
    <title abbrev="JMAP Undelete">JMAP Undelete: Recovering Destroyed Objects</title>
    <seriesInfo name="Internet-Draft" value="draft-coffey-jmap-undelete-00"/>
    <author fullname="John Coffey" initials="J." surname="Coffey">
      <organization>Coffey Labs LLC</organization>
      <address>
        <postal>
          <region>Arizona</region>
          <country>United States of America</country>
        </postal>
        <email>john@coffeylabs.org</email>
        <uri>https://coffeylabs.org</uri>
      </address>
    </author>
    <date year="2026" month="October" day="27"/>
    <area>Applications and Real-Time</area>
    <workgroup>Network Working Group</workgroup>
    <keyword>JMAP</keyword>
    <keyword>undelete</keyword>
    <keyword>retention</keyword>
    <keyword>recovery</keyword>
    <abstract>
      <t>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.</t>
    </abstract>
    <note removeInRFC="true">
      <name>About This Document</name>
      <t>Discussion of this document takes place on the JMAP Working Group
      mailing list (jmap@ietf.org).</t>
    </note>
  </front>
  <middle>
    <section anchor="intro">
      <name>Introduction</name>
      <t>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.</t>
      <t>This document defines that interface for JMAP <xref target="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.</t>
      <t>The extension is generic: any data type registered in the "JMAP
      Data Types" registry <xref target="IANA-JMAP"/> can take part. This
      document gives specific rules for Email <xref target="RFC8621"/>,
      ContactCard <xref target="RFC9610"/>, CalendarEvent <xref target="I-D.ietf-jmap-calendars"/>, FileNode <xref target="I-D.ietf-jmap-filenode"/>, and SieveScript <xref target="RFC9661"/>.</t>
      <t>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
      <xref target="privacy"/>.</t>
      <section anchor="conventions">
        <name>Notational Conventions</name>
        <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
        "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>", "<bcp14>SHALL
        NOT</bcp14>", "<bcp14>SHOULD</bcp14>", "<bcp14>SHOULD NOT</bcp14>",
        "<bcp14>RECOMMENDED</bcp14>", "<bcp14>NOT RECOMMENDED</bcp14>",
        "<bcp14>MAY</bcp14>", and "<bcp14>OPTIONAL</bcp14>" in this document
        are to be interpreted as described in BCP&#160;14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they
        appear in all capitals, as shown here.</t>
        <t>Type signatures, examples, and property descriptions in this
        document follow the conventions established in <xref target="RFC8620" section="1.1"/>. Data types defined in the core
        specification are also used in this document.</t>
      </section>
      <section anchor="terminology">
        <name>Terminology</name>
        <dl newline="false">
          <dt>Destroy:</dt>
          <dd>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 <xref target="RFC9051"/> EXPUNGE.
          Moving an object to a mailbox, calendar, or folder with a "trash"
          role is not destroying it.</dd>
          <dt>Deleted item:</dt>
          <dd>The record that a server keeps of a destroyed object, as
          described by a DeletedItem object (<xref target="deleteditem"/>).</dd>
          <dt>Container:</dt>
          <dd>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.</dd>
          <dt>Retention period:</dt>
          <dd>The length of time for which a deleted item stays
          restorable.</dd>
          <dt>Purge:</dt>
          <dd>To destroy a deleted item before its retention period ends.</dd>
        </dl>
      </section>
      <section anchor="capability">
        <name>Addition to the Capabilities Object</name>
        <t>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.</t>
        <t>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:</t>
        <dl newline="true">
          <dt>objectTypes: String[]</dt>
          <dd>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 <bcp14>MUST NOT</bcp14> be empty. A
          data type appears here only if the server keeps deleted items for
          it at the time the Session object is fetched.</dd>
          <dt>retentionPeriod: UnsignedInt|null</dt>
          <dd>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.</dd>
          <dt>mayPurge: Boolean</dt>
          <dd>Whether the user may purge deleted items in this account with
          DeletedItem/set (<xref target="deleteditem-set"/>).</dd>
        </dl>
      </section>
    </section>
    <section anchor="retention">
      <name>Keeping Deleted Items</name>
      <t>While an account advertises this capability, a server
      <bcp14>MUST</bcp14> 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" (<xref target="RFC8621" section="2.5"/>).</t>
      <t>A server <bcp14>MUST NOT</bcp14> create a DeletedItem:</t>
      <ul>
        <li>for an object that never existed in the account, such as a
        message rejected before delivery;</li>
        <li>when a DeletedItem is itself purged or expires; or</li>
        <li>when an object is moved between accounts with a /copy method
        that has "onSuccessDestroyOriginal" set to true, because the object
        still exists in the destination account.</li>
      </ul>
      <t>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.</t>
      <t>When an account stops advertising a data type in "objectTypes",
      or stops advertising the capability, deleted items already kept
      <bcp14>SHOULD</bcp14> remain restorable until their own "expiresAt"
      time. A server <bcp14>MUST NOT</bcp14> 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.</t>
    </section>
    <section anchor="deleteditem">
      <name>DeletedItem</name>
      <t>A DeletedItem object describes one destroyed object. It has the
      following properties:</t>
      <dl newline="true">
        <dt>id: Id (immutable; server-set)</dt>
        <dd>The id of the DeletedItem.</dd>
        <dt>objectType: String (immutable; server-set)</dt>
        <dd>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 <xref target="RFC9670"/>.</dd>
        <dt>objectId: Id (immutable; server-set)</dt>
        <dd>The id the destroyed object had. Clients can use this to offer
        "undo" for an object they have just destroyed (see <xref target="deleteditem-query"/>).</dd>
        <dt>deletedAt: UTCDate (immutable; server-set)</dt>
        <dd>When the object was destroyed.</dd>
        <dt>expiresAt: UTCDate|null (immutable; server-set)</dt>
        <dd>When the deleted item stops being restorable, or null if the
        server keeps it until it is purged. After this time the server
        <bcp14>MUST NOT</bcp14> restore it.</dd>
        <dt>size: UnsignedInt (immutable; server-set)</dt>
        <dd>The size, in octets, of the blob referenced by "blobId".</dd>
        <dt>blobId: Id|null (immutable; server-set)</dt>
        <dd>The id of a blob holding the destroyed object's content in the
        representation given in <xref target="blobs"/>, 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.</dd>
        <dt>snapshot: Object (immutable; server-set)</dt>
        <dd>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 <bcp14>MUST</bcp14>
        include the properties listed for the data type in <xref target="snapshot"/> where the object had them, and
        <bcp14>MAY</bcp14> include others. The "id" property is not
        included, because it is given by "objectId".</dd>
      </dl>
      <section anchor="snapshot">
        <name>Snapshot Properties</name>
        <t>The following properties <bcp14>MUST</bcp14> 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.</t>
        <table anchor="snapshot-table">
          <name>Required Snapshot Properties</name>
          <thead>
            <tr>
              <th>objectType</th>
              <th>Properties</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Email</td>
              <td>mailboxIds, keywords, receivedAt, size,
            subject, from, preview</td>
            </tr>
            <tr>
              <td>ContactCard</td>
              <td>addressBookIds, name, kind</td>
            </tr>
            <tr>
              <td>CalendarEvent</td>
              <td>calendarIds, title, start,
            duration, timeZone</td>
            </tr>
            <tr>
              <td>FileNode</td>
              <td>parentId, name, type, size</td>
            </tr>
            <tr>
              <td>SieveScript</td>
              <td>name, isActive</td>
            </tr>
          </tbody>
        </table>
        <t>A specification that adds a data type to this extension
        <bcp14>SHOULD</bcp14> list its required snapshot properties in the
        same way.</t>
      </section>
      <section anchor="blobs">
        <name>Blob Representations</name>
        <t>When "blobId" is not null, the blob holds the destroyed object in
        the following representation:</t>
        <table anchor="blob-table">
          <name>Deleted Item Blob Representations</name>
          <thead>
            <tr>
              <th>objectType</th>
              <th>Representation</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Email</td>
              <td>The message as stored, as the Email's own
            "blobId" referenced it (message/rfc822)</td>
            </tr>
            <tr>
              <td>ContactCard</td>
              <td>The JSContact Card <xref target="RFC9553"/> (application/jscontact+json)</td>
            </tr>
            <tr>
              <td>CalendarEvent</td>
              <td>The JSCalendar Event <xref target="RFC8984"/> (application/jscalendar+json)</td>
            </tr>
            <tr>
              <td>FileNode</td>
              <td>The file's content, as the FileNode's
            own "blobId" referenced it; a FileNode with no content (a
            folder) has null</td>
            </tr>
            <tr>
              <td>SieveScript</td>
              <td>The script, as the SieveScript's
            own "blobId" referenced it (application/sieve)</td>
            </tr>
          </tbody>
        </table>
        <t>A server <bcp14>MAY</bcp14> return the same blobId the destroyed
        object had. The blob <bcp14>MUST</bcp14> remain downloadable for as
        long as the DeletedItem exists.</t>
      </section>
      <section anchor="access">
        <name>Access in Shared Accounts</name>
        <t>In an account the user owns, every DeletedItem is visible to
        them. In an account shared with the user <xref target="RFC9670"/>,
        a DeletedItem <bcp14>MUST</bcp14> 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 <bcp14>MAY</bcp14> limit
        visibility further, for example to the account's owner only.</t>
        <t>A DeletedItem that is not visible to the user <bcp14>MUST</bcp14>
        be treated as if it did not exist by every method in this
        document.</t>
      </section>
      <section anchor="deleteditem-get">
        <name>DeletedItem/get</name>
        <t>This is a standard "/get" method as described in <xref target="RFC8620" section="5.1"/>.</t>
        <t>A DeletedItem past its "expiresAt" time <bcp14>MUST</bcp14> be
        returned in "notFound", even if the server has not yet removed
        it.</t>
      </section>
      <section anchor="deleteditem-changes">
        <name>DeletedItem/changes</name>
        <t>This is a standard "/changes" method as described in <xref target="RFC8620" section="5.2"/>. A DeletedItem that is restored,
        purged, or expires is reported as destroyed. Since DeletedItem
        objects are immutable, they are never reported as updated.</t>
      </section>
      <section anchor="deleteditem-query">
        <name>DeletedItem/query</name>
        <t>This is a standard "/query" method as described in <xref target="RFC8620" section="5.5"/>.</t>
        <t>A <strong>FilterCondition</strong> object has the following
        properties, any of which may be omitted:</t>
        <dl newline="true">
          <dt>objectType: String</dt>
          <dd>The "objectType" property must be equal to this.</dd>
          <dt>objectIds: Id[]</dt>
          <dd>The "objectId" property must be in this list.</dd>
          <dt>deletedBefore: UTCDate</dt>
          <dd>The "deletedAt" property must be before this date-time.</dd>
          <dt>deletedAfter: UTCDate</dt>
          <dd>The "deletedAt" property must be the same as or after this
          date-time.</dd>
          <dt>inContainer: Id</dt>
          <dd>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.</dd>
          <dt>text: String</dt>
          <dd>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 <bcp14>MAY</bcp14> also search the
          content of the blob.</dd>
        </dl>
        <t>If more than one property is given, the deleted item must match
        all of them.</t>
        <t>The following values for the "property" field of a Comparator
        object <bcp14>MUST</bcp14> be supported for sorting:</t>
        <ul>
          <li>"deletedAt"</li>
          <li>"expiresAt"</li>
          <li>"size"</li>
        </ul>
        <t>If no sort is given, the results are sorted by "deletedAt",
        newest first.</t>
      </section>
      <section anchor="deleteditem-querychanges">
        <name>DeletedItem/queryChanges</name>
        <t>This is a standard "/queryChanges" method as described in <xref target="RFC8620" section="5.6"/>.</t>
      </section>
      <section anchor="deleteditem-set">
        <name>DeletedItem/set</name>
        <t>This is a standard "/set" method as described in <xref target="RFC8620" section="5.3"/>, used only to purge deleted items.
        DeletedItem objects cannot be created or updated with this method:
        the server <bcp14>MUST</bcp14> reject every create and every update
        with a "forbidden" SetError.</t>
        <t>Destroying a DeletedItem purges it. The server
        <bcp14>MUST</bcp14> then make the object unrecoverable through this
        extension and <bcp14>MUST</bcp14> 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 <bcp14>MUST</bcp14> reject the destroy with a
        "forbidden" SetError.</t>
      </section>
      <section anchor="deleteditem-restore">
        <name>DeletedItem/restore</name>
        <t>This method recreates the objects described by one or more
        DeletedItems. It takes the following arguments:</t>
        <dl newline="true">
          <dt>accountId: Id</dt>
          <dd>The id of the account to use.</dd>
          <dt>ids: Id[]</dt>
          <dd>The ids of the DeletedItems to restore. The number of ids
          <bcp14>MUST NOT</bcp14> exceed the "maxObjectsInSet" limit of the
          core capability.</dd>
          <dt>ifInState: String|null</dt>
          <dd>As for /set (<xref target="RFC8620" section="5.3"/>), against
          the DeletedItem state.</dd>
        </dl>
        <t>The response has the following arguments:</t>
        <dl newline="true">
          <dt>accountId: Id</dt>
          <dd>The id of the account used for the call.</dd>
          <dt>oldState: String|null</dt>
          <dd>The DeletedItem state before the call.</dd>
          <dt>newState: String</dt>
          <dd>The DeletedItem state after the call.</dd>
          <dt>restored: Id[Object]|null</dt>
          <dd>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.</dd>
          <dt>notRestored: Id[SetError]|null</dt>
          <dd>A map from the id of each DeletedItem that could not be
          restored to a SetError, or null if there were none.</dd>
        </dl>
        <t>The following SetError types may be returned in
        "notRestored":</t>
        <ul>
          <li>"notFound": The DeletedItem does not exist, is not visible to
          the user, or is past its "expiresAt" time.</li>
          <li>"forbidden": The user may not create the object in any
          container it could be restored to.</li>
          <li>"overQuota": Restoring the object would take the account past a
          quota.</li>
          <li>"tooLarge": The object is larger than the server now accepts
          for that data type.</li>
        </ul>
        <t>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.</t>
        <t>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 <bcp14>MUST NOT</bcp14> assume
        that the restored object has its old id.</t>
        <t>A restored object <bcp14>MUST</bcp14> have the property values in
        its snapshot, and the server <bcp14>SHOULD</bcp14> 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.</t>
        <section anchor="restore-containers">
          <name>Containers</name>
          <t>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:</t>
          <ul>
            <li>Email: the Mailbox with the "inbox" role.</li>
            <li>ContactCard: the AddressBook with "isDefault" true.</li>
            <li>CalendarEvent: the Calendar with "isDefault" true.</li>
            <li>FileNode: the root of the file tree.</li>
          </ul>
          <t>If the user may not add objects to the default container
          either, the restore fails with "forbidden".</t>
          <t>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.</t>
        </section>
        <section anchor="restore-email">
          <name>Email</name>
          <t>A restored Email <bcp14>MUST</bcp14> 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 <bcp14>MUST NOT</bcp14> run
          delivery-time processing (such as Sieve <xref target="RFC5228"/>
          scripts, spam filtering, or vacation responses) on it.</t>
        </section>
        <section anchor="restore-named">
          <name>FileNode and SieveScript</name>
          <t>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
          <bcp14>MUST</bcp14> 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.</t>
          <t>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.</t>
          <t>A restored SieveScript <bcp14>MUST NOT</bcp14> 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.</t>
        </section>
      </section>
    </section>
    <section anchor="expiry">
      <name>Expiry</name>
      <t>When a DeletedItem's "expiresAt" time passes, the server
      <bcp14>MUST</bcp14> stop offering it through every method in this
      document at once, and <bcp14>SHOULD</bcp14> 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.</t>
    </section>
    <section anchor="quota">
      <name>Quotas</name>
      <t>A server decides whether deleted items count toward an account's
      quotas. Counting them is <bcp14>NOT RECOMMENDED</bcp14>, 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 <xref target="RFC9425"/> <bcp14>SHOULD</bcp14>
      include "DeletedItem" in the "types" of each Quota they count
      toward.</t>
      <t>A restored object counts toward quotas as a new object would,
      whether or not the DeletedItem did.</t>
    </section>
    <section anchor="examples">
      <name>Examples</name>
      <t>A client shows a "Recently deleted" view of email:</t>
      <sourcecode type="json">
[[ "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" ]]
</sourcecode>
      <t>The server responds:</t>
      <sourcecode type="json">
[[ "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" ]]
</sourcecode>
      <t>The user restores it. The "invoices" Mailbox has since been
      destroyed, so the message goes back to the Inbox only:</t>
      <sourcecode type="json">
[[ "DeletedItem/restore", {
  "accountId": "u1",
  "ids": [ "d42" ]
}, "0" ]]
</sourcecode>
      <sourcecode type="json">
[[ "DeletedItem/restore", {
  "accountId": "u1",
  "oldState": "s9",
  "newState": "s10",
  "restored": {
    "d42": {
      "objectType": "Email",
      "object": {
        "id": "M9c07",
        "mailboxIds": { "mb-inbox": true }
      }
    }
  },
  "notRestored": null
}, "0" ]]
</sourcecode>
      <t>A client that has just destroyed an Email with id "M8f2" offers
      "Undo" by looking up its DeletedItem:</t>
      <sourcecode type="json">
[[ "DeletedItem/query", {
  "accountId": "u1",
  "filter": { "objectIds": [ "M8f2" ] }
}, "0" ]]
</sourcecode>
    </section>
    <section anchor="security">
      <name>Security Considerations</name>
      <t>All security considerations of JMAP <xref target="RFC8620"/> and of
      the specifications defining each supported data type apply.</t>
      <t>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 <bcp14>SHOULD</bcp14> make purging easy to find.</t>
      <t>In shared accounts, deleted items can disclose what another user
      deleted and when. The visibility rule in <xref target="access"/>
      limits this to people who could already read the object. A server
      that cannot track this reliably <bcp14>SHOULD</bcp14> show deleted
      items only to the account's owner.</t>
      <t>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 <bcp14>MAY</bcp14> refuse
      to keep deleted items for objects removed for such reasons, and
      <bcp14>MAY</bcp14> refuse to restore them with "forbidden".</t>
      <t>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 (<xref target="restore-named"/>).</t>
      <t>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.</t>
    </section>
    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>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
      <bcp14>SHOULD</bcp14> set "mayPurge" to true for the account's owner,
      so that a user can make a deletion final.</t>
      <t>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 <bcp14>MUST NOT</bcp14> offer it
      through this extension, and operators are expected to tell users of
      such policies through other means.</t>
      <t>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.</t>
    </section>
    <section anchor="iana">
      <name>IANA Considerations</name>
      <section anchor="iana-capability">
        <name>JMAP Capability Registration for "undelete"</name>
        <t>IANA is requested to register the "undelete" JMAP Capability as
        follows:</t>
        <dl newline="false" spacing="compact">
          <dt>Capability Name:</dt>
          <dd>urn:ietf:params:jmap:undelete</dd>
          <dt>Specification document:</dt>
          <dd>this document</dd>
          <dt>Intended use:</dt>
          <dd>common</dd>
          <dt>Change Controller:</dt>
          <dd>IETF</dd>
          <dt>Security and privacy considerations:</dt>
          <dd>this document,
          <xref target="security"/> and <xref target="privacy"/></dd>
        </dl>
      </section>
      <section anchor="iana-datatype">
        <name>JMAP Data Type Registration for "DeletedItem"</name>
        <t>IANA is requested to register "DeletedItem" in the "JMAP Data
        Types" registry as follows:</t>
        <dl newline="false" spacing="compact">
          <dt>Type Name:</dt>
          <dd>DeletedItem</dd>
          <dt>Can Reference Blobs:</dt>
          <dd>Yes</dd>
          <dt>Can Use for State Change:</dt>
          <dd>Yes</dd>
          <dt>Capability:</dt>
          <dd>urn:ietf:params:jmap:undelete</dd>
          <dt>Reference:</dt>
          <dd>this document</dd>
        </dl>
      </section>
    </section>
    <section anchor="impl-status" removeInRFC="true">
      <name>Implementation Status</name>
      <t>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 <xref target="RFC7942"/>.</t>
      <t>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.</t>
    </section>
  </middle>
  <back>
    <references>
      <name>References</name>
      <references>
        <name>Normative References</name>
        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author fullname="S. Bradner" initials="S." surname="Bradner"/>
            <date month="March" year="1997"/>
            <abstract>
              <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>
        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author fullname="B. Leiba" initials="B." surname="Leiba"/>
            <date month="May" year="2017"/>
            <abstract>
              <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>
        <reference anchor="RFC8620" target="https://www.rfc-editor.org/info/rfc8620" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8620.xml">
          <front>
            <title>The JSON Meta Application Protocol (JMAP)</title>
            <author fullname="N. Jenkins" initials="N." surname="Jenkins"/>
            <author fullname="C. Newman" initials="C." surname="Newman"/>
            <date month="July" year="2019"/>
            <abstract>
              <t>This document specifies a protocol for clients to efficiently query, fetch, and modify JSON-based data objects, with support for push notification of changes and fast resynchronisation and for out-of- band binary data upload/download.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="8620"/>
          <seriesInfo name="DOI" value="10.17487/RFC8620"/>
        </reference>
        <reference anchor="RFC8621" target="https://www.rfc-editor.org/info/rfc8621" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8621.xml">
          <front>
            <title>The JSON Meta Application Protocol (JMAP) for Mail</title>
            <author fullname="N. Jenkins" initials="N." surname="Jenkins"/>
            <author fullname="C. Newman" initials="C." surname="Newman"/>
            <date month="August" year="2019"/>
            <abstract>
              <t>This document specifies a data model for synchronising email data with a server using the JSON Meta Application Protocol (JMAP). Clients can use this to efficiently search, access, organise, and send messages, and to get push notifications for fast resynchronisation when new messages are delivered or a change is made in another client.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="8621"/>
          <seriesInfo name="DOI" value="10.17487/RFC8621"/>
        </reference>
        <reference anchor="RFC9610" target="https://www.rfc-editor.org/info/rfc9610" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9610.xml">
          <front>
            <title>JSON Meta Application Protocol (JMAP) for Contacts</title>
            <author fullname="N. Jenkins" initials="N." role="editor" surname="Jenkins"/>
            <date month="December" year="2024"/>
            <abstract>
              <t>This document specifies a data model for synchronising contact data with a server using the JSON Meta Application Protocol (JMAP).</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9610"/>
          <seriesInfo name="DOI" value="10.17487/RFC9610"/>
        </reference>
        <reference anchor="RFC9661" target="https://www.rfc-editor.org/info/rfc9661" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9661.xml">
          <front>
            <title>The JSON Meta Application Protocol (JMAP) for Sieve Scripts</title>
            <author fullname="K. Murchison" initials="K." surname="Murchison"/>
            <date month="September" year="2024"/>
            <abstract>
              <t>This document specifies a data model for managing Sieve scripts on a server using the JSON Meta Application Protocol (JMAP). Clients can use this protocol to efficiently search, access, organize, and validate Sieve scripts.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9661"/>
          <seriesInfo name="DOI" value="10.17487/RFC9661"/>
        </reference>
        <reference anchor="RFC9670" target="https://www.rfc-editor.org/info/rfc9670" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9670.xml">
          <front>
            <title>JSON Meta Application Protocol (JMAP) Sharing</title>
            <author fullname="N. Jenkins" initials="N." role="editor" surname="Jenkins"/>
            <date month="November" year="2024"/>
            <abstract>
              <t>This document specifies a data model for sharing data between users using the JSON Meta Application Protocol (JMAP). Future documents can reference this document when defining data types to support a consistent model of sharing.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9670"/>
          <seriesInfo name="DOI" value="10.17487/RFC9670"/>
        </reference>
        <reference anchor="RFC9553" target="https://www.rfc-editor.org/info/rfc9553" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9553.xml">
          <front>
            <title>JSContact: A JSON Representation of Contact Data</title>
            <author fullname="R. Stepanek" initials="R." surname="Stepanek"/>
            <author fullname="M. Loffredo" initials="M." surname="Loffredo"/>
            <date month="May" year="2024"/>
            <abstract>
              <t>This specification defines a data model and JavaScript Object Notation (JSON) representation of contact card information that can be used for data storage and exchange in address book or directory applications. It aims to be an alternative to the vCard data format and to be unambiguous, extendable, and simple to process. In contrast to the JSON-based jCard format, it is not a direct mapping from the vCard data model and expands semantics where appropriate. Two additional specifications define new vCard elements and how to convert between JSContact and vCard.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9553"/>
          <seriesInfo name="DOI" value="10.17487/RFC9553"/>
        </reference>
        <reference anchor="RFC8984" target="https://www.rfc-editor.org/info/rfc8984" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8984.xml">
          <front>
            <title>JSCalendar: A JSON Representation of Calendar Data</title>
            <author fullname="N. Jenkins" initials="N." surname="Jenkins"/>
            <author fullname="R. Stepanek" initials="R." surname="Stepanek"/>
            <date month="July" year="2021"/>
            <abstract>
              <t>This specification defines a data model and JSON representation of calendar data that can be used for storage and data exchange in a calendaring and scheduling environment. It aims to be an alternative and, over time, successor to the widely deployed iCalendar data format. It also aims to be unambiguous, extendable, and simple to process. In contrast to the jCal format, which is also based on JSON, JSCalendar is not a direct mapping from iCalendar but defines the data model independently and expands semantics where appropriate.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="8984"/>
          <seriesInfo name="DOI" value="10.17487/RFC8984"/>
        </reference>
        <reference anchor="I-D.ietf-jmap-calendars" target="https://datatracker.ietf.org/doc/html/draft-ietf-jmap-calendars-33" xml:base="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-jmap-calendars.xml">
          <front>
            <title>JSON Meta Application Protocol (JMAP) for Calendars</title>
            <author fullname="Neil Jenkins" initials="N." surname="Jenkins">
              <organization>Fastmail</organization>
            </author>
            <author fullname="Michael Douglass" initials="M." surname="Douglass">
              <organization>Spherical Cow Group</organization>
            </author>
            <date day="13" month="October" year="2026"/>
            <abstract>
              <t>This document specifies a data model for synchronizing calendar data with a server using JMAP. Clients can use this to efficiently read, write, and share calendars and events, receive push notifications for changes or event reminders, and keep track of changes made by others in a multi-user environment.</t>
            </abstract>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-ietf-jmap-calendars-33"/>
        </reference>
        <reference anchor="I-D.ietf-jmap-filenode" target="https://datatracker.ietf.org/doc/html/draft-ietf-jmap-filenode-19" xml:base="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-jmap-filenode.xml">
          <front>
            <title>JMAP File Storage extension</title>
            <author fullname="Bron Gondwana"/>
            <date day="28" month="October" year="2026"/>
            <abstract>
              <t>The JMAP base protocol (RFC 8620) provides the ability to upload and
   download arbitrary binary data.  This binary data is called a "blob",
   and can be used in all other JMAP extensions.</t>
              <t>This extension adds a FileNode data type and methods to expose blobs
   as a filesystem, along with the types of metadata that are provided
   by other remote filesystem protocols.</t>
            </abstract>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-ietf-jmap-filenode-19"/>
        </reference>
      </references>
      <references>
        <name>Informative References</name>
        <reference anchor="RFC5228" target="https://www.rfc-editor.org/info/rfc5228" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5228.xml">
          <front>
            <title>Sieve: An Email Filtering Language</title>
            <author fullname="P. Guenther" initials="P." role="editor" surname="Guenther"/>
            <author fullname="T. Showalter" initials="T." role="editor" surname="Showalter"/>
            <date month="January" year="2008"/>
            <abstract>
              <t>This document describes a language for filtering email messages at time of final delivery. It is designed to be implementable on either a mail client or mail server. It is meant to be extensible, simple, and independent of access protocol, mail architecture, and operating system. It is suitable for running on a mail server where users may not be allowed to execute arbitrary programs, such as on black box Internet Message Access Protocol (IMAP) servers, as the base language has no variables, loops, or ability to shell out to external programs. [STANDARDS-TRACK]</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="5228"/>
          <seriesInfo name="DOI" value="10.17487/RFC5228"/>
        </reference>
        <reference anchor="RFC7942" target="https://www.rfc-editor.org/info/rfc7942" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7942.xml">
          <front>
            <title>Improving Awareness of Running Code: The Implementation Status Section</title>
            <author fullname="Y. Sheffer" initials="Y." surname="Sheffer"/>
            <author fullname="A. Farrel" initials="A." surname="Farrel"/>
            <date month="July" year="2016"/>
            <abstract>
              <t>This document describes a simple process that allows authors of Internet-Drafts to record the status of known implementations by including an Implementation Status section. This will allow reviewers and working groups to assign due consideration to documents that have the benefit of running code, which may serve as evidence of valuable experimentation and feedback that have made the implemented protocols more mature.</t>
              <t>This process is not mandatory. Authors of Internet-Drafts are encouraged to consider using the process for their documents, and working groups are invited to think about applying the process to all of their protocol specifications. This document obsoletes RFC 6982, advancing it to a Best Current Practice.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="205"/>
          <seriesInfo name="RFC" value="7942"/>
          <seriesInfo name="DOI" value="10.17487/RFC7942"/>
        </reference>
        <reference anchor="RFC9051" target="https://www.rfc-editor.org/info/rfc9051" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9051.xml">
          <front>
            <title>Internet Message Access Protocol (IMAP) - Version 4rev2</title>
            <author fullname="A. Melnikov" initials="A." role="editor" surname="Melnikov"/>
            <author fullname="B. Leiba" initials="B." role="editor" surname="Leiba"/>
            <date month="August" year="2021"/>
            <abstract>
              <t>The Internet Message Access Protocol Version 4rev2 (IMAP4rev2) allows a client to access and manipulate electronic mail messages on a server. IMAP4rev2 permits manipulation of mailboxes (remote message folders) in a way that is functionally equivalent to local folders. IMAP4rev2 also provides the capability for an offline client to resynchronize with the server.</t>
              <t>IMAP4rev2 includes operations for creating, deleting, and renaming mailboxes; checking for new messages; removing messages permanently; setting and clearing flags; parsing per RFCs 5322, 2045, and 2231; searching; and selective fetching of message attributes, texts, and portions thereof. Messages in IMAP4rev2 are accessed by the use of numbers. These numbers are either message sequence numbers or unique identifiers.</t>
              <t>IMAP4rev2 does not specify a means of posting mail; this function is handled by a mail submission protocol such as the one specified in RFC 6409.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9051"/>
          <seriesInfo name="DOI" value="10.17487/RFC9051"/>
        </reference>
        <reference anchor="RFC9425" target="https://www.rfc-editor.org/info/rfc9425" xml:base="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9425.xml">
          <front>
            <title>JSON Meta Application Protocol (JMAP) for Quotas</title>
            <author fullname="R. Cordier" initials="R." role="editor" surname="Cordier"/>
            <date month="June" year="2023"/>
            <abstract>
              <t>This document specifies a data model for handling quotas on accounts with a server using the JSON Meta Application Protocol (JMAP).</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="9425"/>
          <seriesInfo name="DOI" value="10.17487/RFC9425"/>
        </reference>
        <reference anchor="IANA-JMAP" target="https://www.iana.org/assignments/jmap">
          <front>
            <title>JSON Meta Application Protocol (JMAP)</title>
            <author>
              <organization>IANA</organization>
            </author>
          </front>
        </reference>
      </references>
    </references>
    <section anchor="open-issues" removeInRFC="true">
      <name>Open Issues</name>
      <ul>
        <li>Should a restored object keep its old id when the server can
        guarantee it was never reused? Keeping it helps clients that cached
        the object; a new id is simpler and matches what clients already
        handle for created objects.</li>
        <li>Destroyed containers (Mailbox, AddressBook, Calendar) are not
        recoverable here; only their contents are. Is container recovery
        needed, or is restoring contents to the default container
        enough?</li>
        <li>Restoring large FileNode trees may need an asynchronous form,
        or a "restore with children" option.</li>
        <li>Should a /set destroy response optionally return the ids of the
        DeletedItems it created, to save the "Undo" lookup?</li>
        <li>Whether an IMAP or WebDAV counterpart is wanted, or JMAP
        alone is enough.</li>
      </ul>
    </section>
  </back>
</rfc>
