The Developer’s Guide to Integrating with IMAP

The Developer’s Guide to Integrating with IMAP

• 10 min read
Tags:

Updated Oct 2026

So you want to build an IMAP integration?  Easy!

from imaplib import IMAP4_SSL

with IMAP4_SSL(host) as session:
  
  session.login(login, password)
  session.select('INBOX')
  status, data = session.search(None, 'ALL')

  for sequence in data[0].split():
    status, body = session.fetch(sequence, '(BODY[TEXT])')
    process(body)

Looks good, doesn’t it?  Or have we celebrated too soon?  While a library like this one might give the impression that integrating with IMAP is as simple as login, fetch, log out, underneath the surface IMAP contains nuances that can cause any developer massive headaches. If your mission is to build a reliable integration, it behooves you to learn about what lies underneath.

In this blog, we’ll uncover:

  1. IMAP: The Protocols, Commands, and Scopes
  2. What to Look Out for When Integrating with IMAP
  3. A Better Way to Integrate With IMAP

I. How does IMAP work?

An IMAP integration connects your app to a user’s mail server over a stateful, line-oriented protocol so it can list, fetch, and change messages. A basic login-and-fetch loop takes a few lines of code. A reliable integration also has to handle message UIDs that change, soft deletes and expunges, push notifications, and incremental sync after a disconnect.

The IMAP model assumes a centralized server and a multitude of transient, possibly low-bandwidth clients. These clients may examine what messages are on the server without downloading them in their entirety; therefore they are under no obligation to delete the messages after fetching them, as POP clients typically do.

Therefore, a client does not need to have any storage at all (though in practice most at least maintain a cache)!  Though this architecture seems unremarkable today, in the late 1980s the concept of using one’s mailbox as a central database rather than a queue was revolutionary. 

The IMAP Protocol: The IMAP protocol itself is line-oriented and typically occurs over a long-running session per user-agent (there is no asynchronous do-this-and-wake-me-when-it’s-done functionality). A client will connect, authenticate, and select a particular mailbox to issue commands against.

Authentication can occur in two ways: with the LOGIN command (simple password) or AUTHENTICATE (any SASL mechanism e.g. OAuth, Kerberos…). This session based approach has significant implications for the IMAP server’s performance.

