-
Notifications
You must be signed in to change notification settings - Fork 22
ClientImplementation
IMAP clients can be implemented in many different ways. Sometimes clients access the server in very unfriendly ways because of ignorance, sometimes it's simply because the user interface requires it. This guide tries to help you to implement your client in as server-friendly way as possible, even for user interface features that are considered bad by some people.
Nowadays STARTTLS command is the preferred way to start TLS encryption for the connection. Unfortunately a lot of servers block port 143 and only allow port 993 (imaps), because this is easier to implement in firewall and it avoids the problems of bad clients sending passwords in plaintext.
Some clients allow users to select between "no encryption", "SSL" and "TLS". However many users don't have a clue what the difference between SSL and TLS is. In fact it's even incorrect to refer to imaps port as SSL, because it most likely uses TLS protocol as well. I think the non-advanced user interface for this configuration should be a single checkbox:
- Allow insecure connections (without SSL/TLS).
There should also be an advanced UI where the power users could specify exactly if they want imaps or STARTTLS and what port to use.
If this box is checked, the client would initially figure out how to perform the encryption and then cache it for later use. If it ever fails to connect, it could do this check all over again in case server configuration had changed (perhaps asking from user first if it is wanted).
-
Connect to port 143. Wait one second (or so) for the connection to establish.
-
If 143 didn't answer soon enough, connect to port 993 (without killing the 143 connection).
-
If connection to port 993 succeeded but port 143 didn't, use 993.
-
If connection to port 143 succeeds (even if 993 connection was already started), look for STARTTLS in CAPABILITY (see the next section)
-
If you see STARTTLS in capabilities, execute it. If it succeeds, use 143.
-
If there is no STARTTLS capability, connect to port 993. If it succeeds, use it.
-
If there is no STARTTLS capability and port 993 doesn't answer, fail.
-
Some IMAP servers advertise CAPABILITY already in the OK greeting:
* OK [CAPABILITY IMAP4rev1 STARTTLS] Hello.
If you see it, don't waste time executing CAPABILITY command. After performing STARTTLS, the capabilities may change. Especially important is that AUTH=PLAIN might show up after that:
* OK [CAPABILITY IMAP4rev1 STARTTLS] Hello.
1 STARTTLS
1 OK Begin TLS negotiation now.
<<<TLS negotiation>>>
2 CAPABILITY
* CAPABILITY IMAP4rev1 AUTH=PLAIN
2 OK
Capabilities may also change after user has logged in. Different users may even have different capabilities. Some servers announce the new capabilities after LOGIN/AUTHENTICATE:
1 LOGIN user pass
1 OK [CAPABILITY IMAP4REV1 UIDPLUS] Logged in.
If you didn't get the capability, ask for it yourself.
The most important thing is: If you see LOGINDISABLED in capabilities, NEVER try to perform plaintext authentication. It is guaranteed to fail and your client just sent the password unencrypted over the internet.
So how do you authenticate?
-
If you see AUTH=x in capabilities where x is something you can handle and it's not LOGIN or PLAIN, use it.
- Some misconfigured servers may fail it anyway, so it might be worth trying the next method in list after a failure.
-
If you see AUTH=PLAIN and there is no LOGINDISABLED capability, use either LOGIN command or AUTHENTICATE PLAIN.
-
LOGIN command is preferred over AUTHENTICATE PLAIN because it avoids one round-trip (unless SASL-IR is also advertised).
-
AUTHENTICATE PLAIN supports logging in as another user ("master user login"), but normally you don't need to use it.
-
-
If you see LOGINDISABLED and STARTTLS in capabilities, perform STARTTLS even if it wasn't required. After STARTTLS you should be able to authenticate.
- If you still see only LOGINDISABLED and no non-plaintext AUTH=x mechanisms that you can handle, fail the authentication.
If you see SASL-IR in capabilities, you can send SASL initial response in AUTHENTICATE command. Do this when possible (and useful) to avoid one round-trip.
You may wish to support also login referrals, but they are quite rarely used.
Don't be lazy, read the IMAP RFC instead of guessing how things work based on what a couple of servers send you. If you don't fully understand the RFC, ask about it in the IMAP protocol mailing list. When you implement your input parsing, pay special attention to ABNF rules. You might be surprised at some things that server is allowed to send you. For example just because servers usually list mailbox names with quoted strings, it doesn't mean that listing them with literals isn't allowed. Also reply to BODY[] can be NIL or "hello" just as well as a literal.
IMAP allows simultaneous access for multiple clients to a mailbox. Don't
expect that you're the only one accessing it. If you're using multiple
clients, it's very annoying when \Seen
flags aren't synchronized
between the clients. If your client does changes to mailbox state, send
them immediately to the server, and also show changes sent by the server
immediately to the user.
Don't ignore unexpected untagged replies. Especially EXPUNGE replies must be remembered, and it's not only the EXPUNGE command that can create those replies. UIDVALIDITY changes must also completely invalidate your local mailbox cache, no matter when that happens. You might as well support handling most of the untagged responses, independently on what generated them. For example:
1 NOOP
* 1 EXPUNGE
* 1 FETCH (FLAGS (\Seen))
* 16 EXISTS
* STATUS "imap-list" (MESSAGES 6 UNSEEN 5)
* OK [UIDVALIDITY 12345]
1 OK
If you see the following, you should update your internal state:
-
The first message was expunged, remove it.
-
The new first message (originally the second one) had its flags updated. If the message is visible on screen, update it.
-
A new message was added to the mailbox. You'll probably want to add it to your message list.
-
"imap-list" mailbox STATUS was changed. If the mailbox is visible and its unseen count is displayed, update the count.
-
The mailbox's UIDVALIDITY changed. You can no longer trust your cached UIDs. The message sequences haven't changed though, so you can fix your local cache by issuing
UID SEARCH 1:*
and mapping the messages to the returned new UIDs. Servers rarely send this though, and some servers instead just disconnect the client if this happens. If you don't want to bother implementing the UID SEARCH fix, you can instead just for example disconnect the connection and let your regular UIDVALIDITY-change-on-SELECT handling clear your local cache and redownload everything this.
Commands can fail - deal with it. You may have requested a message that was just expunged from server by another connection. Someone might have deleted the mailbox you just tried to access. In any case, if server replies with NO for a command, you should try to deal with it or show the error message to user. RFC-2180 helps you with figuring out how to handle FETCH and STORE errors. Just don't keep retrying a failed command forever.
How to list mailboxes is one of the most confusing problems with IMAP. When you're implementing your solution, keep this in mind all the time:
- Is your client usable and fast if the server has millions of mailboxes? For example it could be exporting the entire Usenet.
This basically means that you should avoid using "*" LIST wildcard. Instead show only the mailboxes that user has expressed interest in. Most clients show mailboxes as a tree structure. Typically you begin by displaying only the toplevel mailboxes:
1 LIST "" %
If some of the mailboxes have children (see the children section below), get a list of them only after user has opened that hierarchy in the tree:
2 LIST "" work/%
Don't expand automatically the whole tree. That's even worse than
executing LIST "" *
.
LIST reply is very loosely defined. It may be very much out of order. It may even contain duplicates. Here's an example reply for LIST "" *:
* LIST (\UnMarked) "." "foo.bar"
* LIST (\Marked) "." "foo"
* LIST () "." "baz"
* LIST (\UnMarked \NoSelect) "." "baz"
* LIST () "." "a.b.c"
All the toplevel mailboxes in a namespace have the same separator. So a single LIST command can never return different separators, with the exception of INBOX which is a special case. INBOX exists in a namespace of its own, so it (and its children) may have a different separator than the rest of the mailboxes. You rarely see this though, except as a NIL separator for INBOX with UW-IMAP.
If you need to know the hierarchy separator, you should get it from a non-INBOX LIST reply if you've already done LIST. If you don't see any non-INBOX mailboxes or you don't need to do LIST for any other reason, you can get the separator for default namespace with:
1 LIST "" ""
* LIST (\Noselect) "/" ""
1 OK
If you want to get it for other namespaces, use:
1 LIST #shared/ ""
* LIST (\Noselect) "/" "#shared/"
1 OK
Some clients cache the hierarchy separator forever. This has problems if the server configuration is changed (e.g. server software changed). Try to avoid this problem.
Mailbox separator can also be backslash ('\) character. Make sure you use it correctly:
1 LIST "" "%\\%"
* LIST () "\\" "hello\\world"
1 OK
2 SELECT "hello\\world"
Mailbox names are in modified UTF-7 format as specific by the IMAP RFC. Display and create mailboxes using UTF-7. However existing mailbox names might already be wrong, so try not to break if you see 8bit characters. You should try to show them as UTF-8 (future IMAP RFCs might move to using UTF-8), or if the name isn't valid UTF-8 show them any way you want.
When parsing the LIST replies, remember that mailbox names may also be literals. This is perfectly legal:
1 list "" %
* LIST () "/" {7}
"hello"
1 OK
If server capabilities contain
CHILDREN or
LIST-EXTENDED, you might see
\HasChildren
and \HasNoChildren
flags in LIST replies. It's not
required however that these are listed unless you explicitly request
them with LIST-EXTENDED. So if don't see either of them, you can't
assume anything about its children state. Note that \NoInferiors
implies \HasNoChildren
.
If you don't know the children state, there's a simple way to find out. Try to list its children to see if it returns anything:
1 LIST "" box/%
If you see any replies, it has children, otherwise it doesn't. Note that mailbox names may also contain "%" and "*" characters. Doing the above LIST command for a mailbox named "*" wouldn't be very nice, so you should replace "*" with "%" in mailbox names when LISTing them:
1 LIST "" %
* LIST () "/" INBOX
* LIST () "/" "*foo"
1 OK
2 LIST "" "%foo/%"
* LIST () "/" "*foo/child"
2 OK
If you want to know children status for all root mailboxes and the children flags weren't returned for LIST "" %, you can use:
1 LIST "" %/%
If server supports LIST-EXTENDED, you can force it to return children flags:
1 LIST () "" % RETURN (CHILDREN)
If server advertises NAMESPACE extension, you should show all the namespaces. However don't list their contents unless user explicitly opens the namespace. For example an initial mailbox tree view could look something like:
-
Default namespace
-
INBOX
-
Trash
-
Spam
-
-
#shared
-
#news
Only if user expands #shared or #news you should try to list their contents. You might even allow users to hide some namespaces entirely. For example UW-IMAP exports different mailbox drivers as namespaces, so if user isn't interested of using them, they're just taking extra space on screen.
Some servers list namespace prefixes as part of mailboxes in the default namespace, as well as allow a single LIST command to span across multiple namespaces. This is because many clients don't support namespaces, so this makes the non-default namespaces visible for such clients. This however isn't a preferred server behavior, so your client shouldn't rely on this, but your client shouldn't get confused either if it sees LIST reply with a namespace prefix.
A server that doesn't show namespace prefixes with LIST:
1 NAMESPACE
* NAMESPACE (("" "/")) NIL (("#public/" "/"))
1 OK
2 LIST "" %
* LIST () "/" INBOX
2 OK
A server that does show namespace prefixes with LIST:
1 NAMESPACE
* NAMESPACE (("" "/")) NIL (("Public mailboxes/" "/"))
1 OK
2 LIST "" %
* LIST () "/" INBOX
* LIST (\NoSelect) "/" "Public mailboxes"
2 OK
In the latter case you could just ignore the returned "Public mailboxes"
LIST reply. However if it doesn't have a \NoSelect
flag, it's possible
that such mailbox really does exist in the default namespace. Its
children would then exist in the "Public mailboxes" namespace.
Subscriptions are meant to be used as some kind of bookmarks for mailboxes. If your server had a million mailboxes, the user would probably want to know only about a few of them. User would subscribe those few ones and the client would display only them as the mailbox list in normal operation.
Some clients subscribe automatically to all mailboxes when logging in for the first time. This is the worst kind of subscription abuse there is. Never subscribe to mailboxes you didn't create yourself. It's also questionable if you should subscribe to mailboxes you did create yourself. Your "create mailbox" user interface should perhaps have a checkbox "Subscribe this mailbox also".
You should handle subscription lists similarly to listing mailboxes with LIST, just instead of LIST use LSUB command. LIST-EXTENDED LIST command also supports listing subscriptions. Although it's not horribly bad to use "*" wildcard for LSUB, try to avoid it anyway. The user might still be subscribed to lots of mailboxes and it's a waste of bandwidth to list mailboxes which aren't visible.
Many clients implement subscriptions by having a "Show only subscribed mailboxes" option in settings. Mailboxes can be subscribed and unsubscribed using a special subscriptions dialog. Although this works, it's not necessarily the best possible user interface. If you ever want to temporarily access an unsubscribed mailbox it takes a lot of work to temporarily subscribe and then unsubscribe again.
Another problem with subscriptions is that if they're lost from the server (e.g. server software replaced), users are in complete panic because they no longer see their mailboxes. A good user interface would prevent this panic by indicating in some way that there are unsubscribed mailboxes.
One possible user interface would allow easily switching between subscribed and all mailboxes. For example the UI could look like:
-
INBOX
-
Trash
-
#shared/joe/project
-
Unsubscribed mailboxes (53)
Where opening the last unsubscribed mailboxes would show all of the ones not already visible (with the number of such mailboxes). Or if there are no unsubscribed mailboxes, this entry simply wouldn't be visible.
LIST replies may contain \Marked
and \UnMarked
. The IMAP RFC
specifies them as "interesting" and "uninteresting" mailboxes. \Marked
mailboxes usually have a non-zero RECENT message count, and \UnMarked
have a zero RECENT message count. This isn't necessarily true though.
There are three kinds of mailboxes:
-
Mailboxes that can have only messages.
-
Mailboxes that can have only child mailboxes. This could also be called a directory.
-
Mailboxes that can have both messages and child mailboxes. These are also called dual-use mailboxes.
If server doesn't support dual-use mailboxes, the user has to choose between creating a mailbox that can have messages and a directory. Many clients implement this badly, so users often complain that "the server doesn't support subfolders!" while in fact they are supported.
CREATE mailbox
creates a mailbox that can have messages.
CREATE mailbox/
creates a mailbox that can have child mailboxes. If
the server supports dual-use mailboxes the latter might not actually do
anything. It's not really even necessary, CREATE mailbox/foo
could be
used directly without creating the parent mailbox at all.
So unless you know that the server supports dual-use mailboxes your user interface should have some kind of a "create a directory" checkbox or some help text that lets user know that it's possible to create two types of mailboxes.
Dual-use state isn't actually server-specific. Some namespaces may have
dual-use mailboxes while others may not. If you keep track of the
dual-use status do it separately for each namespace. If server has at
least one selectable (doesn't have \NoSelect
flag) mailbox created
(other than INBOX), you can figure out from its LIST reply if the
namespace supports dual-use mailboxes. If the flags contain
\NoInferiors
it doesn't, otherwise it does.
If a mailbox has \NoInferiors
flag, your client should give a helpful
error message if user tries to create or rename a mailbox under it.
When you rename a mailbox yourself, you can also rename the mailbox in your local cache. If another client does the rename, this isn't really possible because you'll simply see the old mailbox gone and a new mailbox appear. It's not safe to assume that the local cache is valid even if you can figure out that this could be a rename.
Some clients perform mailbox deletions by moving the mailbox under Trash. This doesn't work if the server doesn't support dual-use mailboxes. If you insist on doing this, at least handle the RENAME failure in some way, either just DELETEing the mailbox or doing the move by updating only internal state.
Many clients show number of unseen messages next to the mailbox name in mailbox list. The clients usually do this by sending STATUS command to all mailbox every few minutes. STATUS can be expensive to some servers though, so this isn't really recommended. However since users want that feature, such recommendations are pretty useless. There are a few things you can do to lessen the server's load though.
When you're connected to the server and you've got the list of mailboxes, send STATUS only to the mailboxes visible in your mailbox list. If some child mailboxes aren't visible, it's pointless to waste server's time on calculating their status. Also don't send STATUS to selected mailbox. It works badly with some servers, and you already know the selected mailbox's state so it's pointless to send the STATUS. Instead use simply a NOOP (or CHECK or IDLE) to find out if the selected mailbox has new messages.
When sending multiple LIST or STATUS commands, send them all at once pipelined so you don't waste time because of network latency. Take advantage of the LIST-STATUS extension if it's provided.
Clients rarely use \Recent
flag counters. They can be very useful for
people who tend to leave messages unseen as a way of marking them as
"act on later". Seeing "37 unseen" isn't very helpful if you've no
intention of marking those 37 messages as \Seen
anytime soon. Having a
non-zero recent count for non-selected mailbox however means that the
mailbox really has messages that you haven't seen before. Such mailboxes
could perhaps be marked with bold font or some other way in the client's
user interface.
After selecting a mailbox, clients usually show a list of messages. Usually the list contains at least message sender, date and subject.
Most clients simply fetch the wanted message data for all messages and
then display them in a list. This is very annoying to use when opening a
large mailbox for the first time. Suppose your mailbox had 2500 messages
and you tried to open the mailbox for the first time with your laptop's
mail client using a 8kB/s GPRS connection.
FETCH 1:* (ENVELOPE INTERNALDATE)
command could easily send over a 1MB
reply (my 2520 message INBOX sent 1,1MB), which would take over two
minutes to download. Your user most likely just wanted to check if there
were any new messages. Is it really necessary to wait for 2 minutes for
that?
A well behaving client would first fetch only the messages that are visible in the message list. Only after they're visible the rest of the mailbox list could be downloaded on background. And by background I mean that user should be able to open messages and perform other operations without waiting for the whole mailbox list to finish downloading. This can be done by fetching data in smaller blocks (e.g. 1:100, 101:200, etc. instead of 1:*).
It might even be useful to not download the entire message list at all. Data connections can still cost per downloaded megabyte. This can be easily implemented by having your mailbox list filled with empty or "message not downloaded yet" stubs. When user scrolls the mailbox list and stubs become visible, the messages are downloaded and the stubs are replaced with the real content. The client can of course anticipate this and always have the previous and the next pageful downloaded so user would rarely see the stubs.
You should basically fetch only the information you need, but try not to fetch anything that is costly to servers. Below is a list of fields and how costly they usually are for servers:
-
UID, FLAGS: Very cheap for all servers
-
INTERNALDATE: Quite cheap.
-
ENVELOPE: Some servers keep envelopes directly cached (Cyrus), which makes it cheap for them to return. If envelope contains all the necessary information for your client, use it. If you want to fetch some other headers as well, it might be better to just fetch everything with BODY.PEEK[HEADER.FIELDS ()].
-
BODY.PEEK[HEADER.FIELDS ()]: Some servers cache specific headers (Dovecot), which makes it cheaper to return them than returning ENVELOPE which requires parsing headers and building the envelope reply.
-
BODY, BODYSTRUCTURE: Some servers keep these cached (Cyrus, Dovecot), but others don't and it's very costly for them to calculate it because it requires parsing the whole message.
-
RFC822.SIZE: Some servers keeps this cached (Cyrus, Dovecot), but others don't and it can be very costly for them to calculate it if the message's physical size doesn't match the RFC822.SIZE (ie. physically linefeeds are LF, while RFC822.SIZE requires counting linefeeds as CRLFs).
-
RFC822.HEADER, BODY.PEEK[HEADER], BODY.PEEK[HEADER.FIELDS.NOT ()]: These require opening the message file and sending (and parsing with HEADER.FIELDS.NOT) the header. Quite costly, but not as much as sending/parsing the entire message.
-
BODY[], RFC822, RFC822.TEXT: Very bandwidth costly. You should really avoid using these in message list fetches.
Some people want to sort messages by their Date: header, others want to sort messages by when they were received (INTERNALDATE). You should let the user choose this, and if user doesn't care about the received date, don't bother fetching INTERNALDATE.
Many clients wish to display an attachment icon in message list. Instead of fetching the entire message body, you can fetch BODYSTRUCTURE and see if it contains fields that identify an attachment.
Usually clients show some interesting message headers, message's plaintext body and attachment icons if there are any.
The most important thing is to avoid fetching the entire message body. If the message has 1MB attachment, there's no need to download it until user has expressed interest in opening or saving it. You can do this by parsing BODY or BODYSTRUCTURE reply and fetching only the MIME parts you're going to show.
If you already fetched BODY or BODYSTRUCTURE when showing message list, you can fetch everything with one command:
1 FETCH (RFC822.HEADER BODY.PEEK[1])
RFC822.HEADER can be used to fetch the whole header, which by this time should be pretty cheap since you're fetching the message body as well. However if you're interested of only a few specific headers (and you should already know some of them from message list!), you can fetch them instead:
1 FETCH (BODY.PEEK[HEADER.FIELDS (List-Id)] BODY.PEEK[1])
If you don't know the BODY or BODYSTRUCTURE before opening the message, you'll need two commands:
1 FETCH (RFC822.HEADER BODYSTRUCTURE)
* FETCH (...)
1 OK
2 FETCH (BODY.PEEK[1])
If you're fetching a large attachment, it's no excuse to have your user interface unresponsive until the download is complete. You have two choices to prevent this:
-
Create a new connection (or use another existing one), open the mailbox with EXAMINE (because some servers allow mailbox to be SELECTed only once) and download the attachment using it.
-
Fetch the attachment in pieces:
FETCH 1 BODY.PEEK[2]<0.102400>
, wait for OK,FETCH 1 BODY.PEEK[2]<102401.204800>
, etc. The block size should be large enough so that network latency doesn't add too much wasted time, but small enough that any other more important commands can be executed without too much delay.
In both of these cases it's useful to know the approximate latency and available bandwidth between the client and the server. If you know that simply sending a single FETCH command to download the entire message takes less time than using a separate connection, then there's no point in doing that. The block size can be also be dynamically adjusted so that each FETCH request takes for example a second.
You can calculate the latency easily by keeping track of how long it typically takes for server to answer commands that shouldn't be resource intensive (e.g. CAPABILITY, LOGIN, LIST). If you don't know the bandwidth, pick a small enough block size initially and keep growing it.
With high latency links (e.g. 0,3sec+) creating a second connection is probably a better idea than fetching in small blocks, because a lot of time can be wasted on waiting for the OK replies. With low latency links it's probably better to use partial fetches.
Synchronization Operations for Disconnected IMAP4 Clients RFC is useful for you to read.
Clients have two choices to look for new changes: polling, or waiting for server to send them with IDLE. With polling the client usually sends a NOOP command every few minutes. The client should also send CHECK command once in a while instead, e.g. every 15 minutes. With some servers only CHECK command checks for new changes.
If you use IDLE, make sure to break out of it every 29 minutes to make sure that the server doesn't kill the connection after 30 minutes of idling.
Don't send STATUS command to selected mailbox. It works more or less badly with many servers. Some simply refuse to respond to it, while with others it doesn't show the updated mailbox state (e.g. if you send only STATUS commands, new messages in selected mailbox aren't noticed). There should be no need for it anyway, you should know all the same information already (except UIDNEXT value).
IMAP4 Extensions for Quick Mailbox Resynchronization is a highly useful extension to implement.
The standard IMAP model for deleting messages is to first mark them with \Deleted flag and later issue EXPUNGE command, which deletes all the messages in the mailbox marked with the \Deleted flag. There are three commonly used models what IMAP clients do when a user hits "delete" key:
-
Marks the message with \Deleted flag and changes the UI to use strikethrough for the message's entry in the message list. To permanently delete the message user has to issue EXPUNGE command using some button/key.
-
Advantages: Deleting messages is quick and easy, but since it still requires two steps deleting a message can't happen accidentally.
-
Disadvantages: Since the messages marked with \Deleted are visible, user wants to issue EXPUNGE almost immediately to avoid seeing them. Later if user comes to regret the deletion there's no way to undelete a message.
-
-
Marks the message with \Deleted flag and hides the message from UI. There's also a way to toggle showing/hiding the \Deleted messages. To permanently delete the message user has to issue EXPUNGE command using some button/key.
-
Advantages: Same as with 1. Also user doesn't feel pressured to issue EXPUNGE immediately, since the messages aren't visible and distracting.
-
Disadvantages: The message gets hidden immediately when delete-button is clicked. If a user hits the button accidentally while not really paying attention to screen, there's no visible indication that it happened. User also may not realize that the messages still exist and that EXPUNGE would have be to be used once in a while to really get rid of them.
-
-
Copies the message to Trash mailbox and marks it with \Deleted flag. The UI is changed with either 1. or 2. way. To permanently delete the message user has to issue EXPUNGE command using some button/key AND click an "empty trash" button.
-
Advantages: Some users may be more familiar with this model, since it's the same as how files are deleted in Windows, OS X, etc. Deleted messages aren't visible normally, but they can be recovered from Trash mailbox.
-
Disadvantages: To delete a message permanently requires more work. When undeleting the original mailbox information gets lost and user has to explicitly specify what mailbox to copy the message to. Problematic when user is over quota and tries to delete a message: the message copying will fail with "Out of quota" error.
-
1 and 2 could perhaps be improved by having a key to hide all current existing \Deleted messages, without hiding future deletions. This could maybe be also done automatically for messages deleted some hours ago. Also perhaps some indication of how many messages are marked \Deleted.
Another possibility would be to have a virtual Trash mailbox containing all messages that are marked with \Deleted. This would basically be 3. but without having to actually copy the message. Undeletion can be done by just removing the \Deleted flag. This virtual Trash could be implemented by either client or server. Server-side virtual Trash of course requires that the server is capable of that (few are) and that administrator has configured it. Client-side virtual Trash's main problem is how to make sure it contains all \Deleted messages, especially if user uses multiple clients. Unless the client synchronizes all the mailboxes all the time, the only practical way to implement it is to update the Trash only for opened mailboxes.
Make sure you're able to handle multiple SEARCH replies returning messages non-ordered:
1 UID SEARCH ALL
* SEARCH 5 2
* SEARCH 1 6
1 OK
When searching non-ASCII text, you'll have to specify CHARSET parameter.
There is no way to list supported charsets, but you do get a
[BADCHARSET]
tag if the used charset isn't supported. So you can only
guess. UTF-8 should be supported by all servers nowadays, and there
probably isn't much point in trying to guess any others.
8bit parameters must be sent only using literals:
1 SEARCH CHARSET UTF-8 TEXT "pää"
1 BAD 8bit text in quoted string.
2 SEARCH CHARSET UTF-8 TEXT {5}
+ OK
pää
* SEARCH 3
2 OK