IMAP - notKamui/Kourrier GitHub Wiki

Connecting to an IMAP server

Providing you have the correct credentials, you can connect to a given IMAP mail server with the following function:

val session = Kourrier.imap(
    hostname = "mail.server.com",
    port = 993,
    username = "[email protected]",
    password = "1234",
    // debugMode = false,
    // enableSSL = true,
    // properties = Properties()
)

Do note that debugMode, enableSSL and properties are optional:

  • debugMode defaults to false, and serves to enable the debug mode for the current session.
  • enableSSL defaults to true, and serves to enable a Secure Socket Layer connection to the session.
  • properties is a Properties object containing the properties to be used to obtain a Session instance.

An incorrect hostname will lead to a KourrierConnectException. Incorrect credentials will lead to a KourrierAuthenticationException.

The function itself returns an opened session. You will need to close it manually when you're done with it. When it's closed, you can reopen it manually:

session.close()
session.open()

Note that if you try to close a session that is already closed, or open a folder that is already open, you'll hit a KourrierSessionStateException.

Alternatively, you can instead keep the connection information in a variable to pass it later as an argument of the function, like so:

val creds = KourrierConnectionInfo(
    hostname = "mail.server.com",
    port = 993,
    username = "[email protected]",
    password = "1234",
    // debugMode = false,
    // enableSSL = true,
)

Kourrier.imap(
    connectionInfo = creds,
    // properties = Properties()
)

Opening an IMAP folder

Now that you're connected, you can open folders an manipulate them using a neat DSL (Domain Specific Language).

To open open a folder, you just need its name, and it's opening mode:

val session = Kourrier.imap(...)
var inbox: KourrierFolder? = null
session {
    inbox = folder(
        name = "INBOX", 
        mode = KourrierFolderMode.ReadOnly, // KourrierFolderMode.ReadWrite,
        // listener = null,
        // keepAlive = false
    ) {
        /* do something inside the folder */
    }
}

Several things to notice here:

  • The opening mode is an enum value of KourrierFolderMode, and can either be ReadOnly or ReadWrite.
  • listener is an optional argument that sets the folder listener (defaults to null). We'll go over that later in the Folder listener section.
  • The function itself returns the opened folder. You will need to close it when you're done with it. When it's closed, you can reopen it manually:
    folder.close(expunge = false) // will expunge the folder when closed (defaults to false)
    folder.open(mode = KourrierFolderMode.ReadOnly)
    
    Note that if you try to close a folder that is already closed, or open a folder that is already open, you'll hit a KourrierFolderStateException.
  • keepAlive is an optional Boolean that indicates if one wants this folder to be kept alive forever until closed explicitly (defaults to false).
    • Being kept alive implies that it will be listened to, therefore, if you don't give any listener, there's little to no point for it to be kept alive.
    • Note that if the store/session is closed during that time, you might hit a KevalSessionStateException ; the session cannot be closed implicitly by the server.

Inside a folder

While inside a folder, you have access to a DSL and several properties of said folder:

session { 
    folder(...) { // `this` is a KourrierFolder
        messageCount // is the amount of messages in the folder
        unreadCount // is the amount of unread messages in the folder
        newCount // is the amount of new messages in the folder
        hasNewMessage // is true if the current folder contains at least one new message
        folderType // KourrierFolderType, indicates the content type of the folder

        prefetchBy(/* vararg FetchProfile.Item */) // sets the prefetch profile

        this[0] // returns the first message of the folder, or null
        this[0 until messageCount /*, prefetch = true */] // returns the list of message from index 0 to messageCount-1, with a prefetch

        sortedBy { /* ... */ } // uses the sort engine to return a list of messages
        search { /* ... */ } // uses the search engine to return a list of messages
    }
}

There's a lot of things to go over here:

  • folderType is a KourrierFolderType enum value, that can be HoldsFolders, HoldsMessages or HoldsAll.
  • prefetchBy receives one or several FetchProfile.Item (see JavaMail documentation) and combines them to set it as the next prefecth profile.
  • The IntRange version of the message getter has an optional Boolean prefetch that defaults to true, and idicates whether the batch should be prefetched or not.
  • We'll go over the sort and search engines in the next sections.

Sort engine (sortedBy)

The sort engine returns a list of messages ordered by the defined DSL. You have several sort options that you can even reverse:

import com.notkamui.kourrier.search.KourriersortTerm.*

session { 
    folder(...) { 
        sortedBy {
            +From // by email of the sender
            +To // by email of the recipient
            +Subject // by the subject
            +Arrival // by the arrival date
            +Sent // by the send date
            +CC // by email of the first CC recipient
            +Size // by size
        }
    }
}

