What UIDNEXT promises

RFC 9051 describes UIDNEXT as the predicted UID that will be assigned to a new message. While UIDVALIDITY is unchanged, UIDNEXT moves when messages are added, so comparing it across checks can tell a client that arrivals occurred. The specification also warns that the value is not a guarantee that any particular message will have that UID. A client should therefore treat it as a mailbox-change indicator rather than as an allocation API.

Why UID ranges can contain gaps

Messages can be delivered and then expunged before your client fetches them. Server implementation details can also mean the next predicted value advances without leaving a continuous set of UIDs for you to retrieve. An incremental poll should ask the server which UIDs actually exist above the last processed cursor. Fetching a numeric range is fine only when the command semantics tolerate missing UIDs and your code does not interpret every integer as a message.

Combine UIDNEXT with a durable cursor

Store last_processed_uid and uidvalidity. On a lightweight status check, if UIDNEXT has not advanced and UIDVALIDITY is unchanged, there may be no new deliveries to inspect. If UIDNEXT advanced, select the mailbox and search for actual UIDs greater than last_processed_uid. Process those in ascending order, commit side effects, then advance the cursor to the highest safely handled UID. UIDNEXT helps avoid unnecessary work; the actual UID set determines what you process.

Do not use UIDNEXT across a validity change

When UIDVALIDITY changes, all old assumptions about UID persistence must be invalidated. A new UIDNEXT in the new epoch cannot be compared meaningfully with the old cursor. Trigger the resynchronization path, rebuild message mappings from a bounded window, and establish a fresh cursor. Persisting the three values together—mailbox, UIDVALIDITY, and last processed UID—makes this branch explicit rather than accidental.

Polling and IDLE can share the same catch-up logic

An IDLE notification can wake the worker quickly, while a periodic poll protects against lost connections. Both paths should call the same incremental synchronization function: check validity, discover actual UIDs above the cursor, process idempotently, and commit. This avoids two subtly different reply detectors where one uses UIDNEXT correctly and the other relies on UNSEEN or message sequence numbers. One synchronization core is easier to test.

Message sequence numbers are not a replacement

IMAP sequence numbers can change when messages are expunged because they describe the current ordering in the selected mailbox. UIDs are designed for persistence across sessions within one UIDVALIDITY epoch. A polling system that stores sequence number 125 and resumes later can point at a different message. Use sequence numbers only as transient protocol positions inside the current session; store UIDs for cross-session reply processing.

A worked poll cycle

The worker saved UIDVALIDITY 77 and last UID 880. A STATUS check shows UIDNEXT moved from 881 to 885. The worker SELECTs INBOX, confirms UIDVALIDITY is still 77, then searches UID 881:* and the server returns 881, 883, and 884. It processes those three, not the nonexistent 882, and advances the cursor to 884 after durable completion. That is the correct use of UIDNEXT: it told the worker to look, while the server’s actual UID result told it what existed.

Alert on impossible cursor conditions

If last_processed_uid is greater than or equal to a stable UIDNEXT for an extended period, or UIDNEXT appears to move backward without UIDVALIDITY changing, log the full SELECT/STATUS response and stop automatic advancement. Those observations can indicate a client bug, stale cache, or server inconsistency. Silent correction is risky because missing one reply can cause an unwanted follow-up. Prefer a bounded resync and explicit incident record.

Treat UIDNEXT as a boundary hint, not a guaranteed next message

IMAP exposes UIDNEXT as the predicted UID that will be assigned to a new message, but clients must not assume every UID below it exists. Deletions and allocation behavior can leave gaps. A poller can use the value to decide whether there may be work beyond its stored high-water mark, then issue UID-based search/fetch operations for actual messages. The durable state remains the last safely processed UID together with UIDVALIDITY; it is not `UIDNEXT - 1` blindly written to storage.

Consider a poll cycle where the stored cursor is 4100 and the server reports UIDNEXT 4110. Search for UIDs greater than 4100, receive 4102, 4104, and 4109, process each idempotently, then advance the safe cursor according to your chosen gap policy only after you know what the server actually returned. If UIDVALIDITY changes, discard this arithmetic and run the resynchronization branch. The same catch-up function can be called after an IDLE notification, after a timer-based poll, and after reconnect, which reduces code paths. Alert if the persisted cursor is greater than or incompatible with the selected mailbox state; impossible cursor conditions are usually more actionable than merely logging that an IMAP poll returned zero messages.

Persist cursor updates only after side effects are committed

Do not advance the durable UID cursor before the reply event has been safely written and any required sequence state is committed. Otherwise a process crash can leave the cursor ahead of a message whose business effect never happened, causing the next poll to skip it permanently. Process a fetched UID, commit its idempotency key and reply classification, then advance the high-water mark. If your storage cannot make those changes atomic, design a replayable intermediate state so restart recovery can revisit uncertain messages without duplicating notifications.

Field checklist

  • Persist UIDNEXT only as supporting state, not as the message cursor.
  • Search actual UIDs above the last processed UID after UIDNEXT advances.
  • Never compare old and new UID values across a UIDVALIDITY change.
  • Use UIDs rather than sequence numbers across IMAP sessions.
  • Share one catch-up function between polling and IDLE reconnects.
  • Alert instead of silently advancing when cursor invariants look impossible.

Primary sources

Standards and provider policies can change. These links are the reference points used for this field note.

  1. RFC 9051 — IMAP4rev2IETF / RFC EditorMailbox flags, UIDs, BODY.PEEK and IMAP4rev2 behavior.
  2. RFC 4549 — Synchronization Operations for Disconnected IMAP4 ClientsIETF / RFC EditorOperational guidance for UIDVALIDITY checks, cache invalidation and mailbox synchronization.