11. Email: Processing email messages

GNATColl provides a set of packages for managing and processing email messages. Through this packages, you can extract the various messages contained in an existing mailbox, extract the various components of a message, editing previously parsed messages, or create new messages from scratch.

This module fully supports MIME-encoded messages, with attachments.

This module currently does not provide a way to send the message through the SMTP protocol. Rather, it is used to create an in-memory representation of the message, which you can then convert to a string, and pass this to a socket. See for instance the AWS library) which contains the necessary subprograms to connect with an SMTP server.

11.1. Message formats

The format of mail messages is defined through numerous RFC documents. GNATColl tries to conform to these as best as possible. Basically, a message is made of two parts:

The headers

These are various fields that indicate who sent the message, when, to whom, and so on

The payload (aka body)

This is the actual contents of the message. It can either be a simple text, or made of one or more attachments in various formats. These attachments can be HTML text, images, or any binary file. Since email transfer is done through various servers, the set of bytes that can be sent is generally limited to 7 bit characters. Therefore, the attachments are generally encoded through one of the encoding defined in the various MIME RFCs, and they need to be decoded before the original file can be manipulated again.

GNATColl gives you access to these various components, as will be seen in the section Parsing messages.

The package GNATCOLL.Email.Utils contains various subprograms to decode MIME-encoded streams, which you can use independently from the rest of the packages in the email module.

The headers part of the message contains various pieces of information about the message. Most of the headers have a well-defined semantics and format. However, a user is free to add new headers, which will generally start with X- prefix. For those fields where the format is well-defined, they contain various pieces of information:

Email addresses

The From, TO or CC fields, among others, contain list of recipients. These recipients are the usual email addresses. However, the format is quite complex, because the full name of the recipient can also be specified, along with comments. The package GNATCOLL.Email.Utils provides various subprograms for parsing email addresses and list of recipients.

Dates

The Date header indicates when the message was sent. The format of the date is also precisely defined in the RFC, and the package GNATCOLL.Email.Utils provides subprograms for parsing this date (or, on the contrary, to create a string from an existing time).

Text

The Subject header provides a brief overview of the message. It is a simple text header. However, one complication comes from the fact that the user might want to use extended characters not in the ASCII subset. In such cases, the Subject (or part of it) will be MIME-encoded. The package GNATCOLL.Email.Utils provides subprograms to decode MIME-encoded strings, with the various charsets.

11.2. Parsing messages

There are two ways a message is represented in memory: initially, it is a free-form String. The usual Ada operations can be used on the string, of course, but there is no way to extract the various components of the message. For this, the message must first be parsed into an instance of the Message type.

This type is controlled, which means that the memory will be freed automatically when the message is no longer needed.

The package GNATCOLL.Email.Parser provides various subprograms that parse a message (passed as a string), and create a Message out of it. Parsing a message might be costly in some cases, for instance if a big attachment needs to be decoded first. In some cases, your application will not need that information (for instance you might only be looking for a few of the headers of the message, and not need any information from the body). This efficiency concern is why there are multiple parsers. Some of them will ignore parts of the message, and thus be more efficient if you can use them.

Once a Message has been created, the subprograms in GNATCOLL.Email can be used to access its various parts. The documentation for these subprograms is found in the file gnatcoll-email.ads directly, and is not duplicated here.

11.3. Parsing mailboxes

Most often, a message is not found on its own (unless you are for instance writing a filter for incoming messages). Instead, the messages are stored in what is called a mailbox. The latter can contain thousands of such messages.

There are traditionally multiple formats that have been used for mailboxes. At this stage, GNATColl only supports one of them, the mbox format. In this format, the messages are concatenated in a single file, and separated by a newline.

The package GNATCOLL.Email.Mailboxes provides all the types and subprograms to manipulate mailboxes. Tagged types are used, so that new formats of mailboxes can relatively easily be added later on, or in your own application.

Here is a small code example that opens an mbox on the disk, and parses each message it contains:

declare
  Box  : Mbox;
  Curs : Cursor;
  Msg  : Message;
begin
  Open (Box, Filename => "my_mbox");
  Curs := Mbox_Cursor (First (Box));
  while Has_Element (Curs) loop
     Get_Message (Curs, Box, Msg);
     if Msg /= Null_Message then
        ...
     end if;
     Next (Curs, Box);
  end loop;
end;

As you can see, the mailbox needs to be opened first. Then we get an iterator (called a cursor, to match the Ada2005 containers naming scheme), and we then parse each message. The if test is optional, but recommended: the message that is returned might be null if the mailbox was corrupted and the message could not be parsed. There are still chances that the next message will be readable, so only the current message should be ignored.

11.4. Creating messages

The subprograms in GNATCOLL.Email can also be used to create a message from scratch. Alternatively, if you have already parsed a message, you can alter it, or easily generate a reply to it (using the Reply_To subprogram. The latter will preset some headers, so that message threading is preserved in the user’s mailers.