From time to time I am in the unfortunate situation of having to manually communicate with an IMAP server (in other words: reading mail via telnet).
Due to the nature of IMAP this is not remotely as simple as reading mail via telnet using the POP3 protocol, however, as IMAP is a very rich and powerful protocol with a quirky syntax.
As I tend to forget the commands for the most important tasks it might be a good idea to write them down.
Some definitions:
IMAP handles messages. Messages live in folders, which can have subfolders. Folders are separated by separators. Multiple groups of folders can exist, those groups are called namespaces. At least one namespace always exists. Within every folder each message has two identifiers (both are positive integers). The first (the sequence number) is valid only as long as the current folder is selected (or open, in other words), and ranges from 1 to N, N being the number of messages in the folder. The second (the UID) does not change from one selection to the next, and usually not between connects. Ideally, the UID for a message never changes once it has been assigned. The IMAP server is free to assign a new UID to a message, but it must tell the client if it does so.
Each request from a client starts with a tag, which is a group of characters consisting of letters, numbers and the dot ("."). The server reply consists of at least one line, but may consist of several. In the latter case, each line starts with an asterisk (*), except for the last, which starts with the tag chosen by the client. This signals the completion of the command. If the server reply is one lined, only the line starting with the client tag is sent. The client may reuse tags if it wishes. The protocol is not synchronous, the client can send several requests without waiting for the server to complete the preceding command.
Unless the client or the server indicate otherwise the default character set for IMAP is UTF7 (which, as long as you keep to the first 128 characters of the ASCII character set, is exactly the same as ASCII or UTF8).
Requests and replies consist of a space separated list of keywords and strings. Strings can be written in two forms, quoted and literal. Quoted strings can consist of any 7-bit-characters, except CR
and LF
, enclosed by "
. If the quoted string contains the character "
itself it
must be quoted as \"
.
Literal strings start with the number of characters in the string, enclosed by curly braces, and a CRLF
. The string characters then follow.
That ought to be enough to make sense of the following:
h3. Login
Assuming the server supports plain text logins (indicated by AUTH=LOGIN
in the server greeting:
$ telnet mailserver 143
[...]
* OK [CAPABILITY IMAP4 IMAP4rev1 LITERAL+ ID AUTH=LOGIN AUTH=PLAIN AUTH=CRAM-MD5 SASL-IR]
mailserver Cyrus IMAP4 v2.3.7-Invoca-RPM-2.3.7-7.el5_4.3 server ready
foo login user password
foo OK [CAPABILITY IMAP4 IMAP4rev1 LITERAL+ ID LOGINDISABLED ACL RIGHTS=kxte QUOTA MAILBOX-REFERRALS
NAMESPACE UIDPLUS NO_ATOMIC_RENAME UNSELECT CHILDREN MULTIAPPEND BINARY SORT SORT=MODSEQ
THREAD=ORDEREDSUBJECT THREAD=REFERENCES ANNOTATEMORE CATENATE CONDSTORE IDLE LISTEXT
LIST-SUBSCRIBED X-NETSCAPE URLAUTH] User logged in
In this example the login user name was user
and the password was password
. The tag chosen by the client (i.e. the person using telnet) was foo
, which was echoed by the server in the login response. From now on the tag used will be the dot ("."), unless specified otherwise.
h3. Namespaces
Several groups of folders can exists, these groups are called namespaces. One use is the implementation of shared folders such that the private folders of a user live in one namespace, and the shared folders in another. To list the available namespaces:
. NAMESPACE
* NAMESPACE (("INBOX." ".")) (("user." ".")) (("" "."))
. OK Completed
This user has access to three namespaces: INBOX
, user
and a namespace without a name. The latter is the default name space. The dot (".") after the name is the separator used in this namespace.
h3. Listing folders
Listing folders within a namespace requires the namespace to be listed, and a pattern describing the required names. The pattern supports wildcards, especially "*" (list subfolders, recursively) and "%" (list subfolders, not recursively).
. LIST "" "INBOX.%"
* LIST (\HasNoChildren) "." "INBOX.Folder1"
* LIST (\HasNoChildren) "." "INBOX.Folder2"
* LIST (\HasChildren) "." "INBOX.Folder3"
. OK Completed
This INBOX
folder has three subfolders: Folder1
and Folder2
, both of which have no subfolders, as indicated by the \HasNoChildren
flag, and one (Folder3
) which has. Because of the "%" wildcard the subfolders of Folder3
are not shown in this listing.
In general, it is usually not a good idea to list folders using "*". This may return a list containing potentially thousands of folders (think of systems redistributing Usenet news via IMAP). Instead use "%" to descend the folders considered interesting.
h3. Selecting folders
In order to read messages the folder containing those must be activated first. This requires the full folder name as returned by LIST
.
. SELECT "INBOX"
* FLAGS (\Answered \Flagged \Draft \Deleted \Seen NonJunk Junk $NotJunk $Junk $Forwarded)
* OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen NonJunk Junk $NotJunk $Junk $Forwarded \*)]
* 5966 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1136990532]
* OK [UIDNEXT 12498]
* OK [NOMODSEQ] Sorry, modsequences have not been enabled on this mailbox
. OK [READ-WRITE] Completed
This folder contains 5966 messages (5966 EXISTS
), zero of which are unread (0 RECENT
). The UIDVALIDITY
parameter is an integer describing the validity of the UID numbers assigned to the messages. As long as this number does not change the mapping from message to UID has not changed.
h3. Finding messages
Unlike POP3 IMAP servers actually try to parse the messages stored in the folders in order to extract some information from the headers, such as sender address, recipient address, messageid and general message structure (such as attachments). The reason and upshot of this is that the server can search for messages having certain properties (for example, all messages by a certain sender) without having the client download all messages and doing the search itself. There are two search commands (SEARCH
and UID SEARCH
) which differ in the results they return. The first command returns sequence numbers, the second returns message UIDs.
Multiple search conditions can be used in one search request, those are ANDed (i.e., all have to be satisfied).
A small table of possible search conditions:
|_. Query |_. Looking for |_. Example |
| FROM ""
| Mail from that sender | FROM "user@example.org"
|
| TO ""
| Mail to that recipient | TO "user@example.org"
|
| SINCE
| Mail received after this date | SINCE 1-Nov-2009
|
| BEFORE
| Mail received before this date | BEFORE 1-Nov-2009
|
| DELETED
| Mails marked as deleted | DELETED
|
| SUBJECT
| Mails containing string in the subject | SUBJECT "Proposal"
|
| BODY
| Mails containing string in the body | BODY "Hello Greg"
|
| NOT
| Mails which do not match the key | NOT FROM "user@example.org"
|
| OR
| Mails which match either of key1 or key2 | OR FROM "user@example.org" FROM "user2@example.org"
|
There are quite a bit more of these, "RfC 2060":http://www.faqs.org/rfcs/rfc2060.html lists all possible options. But the ones above are probably the most commonly used.
Please be aware that the full text searches (TEXT
and BODY
) can be probibitively expensive if the server does not keep a full text search database of the messages. Getting an answer to such a query may take a very long time.
. SEARCH FROM "user@example.org" BEFORE 1-Nov-2009
* SEARCH 5 10 456
. OK Completed
h3. Fetching messages
Now that SEARCH
has turned up some messages it might be a good idea to take a look at the contents. The FETCH
command takes a list of sequence numbers or UIDs (as with SEARCH
there are two variants, FETCH
and UID FETCH
) and a list of the information we are interested in. The most commonly used parts are:
|_. Part name |_. Part description |
| BODY[TEXT]
| Just the mail body, without the headers |
| BODY[HEADER]
| The mail headers |
| BODY[HEADER.FIELDS ()]
| Just the header fields indicated in list |
| BODY[]
| The entire mail text, header and body |
| BODY.PEEK
| Works as BODY
does, but does not mark the mail as seen |
| FLAGS
| Flags set for the message |
| UID
| The UID of the message |
As above, RfC 2060 has all the gory details.
. FETCH 5 (FLAGS BODY[HEADER.FIELDS (To)])
* 5 FETCH (FLAGS (\Seen) BODY[HEADER.FIELDS (To)] {24}
To: user@example.com
)
. OK Completed
h3. Deleting messages
Deleting messages in IMAP is a bit tricky, as there is no explicit delete command. Instead, a flag is set on the message marking it as deleted. This, by itself, does nothing to get the message removed. Just when a special command is called all messages in the current folder marked as to be deleted are removed[1].
. UID SEARCH ALL
* 1 EXISTS
* 1 RECENT
* SEARCH 1814
. OK Completed
. UID STORE 1814 +FLAGS (\Deleted)
* 1 FETCH (FLAGS (\Recent \Deleted \Seen) UID 1814)
. OK Completed
. EXPUNGE
* 1 EXPUNGE
* 0 EXISTS
* 0 RECENT
. OK Completed
. UID SEARCH ALL
* SEARCH
. OK Completed
The above is executed in a folder containing just a single message (see the result of the UID SEARCH ALL
). The flag \Deleted
is then added to flag list of the message (UID STORE 1814 +FLAGS (\Deleted)
). The STORE
command returns the new flag list. The EXPUNGE
command then removes the message.
h3. Leaving IMAP
When finished with the session the last thing to do is to leave:
. logout
Connection closed by foreign host
$
fn1. The manual page for the rather excellent perl module Mail::IMAPClient
had the following to say about this:
bq. In case you’re curious, expunging a folder deletes the messages that you thought were already deleted via "delete_message" but really weren't, which means you have to use a method that doesn't exist to delete messages that you thought didn't exist. (Seriously, I'm not making any of this stuff up.)
Unfortunately this gem has disappeared from newer versions of the manual page.