IMAP Data Models: Messages, annotated with several attributes, are organized under a hierarchy of mailboxes (inbox/finance/receipts`). The attributes include metadata like date, size, and optional flags, but also two identifiers: the sequence number from 1…n in the mailbox and a UID. Either can be used to refer to the message, and both are defined only within the space of that particular mailbox (more on this below).

The UID  is less stable than it looks.. The unique identifier of a message MUST NOT change during the session, and SHOULD NOT change between sessions.

That’s one of the promises that IMAP makes—when you open a session on a specific folder, your UIDs will remain the same for the duration that that session is held open. But if you open a new session, all bets are off—you have to check the UIDVALIDITY value returned from the command (SELECT) that opens the session, and if it differs from the value you stored the UIDs may all be completely different.

The structure of the message envelopes themselves is formally defined outside of IMAP. But the spec does call for an attempt to parse out the headers/body of the message so that they may be queried.

Commands: The bulk of the IMAP RFC is devoted to commands. They can broadly be divided into two categories: connection management (e.g. STARTTLS, CLOSE) and CRUD functionality (e.g. FETCH, STORE, SEARCH). Modern extensions have been bolted on to the spec to enable things like delta updates and push notifications, but specific implementations often leave much to be desired. A given server’s support for these is revealed in the CAPABILITY command, which is typically announced by the server after authentication.

Out of Scope: It is also important to understand what IMAP doesn’t define:

  1. Any particular lifecycle for messages (While flags like `/Deleted` do have particular semantics for individual commands, it is largely up to the client to define his or her workflow)
  2. Labels, categories, colors, or tags. These are extensions implemented by individual providers.
  3. Pagination
  4. Global UIDs
  5. Any interpretation of what the message body is (this is left to MIME)
  6. Any facility to send the messages (Sent/SMTP problem)

II. What breaks when you integrate with IMAP?

1. Why do IMAP message UIDs change?

With IMAP, a message’s UID is useless for most practical purposes. It is not unique across folders. It will change when moved to another folder. It can change when a folder is renamed (depending on the configuration of your server). Between sessions it should not change. When it does, the server changes UIDVALIDITY.. It can change when the server decides it should change (reindexing, etc). This instability is why some providers like Gmail provide [their own global ID scheme] (https://developers.google.com/gmail/imap/imap-extensions) as an extension.

But outside of Gmail, how can a message be uniquely identified?  IMAP does reveal a UIDVALIDITY number which can be queried per mailbox. This mechanism, acting like a timestamp, provides a number to associate alongside the UID. A client can query the UIDVALIDITY and detect a change, indicating that it should reindex its cache of that folder. Thus a message can be uniquely identified with the tuple of (UID, UIDVALIDITY, folder_name).

UIDVALIDITY does not address all of the mentioned shortcomings of a message’s UID – particularly it still offers no way to track a message across folders. A scheme to uniquely index messages is left as an exercise  to the reader.

2. How does IMAP handle deleted and expunged messages?

When a client “deletes” a message, they are typically performing a soft delete – marking the message with the DELETED flag which the client might show in a special Trash folder. This operation is actually performed with the STORE command. A client can furthermore EXPUNGE the mailbox which actually deletes the messages from the client’s view – whether or not the server holds onto a copy is left as an implementation detail. Be warned – the innocuous sounding CLOSE command also EXPUNGEs by default.

A special Trash/Deleted folder is not part of the IMAP spec. Servers implement this differently – for some it is a virtual folder which simply shows all messages marked as DELETED. Others require messages to be explicitly moved there as part of message deletion.

The name of the Trash/Deleted folder is also not standard. Clients must query for this – Gmail, for instance, localizes the name of the folder and provides a mapping with an extension to the LIST command.

3. How does IMAP notify a client of new email?

How do you know when a message has arrived?  For the simplest use cases a poller will work, assuming that neither latency nor performance overhead is particularly important. When the integration needs to scale, however, IMAP provides a few extensions: IDLE and NOTIFY. IDLE keeps a connection open so that the server can drip feed events to the client; this approach only supports a single folder, however. NOTIFY is an evolution that allows the client to define what mailboxes and events it wants to subscribe to so that only a single connection is needed.

When a client reconnects to a server, how does it know what has changed?  Does it need to compare its cache with every message in every folder? Luckily, IMAP provides two extensions to enable delta updates: CONDSTORE and QRESYNC. Both extensions introduce a modification sequence (similar to the UIDVALIDITY message) which is associated with each message. This has the additional use case of preventing multiple clients from clobbering each other’s changes when manipulating the same message (or mailbox).

One caveat to extensions: If you want your IMAP integration to support all IMAP implementations that you’ll run into in the wild you unfortunately can’t assume that these extensions exist—most servers support them, but some still don’t.

Why is real-time email sync hard with IMAP?

IMAP has no command that runs in the background and wakes the client when it finishes, so real-time sync needs polling or a protocol extension, and server support for the extensions varies.

Polling: The client asks the server for changes on a schedule. Adds latency and server load as the integration scales.

IDLE: Keeps one connection open so the server pushes events. Covers a single folder.

NOTIFY: The client subscribes to chosen mailboxes and events over one connection. Not every server supports it.

CONDSTORE and QRESYNC: A modification sequence on each message shows what changed since the last sync. Most servers support them, but some do not. They also prevent clients from overwriting each other’s changes.

How do I recover from an IMAP sync failure or reconnect?

  1.  Reconnect and authenticate with LOGIN or AUTHENTICATE.
  2.  SELECT the folder and read its UIDVALIDITY.
  3. If UIDVALIDITY differs from your stored value, discard the cached UIDs for that folder and reindex it.
  4.  If it matches and the server lists CONDSTORE or QRESYNC in its CAPABILITY response, request only the changes since your last modification sequence.
  5.  If the server supports neither, compare your cache against the folder contents.

When should you use an email API instead of IMAP?

Use an email API when you need one integration across providers and do not want to build and maintain IMAP sync, sending, and notifications for each of them. The Nylas Email API reads, sends, searches, and manages email across Gmail, Microsoft 365, Exchange, Yahoo, iCloud, and IMAP providers.


Providers: IMAP directly needs one connection per account, and extension support varies by server. The Nylas Email API is one interface across Gmail, Microsoft 365, Exchange, Yahoo, iCloud, and IMAP accounts.
Authentication: IMAP uses LOGIN or AUTHENTICATE for each account. A Nylas grant is an authenticated connection to one user’s mailbox, and its ID goes on every request.


Sending: IMAP does not define sending, which is handled over SMTP. Nylas uses POST /v3/grants/{grant_id}/messages/send.

Start building for free, or contact sales to talk with one of our platform specialists.

Start building for free, or contact sales to talk with one of our platform specialists.

Frequently Asked Questions

What is IMAP?

IMAP is a protocol that keeps email on a central server and lets clients read, search, and change messages over a long-running session. Clients are not required to delete messages after fetching them.

What is UIDVALIDITY in IMAP?

UIDVALIDITY is a number the server returns when a client selects a mailbox. If it differs from the value a client stored, the message UIDs in that mailbox may all be different and the client must reindex its cache.

Does IMAP send email?

No. IMAP does not define any way to send messages. Sending is handled over SMTP.

How do I get notified of new email over IMAP?

Poll the server on a schedule, or use the IDLE extension to keep one connection open for a single folder, or the NOTIFY extension to subscribe to several mailboxes and events over one connection.

Does the Nylas Email API support IMAP accounts?

Yes. The Nylas Email API reads, sends, searches, and manages email across IMAP providers, as well as Gmail, Microsoft 365, Exchange, Yahoo, and iCloud.

For the protocol text, see RFC 3501 (IMAP4rev1), RFC 9051 (IMAP4rev2), RFC 2177 (IDLE), RFC 5465 (NOTIFY), and RFC 7162 (CONDSTORE and QRESYNC)

Related resources

Why emails sent via your app go to spam (but Outlook delivers them just fine)

This post is part of our “What to do when there’s no email admin” series…

How to fix Microsoft’s “service abuse mode” error

This post is part of our “What to do when there’s no email admin” series…

How to read Office 365 spam headers: A simple guide for non-admins

This post is part of our “What to do when there’s no email admin” series…