<?xml version="1.0" encoding="utf-8"?>
<?xml-model href="rfc7991bis.rnc"?>
<!DOCTYPE rfc [
<!ENTITY nbsp "&#160;">
<!ENTITY zwsp "&#8203;">
<!ENTITY nbhy "&#8209;">
<!ENTITY wj "&#8288;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" category="std" docName="draft-ietf-mailmaint-imap-uidbatches-05" ipr="trust200902" obsoletes="" updates="" submissionType="IETF" xml:lang="en" version="3">
  <front>
    <title>IMAP UIDBATCHES Extension</title>
    <seriesInfo name="Internet-Draft" value="draft-ietf-mailmaint-imap-uidbatches-05"/>
    <author fullname="Daniel Eggert" initials="D" role="editor" surname="Eggert">
      <organization>Apple Inc</organization>
      <address>
        <postal>
          <street>One Apple Park Way</street>
          <city>Cupertino</city>
          <region>CA</region>
          <code>95014</code>
          <country>USA</country>
        </postal>
        <email>deggert@apple.com</email>
      </address>
    </author>
    <date year="2025" month="2" day="6"/>
    <area>Art</area>
    <workgroup>MailMaint</workgroup>
    <keyword>IMAP</keyword>
    <abstract>
      <t>The UIDBATCHES extension of the Internet Message Access Protocol (IMAP) allows clients to retrieve UIDs from the server such that these UIDs split the messages of a mailbox into equally sized batches. It enables the client to perform operations such as FETCH/SEARCH/STORE on these specific batches. This limits the number of messages that each command operates on, enabling better control over resource usage and response sizes.</t>
    </abstract>
  </front>
  <middle>
    <section>
      <name>Introduction</name>
      <t>This document defines an extension to the Internet Message Access Protocol <xref target="RFC3501"/> to retrieve UIDs that partition a mailbox's messages into evenly sized batches.  This extension is compatible with both IMAP4rev1 <xref target="RFC3501"/> and IMAP4rev2 <xref target="RFC9051"/>.</t>
      <t>The purpose of this extension is to allow clients to (pre-)determine UID ranges that limit the number of messages that each command operates on. This is especially beneficial with <xref target="RFC9586"/> UIDONLY mode, where sequence numbers are unavailable to the client.</t>
    </section>
    <section>
      <name>Document Conventions</name>
      <t>In protocol examples, this document uses a prefix of "C: " to denote lines sent by the client to the server, and "S: " for lines sent by the server to the client.  Lines prefixed with "// " are comments explaining the previous protocol line.  These prefixes and comments are not part of the protocol.  Lines without any of these prefixes are continuations of the previous line, and no line break is present in the protocol unless specifically mentioned.</t>
      <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 14 <xref target="RFC2119"/>
          <xref target="RFC8174"/> when, and only when, they appear in
          all capitals, as shown here.</t>
      <t>Other capitalised words are IMAP keywords <xref target="RFC3501"/> or keywords from this document.</t>
    </section>
    <section>
      <name>The UIDBATCHES extension</name>
      <t>An IMAP server advertises support for the UIDBATCHES extension by including the UIDBATCHES capability in the CAPABILITY response / response code.</t>
      <section>
        <name>UIDBATCHES Command</name>
        <dl newline="true">
          <dt>Arguments:</dt>
          <dd>Message count per batch.<br/>Optional batch range.</dd>
          <dt>Responses:</dt>
          <dd>UIDBATCHES response</dd>
          <dt>Result:</dt>
          <dd>OK<br/>BAD command unknown or arguments invalid</dd>
        </dl>
        <t>When the client sends a UIDBATCHES command to the server, the server will return the UID ranges that partition the messages in the currently selected mailbox into equally sized batches.</t>
        <t>Batches are arranged by descending UID order, with the first batch containing the highest UIDs.</t>
        <t>For a mailbox with &lt;M&gt; messages, requesting batches of size &lt;N&gt; returns UID ranges corresponding to the sequence numbers</t>
        <sourcecode><![CDATA[
<M>:<M-N+1>
<M-N>:<M-2*N+1>
<M-2*N>:<M-3*N+1>
...
          ]]></sourcecode>
        <section>
          <name>Usage and Example</name>
          <t>When the client selects a mailbox, it can use the UIDBATCHES command to find the UIDs that split the mailbox’s messages into batches. For example:</t>
          <sourcecode><![CDATA[
C: A142 SELECT INBOX
S: * 6823 EXISTS
S: * 1 RECENT
S: * OK [UNSEEN 12] Message 12 is first unseen
S: * OK [UIDVALIDITY 3857529045] UIDs valid
S: * OK [UIDNEXT 215296] Predicted next UID
S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
S: A142 OK [READ-WRITE] SELECT completed
C: A143 UIDBATCHES 2000
S: * UIDBATCHES (TAG "A143") 215295:99696,99695:20351,20350:7830,7829:1
S: A143 OK UIDBATCHES Completed
        ]]></sourcecode>
          <t>The server’s response provides four UID ranges:</t>
          <ol type="%d." group="reqs">
            <li>215295:99695</li>
            <li>99695:20351</li>
            <li>20350:7830</li>
            <li>7829:1</li>
          </ol>
          <t>Each range contains up to 2,000 messages, except the last range, which contains the remaining 823 messages.</t>
          <t>As new messages cannot appear within these UID ranges, the number of messages in each range will not increase. It may decrease, though, as messages are deleted.</t>
          <t>The client <bcp14>MUST NOT</bcp14> re-run UIDBATCHES unless at least one of the following conditions is met:</t>
          <ol type="%d." group="reqs">
            <li>A different mailbox has been selected</li>
            <li>More than N/2 messages have been expunged from the mailbox</li>
            <li>More than N/2 new messages have been received into the mailbox</li>
          </ol>
          <t>These limits are in place to protect servers, since it may be expensive for the server to run this command. The client <bcp14>MUST NOT</bcp14> excessively re-run UIDBATCHES while the mailbox remains selected.</t>
          <t>The client can keep track of the number of EXPUNGE or VANISH messages and re-run UIDBATCHES if many messages are deleted.</t>
          <t>As new messages arrive into the mailbox, the client should add these to a new message batch (starting at UID 215296 in the above example). Once N/2 or more new messages have been added to the mailbox, the client can ask for updated batches by re-running the UIDBATCHES command.</t>
          <t>The server <bcp14>MAY</bcp14> reject UIDBATCHES commands with a BAD response if the client exceeds these limits.</t>
        </section>
        <section>
          <name>Response Format</name>
          <t>The server <bcp14>MUST</bcp14> reply with a UIDBATCHES response, even if no ranges are returned (see <xref target="empty-responses"/>). The UIDBATCHES response <bcp14>MUST</bcp14> include the tag of the command it relates to (similar to an <tt>ESEARCH</tt> response).</t>
          <t>The UID ranges in the response <bcp14>MUST</bcp14> be ordered in descending sequence, from the highest to the lowest UIDs.</t>
        </section>
        <section anchor="batch-sizes">
          <name>Batch Sizes</name>
          <t>This extension enforces a hard limit on the minimum batch size that a client can request, and it gives the server some flexibility in the actual size being returned. This ensures the server has implementation flexibility, making the operation less resource-intensive. And to prevent clients from misusing this extension to infer message sequence numbers.</t>
          <t>The intent of this extension is to work well in combination with <xref target="RFC9586"/> UIDONLY mode without creating a de-facto loophole that re-introduces sequence numbers.</t>
          <t><xref target="similarity-to-uid-search"/> also outlines some reasoning for these limitation.</t>
          <t>The server <bcp14>MUST</bcp14> support batch sizes of 500 messages or larger.</t>
          <t>The server <bcp14>MUST</bcp14> respond with <tt>BAD</tt> and a response code <tt>TOOSMALL</tt> if the client uses a batch size that is smaller than the minimum allowed by the server, e.g.</t>
          <sourcecode><![CDATA[
S: A302 BAD [TOOSMALL] Minimum batch size is 500
            ]]></sourcecode>
          <t>The server <bcp14>MUST NOT</bcp14> return ranges that contain more than the number of messages per batch requested by the client (2,000 in the above example). But the server <bcp14>MAY</bcp14> return fewer messages per range, notably if that makes the implementation simpler and/or more efficient.</t>
          <t>Servers <bcp14>SHOULD NOT</bcp14> return batches that are substantially smaller, and <bcp14>SHOULD</bcp14> aim to be within 90% of the requested size. The client is likely to pick a batch count based on what it wants to display to the user. A client may e.g. request 2 batches of size 1,000 if it wants to be able to display the last 2,000 messages to the user.</t>
          <t>A server <bcp14>MAY</bcp14> return batches that are substantially smaller if there are changes in mailbox state during the execution of the <tt>UIDBATCHES</tt> command, namely messages being expunged, such that the overall size of the mailbox changes. The client would be able to infer this from the `EXPUNGE`, `VANISHED`, or `EXISTS` responses it receives.</t>
          <t>If the total number of messages is not evenly divisible by the requested batch size, the last batch will contain the remainder. Thus, the last batch in the mailbox (i.e. the batch with the lowest UIDs) will usually have fewer messages than the requested number of messages.</t>
          <t>If the requested batch size is equal to or larger than the number of messages in the mailbox, the server <bcp14>MUST</bcp14> return a response with a single UID range that spans all messages.</t>
        </section>
        <section anchor="uids">
          <name>UIDs</name>
          <t>The server <bcp14>MAY</bcp14> return UID ranges with UIDs that do not exist on the server. The client as a result <bcp14>MUST NOT</bcp14> make assumptions about the existence of messages. If the server returns the response</t>
          <sourcecode><![CDATA[
S: * UIDBATCHES (TAG "A302") 163886:99703,99696:20358,20351:7841,7830:1
S: A302 OK UIDBATCHES Completed
          ]]></sourcecode>
          <t>there may not be any messages on the server with the UIDs such as 163886, 99703, 99696, etc.</t>
          <t>The range <tt>163886:99703</tt> will span approximately the requested number of messages (may be less, see <xref target="batch-sizes"/>), but its start and end UIDs may not correspond to messages on the server.</t>
          <t>This gives the server implementation some flexibility as to which UID ranges to return. They might, e.g., return <tt>163886:99697</tt> and <tt>99696:20358</tt> instead of <tt>163886:99703</tt> and <tt>99696:20358</tt> -- assuming that there are no messages in the range <tt>99704:99697</tt>.</t>
          <t>If there are fewer message in the mailbox than the requested batch size, the server would return a single batch that contains all messages in the mailbox.</t>
          <t>Servers <bcp14>SHOULD</bcp14> end the last UID batch in the mailbox with UID 1 even if this UID does not exist on the server. This makes it unambiguous to the client that this range is in fact the last range.</t>
        </section>
        <section anchor="batch-ranges">
          <name>Batch Ranges</name>
          <t>A client can optionally provide a batch range. The server limits its response to UID ranges corresponding to the specified batch indices. For example, if the client sends</t>
          <sourcecode><![CDATA[
C: A302 UIDBATCHES 2000 10:20
          ]]></sourcecode>
          <t>for a mailbox with more than 40,000 messages, the server would return the 10th to 20th batches, corresponding to the 20,000th and 40,000th message respectively.</t>
          <t>Note that batches start at the highest UIDs: batch 1 is the batch with the highest UIDs.</t>
          <t>The UID ranges that the server returns would still split the mailbox’s messages into batches of the requested size (2,000 in the example).</t>
          <t>If the client requests more batches than exist on the server, the server would return those that do exist. For example if the client sends</t>
          <sourcecode><![CDATA[
C: A302 UIDBATCHES 2000 1:4
          ]]></sourcecode>
          <t>and the selected mailbox has 7,000 messages, the server would then return a UIDBATCHES response with only 4 UID ranges.</t>
          <t>Batch ranges such as <tt>1:4</tt> in the above example <bcp14>MUST</bcp14> be ordered lowest to highest, i.e. be sent as <tt>1:4</tt> and not as <tt>4:1</tt>. Servers <bcp14>SHOULD</bcp14> reject batch ranges that are in the wrong order.</t>
          <t>If the client requests a range of batches that do not exist on the server, the server <bcp14>MUST</bcp14> still return an empty response. See section <xref target="empty-responses"/>.</t>
          <t>Note that the number of messages per batch returned by the server may be approximate as detailed in <xref target="batch-sizes"/>. As a result, if the client needs to request consecutive batch ranges such as 1:100, 101:200, 201:300, and so on, the client may want to make these batch ranges overlap by e.g. requesting 1:100, 100:200, and 200:300. The client would then be able to check if the resulting UIDs do in fact overlap.</t>
          <t>Clients <bcp14>MUST NOT</bcp14> request batch ranges that span more than 100,000 messages, i.e. the number of batches multiplied by the batch size <bcp14>MUST NOT</bcp14> be larger than 100,000. The server <bcp14>MAY</bcp14> reject UIDBATCHES commands with a BAD response with the LIMIT response code if the client exceeds this limit.</t>
          <sourcecode><![CDATA[
C: A302 UIDBATCHES 2000 1:100
S: A302 BAD [LIMIT] Too many messages
          ]]></sourcecode>
        </section>
        <section anchor="empty-responses">
          <name>Empty Responses</name>
          <t>When the client issues any valid UIDBATCHES command and the mailbox is empty, the server <bcp14>MUST</bcp14> reply with a UIDBATCHES response, e.g.</t>
          <sourcecode><![CDATA[
S: * UIDBATCHES (TAG "A302")
S: A302 OK UIDBATCHES Completed
          ]]></sourcecode>
          <t>If the client requests a range of batches that do not exist, the server <bcp14>MUST</bcp14> reply with an empty UIDBATCHES response. If the mailbox has 7,000 messages, and the client sends</t>
          <sourcecode><![CDATA[
C: A302 UIDBATCHES 2000 6:8
          ]]></sourcecode>
          <t>the server would respond with</t>
          <sourcecode><![CDATA[
S: * UIDBATCHES (TAG "A302")
S: A302 OK UIDBATCHES Completed
          ]]></sourcecode>
        </section>
        <section>
          <name>Large Mailboxes</name>
          <t>The server may not be able to return all UID ranges if the mailbox contains an extremely large number of messages.</t>
          <t>The server <bcp14>MUST</bcp14> at least support returning UID ranges spanning 100,000 messages. See <xref target="batch-ranges"/> for details on this limit.</t>
          <t>If the server can not return all of the requested UID ranges, it <bcp14>MUST</bcp14> respond with a BAD response with the LIMIT response code. Notably, when the client requests all UID ranges and the mailbox has more than 100,000 messages, the server <bcp14>MAY</bcp14> reply with a BAD response. For example:</t>
          <sourcecode><![CDATA[
C: A302 UIDBATCHES 2000
S: A302 BAD [LIMIT] Too many messages in mailbox
          ]]></sourcecode>
          <t>The client should know what the message count in the mailbox is, and if the message count exceeds 100,000 it may choose to always request batch ranges as discussed in <xref target="batch-ranges"/> instead of requesting all batches.</t>
        </section>
      </section>
      <section anchor="similarity-to-uid-search">
        <name>Similarity to UID SEARCH Command</name>
        <t>The UIDBATCHES is in effect nothing more than shorthand for a UID SEARCH command of the form</t>
        <sourcecode><![CDATA[
C: A145 UID SEARCH RETURN () <M>,<M-N>,<M-2*N>,<M-3*N>,...
          ]]></sourcecode>
        <t>where M is the number of messages in the mailbox and N is the
        requested batch count.</t>
        <t>The special purpose UIDBATCHES command, though, tries to
        address two problems:</t>
        <ol type="(%c)">
          <li>for many servers, UID SEARCH commands specifying sequence numbers are costly, especially for mailboxes with many messages.</li>
          <li>the UIDONLY extension disallows the use of sequence numbers and thus makes it difficult for the client to split its commands into batches of a size that works well for the client and server.</li>
        </ol>
        <t>By providing a special purpose command, servers can implement a different, optimized code path for determining message batches. And servers using the UIDONLY extension can provide a facility to let the client determine message batches without using sequence numbers in a UID SEARCH command.</t>
        <t><xref target="batch-sizes"/> describes some implementation restrictions to ensure this.</t>
      </section>
      <section>
        <name>Similarity to PARTIAL Extension</name>
        <t>The PARTIAL extension provides a different way for the client to split its commands into batches by using paged SEARCH and FETCH.</t>
        <t>The intention of the UIDBATCHES command is to let the client pre-determine message batches of a desired size.</t>
        <t>This makes it easier for the client to share implementation between servers regardless of their support of PARTIAL. And additionally, because the client can issue a corresponding UID SEARCH command to servers that do not implement UIDBATCHES, the client can use similar batching implementations for servers that support UIDBATCHES and those that do not.</t>
      </section>
      <section>
        <name>Interaction with MESSAGELIMIT Extension</name>
        <t>When the server supports both the MESSAGELIMIT and UIDBATCHES extension, the client <bcp14>SHOULD</bcp14> request batches no larger than the specified maximum number of messages that can be processed in a single command. The client <bcp14>MAY</bcp14> choose to use a smaller batch size.</t>
        <t>Additionally, since servers <bcp14>MAY</bcp14> limit the number of UIDs returned in response to UIDBATCHES, it is reasonable to assume that they would at most return N UIDs where N is the limit the server announced as its MESSAGELIMIT.</t>
      </section>
      <section>
        <name>Interaction with UIDONLY Extension</name>
        <t>As noted above, the UIDBATCHES extension allows for clients to create UID ranges for message batches even when the connection operates in UIDONLY mode which otherwise doesn't allow for using message sequence numbers.</t>
      </section>
      <section>
        <name>Interaction with SEARCHRES Extension</name>
        <t>UIDBATCHES is not a SEARCH nor UID SEARCH command. Servers that support SEARCHRES <xref target="RFC5182"/> <bcp14>MUST NOT</bcp14> store the result of UIDBATCHES in the <tt>$</tt> variable.</t>
      </section>
    </section>
    <section>
      <name>Formal syntax</name>
      <t>The following syntax specification uses the Augmented Backus-Naur Form (ABNF) notation as specified in <xref target="RFC5234"/>.</t>
      <t>Non-terminals referenced but not defined below are as defined by
        IMAP4 <xref target="RFC9051"/>.</t>
      <t>Except as noted otherwise, all alphabetic characters are case-insensitive.  The use of upper or lower case characters to define token strings is for editorial clarity only.  Implementations <bcp14>MUST</bcp14> accept these strings in a case-insensitive fashion.</t>
      <sourcecode><![CDATA[
capability          =/ "UIDBATCHES"
                       ;; <capability> from [RFC3501]

command-select      =/ message-batches

message-batches     = "UIDBATCHES" SP nz-number
                      [SP nz-number ":" nz-number]

uidbatches-response = "UIDBATCHES" search-correlator
                      [SP uid-range *("," uid-range) ]

mailbox-data        =/ uidbatches-response
          ]]></sourcecode>
    </section>
    <section>
      <name>Security Considerations</name>
      <t>This document defines an additional IMAP4 capability.  As such, it does not change the underlying security considerations of <xref target="RFC3501"/> and IMAP4rev2 <xref target="RFC9051"/>.</t>
      <t>This document defines an optimization that can both reduce the amount of work performed by the server, as well as the amount of data returned to the client.  Use of this extension is likely to cause the server and the client to use less memory than when the extension is not used.  However, as this is going to be new code in both the client and the server, rigorous testing of such code is required in order to avoid the introduction of new implementation bugs.</t>
    </section>
    <section>
      <name>IANA Considerations</name>
      <section>
        <name>Changes/additions to the IMAP4 capabilities registry</name>
        <t>IMAP4 capabilities are registered by publishing a standards track or IESG approved Informational or Experimental RFC. The registry is currently located at:</t>
        <t>https://www.iana.org/assignments/imap4-capabilities</t>
        <t>IANA is requested to add registrations of the "UIDBATCHES" capability to this registry, pointing to this document.</t>
      </section>
    </section>
  </middle>
  <back>
    <references>
      <name>References</name>
      <references>
        <name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3501.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5182.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5234.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9051.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9586.xml"/>
      </references>
    </references>
  </back>
</rfc>
