Why automation can accidentally change the human inbox

IMAP message state includes flags such as `\Seen`. A reply-classification worker that fetches message bodies carelessly can change those flags as a side effect, so a message that was visually unread for the sales owner appears read before a person opens it. That may not break delivery, but it breaks workflow and creates distrust in automation. Decide explicitly whether inspection should change mailbox state. If the answer is no, use an IMAP fetch form that preserves unread status and test that behavior against the actual provider.

BODY.PEEK is the key IMAP4rev2 behavior

RFC 9051 defines `BODY.PEEK[...]` as the alternate form of body fetching that does not implicitly set the `\Seen` flag. In other words, the worker can retrieve the section it needs for parsing while leaving read/unread state unchanged as a side effect of the fetch itself. That is different from guaranteeing the message will never become seen: another client, a later STORE command, or provider-specific workflow can still change flags. The engineering rule is narrow and useful—do not let your own inspection fetch mark the message read unintentionally.

UID FETCH 4821 (FLAGS BODY.PEEK[HEADER] BODY.PEEK[TEXT])

Use UIDs for durable processing, not sequence positions

When the poller finds candidate messages, persist the UID together with the mailbox's UIDVALIDITY value. Sequence numbers can change as messages are expunged, so “message 12” is not a durable identity. UIDs remain stable within a UIDVALIDITY epoch and are better suited to reply-processing state. This also makes the read-without-seen workflow safer: a retry can request the same UID and compare its processed marker instead of guessing which message now occupies a changing sequence position.

Fetch only the sections needed by the classifier

A reply worker may not need every attachment and MIME part to decide whether a message is a human reply, out-of-office, or delivery report. Fetch headers first—Message-ID, In-Reply-To, References, From, Subject, and content metadata—then retrieve a bounded body section if classification requires it. Smaller fetches reduce bandwidth and parser complexity. Keep the raw source available for ambiguous cases, but do not let “we might need everything” force the poller to download large attachments every five minutes.

Separate mailbox flags from application state

Do not use `\Seen` as the only “processed” marker in your database. Humans and other clients change that flag for reasons unrelated to outbound automation. Maintain an application-level record keyed by mailbox and durable message identity: discovered, parsed, classified, state-applied, and optionally reviewed. The message may remain unread while the application has already cancelled follow-ups. This separation makes retries idempotent and lets a human open the message later without confusing the worker into processing it twice.

Test with the exact provider and folder mapping you will run

IMAP servers differ in capabilities, namespaces, folder names, and operational limits. Create a test reply, confirm it appears in the discovered mailbox, fetch it with PEEK, and verify flags before and after. Then open the same mailbox in the normal human client to ensure the visual unread state matches expectations. Repeat with an HTML message and one multipart message. A protocol feature is only useful if the provider, parser, and mailbox UI together behave as your workflow assumes.

Make read-state policy visible to the team

Document whether automation is allowed to mark anything as read, when it may set flags intentionally, and which database state controls follow-up cancellation. If a later developer replaces the IMAP library and uses a convenience “get message” method that marks messages seen, the regression should be caught by a test rather than by a salesperson noticing missing unread badges. Treat read-state preservation as a small product requirement, not an implementation accident.

Use EXAMINE when the whole operation should be read-only

BODY.PEEK prevents a fetch from setting the \Seen flag, but an even stronger control is to open a mailbox read-only when the job only needs to inspect messages. IMAP provides EXAMINE as the read-only counterpart to SELECT. A classifier that scans an inbox for replies can use a read-only session for discovery and fetch only the headers or body sections it needs. Any business state change—marking a message, moving it, or deleting it—can happen in a separate, explicit path. This separation makes accidental inbox mutation harder because the connection used for detection is not authorized to perform normal write operations on the selected mailbox. It also makes tests clearer: reading a message should leave both the \Seen flag and mailbox contents unchanged.

Persist mailbox identity together with the UID

A UID is durable only inside a mailbox and UIDVALIDITY epoch. Store the account, mailbox name or stable mailbox identifier, UIDVALIDITY and UID together when recording that a message has been processed. If UIDVALIDITY changes, RFC 9051 requires clients to treat the previous UID mapping as no longer trustworthy and reconcile state. Without that field, a worker can mistakenly associate a recycled numeric UID with the wrong message after a mailbox is recreated or a server regenerates identifiers. Message-ID is useful as an additional cross-check, but it does not replace the mailbox UID model because Message-ID values come from message content and are not guaranteed to be globally well-behaved in every real mailbox.

Test the library abstraction against the raw flag result

Many IMAP libraries expose convenience methods named fetch, getMessage or read that do not make their flag behavior obvious. In a test mailbox, send a message that is definitely unread, run the exact production method, and then fetch FLAGS to confirm whether \Seen changed. Repeat with the PEEK or read-only option you intend to use. This is more reliable than assuming the wrapper maps perfectly to the protocol command. Keep the test in CI or an integration suite if the library is upgraded frequently, because a change in defaults can silently alter the human inbox workflow even when reply detection continues to “work.”

Field checklist

  • Use BODY.PEEK when inspection should not implicitly set `\Seen`.
  • Persist UID plus UIDVALIDITY for durable message processing.
  • Fetch only required headers/body sections before large MIME content.
  • Keep application processed state separate from mailbox read/unread flags.
  • Run a real provider test that compares flags before and after automation.
  • Add a regression test so a library change cannot silently mark replies read.

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.