By Timo Sirainen <[email protected]>, comments welcome.
Updates by Dave Cridland
NOTE: This HOWTO has been rewritten as Best Practices for Implementing an IMAP Client
These are the most important issues to understand, or your implementation will mostly likely be completely broken and the only way to fix it would be a complete rewrite:
Use token-based parsing. It quite likely makes it easier for you. Many commands can send atoms, quoted-strings or literals as parameters and you'll have to be able to handle all of them. Literals sent by the server are almost always small and it's easier for you to just read them into memory and handle them the same way as quoted-strings. Exceptions are the replies for FETCH BODY[], RFC822, RFC822.HEADER and RFC822.TEXT which can be large, try to avoid reading them into memory.
Note that you can't really assume any size limits, the size of the returned replies depends on the size of mailbox and other things. You might however want to do at least some sanity checks for them, such as not allowing any token to be larger than 10MB or so.
Miscellaneous points that you should get right:
LIST "" *
. You may end up listing
thousands of mailboxes as a result. The correct way to do it is LIST
"" %
and list child mailboxes only when the user requests them. If
you need to know if the mailboxes have children, there are several ways to
do it:
LIST "" %/%
lists all the immediate child mailboxes.
This is the only way that works with all the servers.LIST (CHILDREN) "" %
and again pick the \HasChildren and \HasNoChildren flags.LIST "" mailbox/%
.LIST "" *
:
* LIST (\UnMarked) "." "foo.bar" * LIST (\Marked) "." "foo" * LIST () "." "baz" * LIST (\UnMarked \NoSelect) "." "baz"
SELECT mailbox, STATUS mailbox
behavior isn't well
defined. Don't do STATUS for a selected mailbox. Except for UNSEEN
count, you should get all that information as a reply to SELECT. If you
really want to know the UNSEEN count as well, use SEARCH
UNSEEN
. Even that has to be done only once, since the server keeps
you updated of message flag changes automatically. UIDNEXT changes aren't
notified however, if you care about them. That's a bit problematic
case.malloc(literal_size+1)
, it may overflow to
malloc(0)
. The same problem happens with
malloc(messages_count * sizeof(struct message))
. See
here and
here
for more information.Shared -
, and the LIST will give you
Shared - mailbox
. The mailbox's name is
mailbox
.1 STATUS INBOX (MESSAGES UNSEEN RECENT) 2 STATUS foo (MESSAGES UNSEEN RECENT) 3 STATUS bar (MESSAGES UNSEEN RECENT) * STATUS "INBOX" (MESSAGES 3 RECENT 2 UNSEEN 3) * STATUS "bar" (MESSAGES 5 RECENT 1 UNSEEN 1) 1 OK * STATUS "foo" (MESSAGES 20 RECENT 0 UNSEEN 0) 3 OK 2 OK
Optionally support multiple connections to server. Depending on the server, this might be a huge performance benefit. If the user mostly switches between three mailboxes, keeping each one of them in a separate connection would require selecting the mailbox only once and leaving them open. This also makes it possible for the server to notify the client immediately about changes in those mailboxes.
Note that this also has many potential problems that you should consider, although quite unlikely to ever occur. For example different connections to the same the server might actually have different capabilities because the server software was just upgraded or some configuration setting was changed. Or maybe you were just redirected to another server in a cluster, which had slightly different settings. Maybe the server limits the number of IMAP connections per user, and kills one of your old connections while you create a new one. Or maybe it doesn't let the new one in at all.
Not that much IMAP specific, but things you should consider:
Try not to fetch the entire mailbox's metadata. If the user sees only 20 newest messages in the screen, there's no reason to ask about any others from the server. If the user has a slow network connection and a large mailbox, it can be frustrating to wait for headers of messages to download which are never seen.
This shouldn't be too difficult to implement even with standard GUI toolkits. You know the number of messages in the mailbox, so create that many dummy items (eg. empty or "wait..") in your messages list. When the user scrolls for more data, fetch the data for the messages that are then visible. The user may see the dummy items temporarily in the screen until the server has replied, but with some smart prefetching you can avoid that for most cases (ie. download a bit more than just the visible messages). You could also optionally fetch all the other messages as well, but do it in smaller blocks on the background so the user doesn't have to wait.