The sort terms are enum values of KourrierSortTerm (thus you can static import them for readability).

These all order in the natural/increasing direction (alphanumerical order / natural date order), however, you can reverse the direction of the order by replacing the unary plus (+) by an exclamation point (!). For example, !Size adds a sort term by decreasing size (instead of increasing).

Search engine (search)

The search engine returns a list of messages following the defined DSL (note that you can call the sort engine inside the search engine too !):

session { 
    folder(...) { 
        search {
            markAsRead // defaults to false, can be set to true. Marks the search results as read

            hasSortTerms // whether sort terms have been applied, or not
            sortTerms // the list of sort terms that have been applied
            sortedBy { /*...*/ } // uses the sort engine, does not return anything

            +from("[email protected]") // search sender
            +to("[email protected]") // search regular recipient
            +cc("[email protected]") // search CC recipient
            +bcc("[email protected]") // search BCC recipient
            +subject("Foo Bar Baz") // search subject
            +body("Hello world") // search mail body content
            +header("My head") // search mail header content

            +receivedOn(Date(...)) // search received on date
            +receivedOnOrAfter(Date(...)) // search received on or after date
            +receivedAfter(Date(...)) // search received after date
            +receivedOnOrBefore(Date(...)) // search received on or before date
            +receivedBefore(Date(...)) // search received before date
            +receivedBetween(Date(...) .. Date(...)) // search received in date range

            +sentOn(Date(...)) // search sent on date
            +sentOnOrAfter(Date(...)) // search sent on or after date
            +sentAfter(Date(...)) // search sent after date
            +sentOnOrBefore(Date(...)) // search sent on or before date
            +sentBefore(Date(...)) // search sent before date
            +sentBetween(Date(...) .. Date(...)) // search sent in date range

            +messageID(0) // search by message ID
            +messageNumber(0) // search by message number

            +sizeIs(0) // search with exact size
            +sizeIsAtLeast(0) // search with size or larger
            +sizeIsLargerThan(0) // search with larger size
            +sizeIsAtMost(0) // search with size or smaller
            +sizeIsSmallerThan(0) // search with smaller size
            +sizeBetween(0..10) // search with size between range

            +flags(KourrierFlags(...), true) // search with `KourrierFlags`. Boolean is is true if the search contains flags, false for exclusion.

            +modifiedSince(0L) // search with modification sequence (Long value)
            +older(0) // search messages older than the given time (in seconds)
            +younger(0) // search messages younger than the given time (in seconds)

            +(from("A") and to("B")) // search with two predicates that have to be true
            +(from("A") or to("B")) // search with two predicates with at least one true
        }
    }
}

Note that the flags term receives a built KourrierFlags. You can build a set of flags with the constructor that receives a vararg KourrierFlag, which is an enum value that can be Answered, Deleted, Draft, Flagged, Recent, Seen, or User.

You can inverse any search term by replacing the unary plus (+) with an exclamation point (!). For example !from("[email protected]") will search all messages EXCEPT those from [email protected].

Messages

Once you fetched IMAP messages, you'll be presented with instances of KourrierIMAPMessage. They contain several properties:

  • uid, the UID of the message
  • from, the address of the sender
  • headers, a list of KourrierMessageHeaders (which themselves have a name and a value)
  • subject, the actual subject of the message
  • body, the full body content of the message
  • bodyParts, the full body content separated by body parts in a list (convenient for MimeMessages that are multipart)

Folder listener

If you recall, when opening a folder, there is an optional listener parameter. It is of type KourrierFolderListener, which is an interface composed of 4 methods (which all take a KourrierIMAPMessage as a parameter):

  • onMessageReceived that will be launched for each message received in the folder.
  • onMessageRemoved that will be launched for each message removed (as in, expunged. Do note that it is NOT synonymous to "deleted")
  • onMessageFlagsChanged that will be launched for each message which has its flags updated.
  • onMessageEnvelopeChanged that will be launched for each message which has its headers (but not body) changed.

A generic open class KourrierFolderAdapter is also available.

For example:

val listener = object : KourrierFolderAdapter() {
    override fun onMessageReceived(message: KourrierIMAPMessage) {
        println(message.bodyParts[0])
    }
}

session {
    folder("INBOX", KourrierFolderMode.ReadWrite, listener)
}

This will print the first body part of each message received until the folder is closed.

Keep in mind that some mail servers require the folder to be open in ReadWrite mode for it to be listenable !