Building an Email Client in Director
November 19, 2004
by James Newton
Source files: pop3.zip (Windows) | pop3.sit (Macintosh).
The movie above provides the bare bones of an email client that you can use to download messages. It uses an instance of the Multiuser Xtra to connect to a POP3 mail server. The above Shockwave movie refers to a non-existant email server, but you can modify the default parameters to connect to any mailbox that you have access to.
Even if you don't intend to build your own customised email client, this article can help you understand how your favourite email program works.
TechNote 14182 "Sending e-mail from Director using the Multiuser Xtra" on Macromedia's site explains how to upload an email message to a mailbox, so I won't deal with that aspect here.
Note: When you click on the Connect button in the movie above, you should see a security alert. The Shockwave player warns you that you are trying to connect to a server in a different domain from the current html page. This is a standard security precaution and here is no cause for alarm: you need to log on to a different domain in order to download mail.
In this article, we'll look at:
- Email addresses and mailboxes
- Post Office Protocol 3 (POP3)
- Logging in to a mailbox
- Querying the mailbox
- Logging off and deleting messages
- Optional commands for POP3 servers
- Handling longer messages
- Using the Multiuser Xtra
- Connecting to a POP3 server
- Sending commands to the POP3 server
- Dealing with packets
Email addresses and mailboxes (top)
Mailboxes are often known by the server as simple names, like "testAccount". The email address of the mailbox, as far as the rest of the world is concerned, might be "test / at / invalidserver.com". To send a message to the mailbox, you need to use the second form, with its distinctive " / at / " character. To retrieve mail from it, you need to know what the server itself calls the mailbox. This duality adds protection. The two names need not be different, but if they are it is so much more difficult for an unscrupulous third party to gain access to your personal mail.
Post Office Protocol 3 (top)
You can find the official specifications of the POP3 protocol at http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc1939.txt. Though the main purpose of this document is to describe how a POP3 server should react to different commands it also provides all the information that you need to communicate with an existing server. The document is clear and detailed, so I will only provide a summary here.
To resume the official document:
- Each connection to a POP3 server passes can through 3 states: authorisation, transaction and update. Only commands relative to the current state can be treated.
- All commands start with a four-letter command, perhaps followed by one or more parameters, and terminate with a carriage-return-line-feed character pair:
RETURN&numToChar(10)
. In the examples below, this character pair is indicated by the _ character - A positive response from the POP3 server always starts with "+OK"; a negative response always starts with "-ERR"
- Messages on the server are numbered from 1 to n, in the order in which they are received. You can specify a given message by using its index number.
- Messages are sent in packets. If a message is long, it will arrive as several packets which will need to be stitched back together in the right order on receipt.
The Authorisation State (top)
Once you have logged on to the server, you need to identify yourself. This is done by sending the following two commands, in this order:
USER nameOfMailBox_
PASS password_
You need to wait for a positive response from the POP3 server to the USER command before sending the PASS command. The response may be positive even if a mailbox with that name does not exist. Only once the server has received both user name and password information will it check if you have provided the right data. This prevents hackers for sniffing first for the name of a mailbox, and then testing possible passwords for that mailbox.
In the examples below and in the movie above, the characters "<= " indicate an outgoing message and "=> " indicates an incoming message. The messages involved in logging in to the test mailbox are as follows:
<= ConnectToNetServer(" ", " ", "invalidserver.com", 110, " ") => +OK <= USER testAccount_ => +OK <= PASS password_
=> +OK
The replies may in fact be more explicit than those shown above, but any additional text may vary from server to server.
The Transaction State (top)
Once you have gained access to a given mailbox, you can ask for information about the waiting messages, download entire messages or just the header, and flag given messages for deletion. Messages will not be deleted until you log off, and even then, only if you log off correctly. There are 6 commands you can use in the Transaction state:
- STAT tells you how many messages are waiting, and the total size of the waiting messages
- LIST gives you details on the size of individual messages
- RETR downloads an entire message
- DELE instructs the server to prepare to delete a message
- RSET instructs the server not to delete a message flagged by DELE
- NOOP sends a "NO OPeration" message, which can be used to prevent the server from logging the client out automatically after a time-out delay
The Update State (top)
The server will delete no messages until it receives a QUIT command. This ensures that messages will not be deleted unexpectedly if the connection is broken. Using the movie above, try the following steps:
- Send one or more messages to the mailbox you are using for your tests
- Log on with the email.dir movie, using the appropriate parameters
- Use the DELE command to flag one or more messages for deletion
- Click on Disconnect without sending a QUIT message
- Try to reconnect.
You may find that reconnection is impossible for at least 10 minutes, because the server will have received no notification of the disconnection. It will hold the connection open for a default time-out period and then, after receiving no commands for that time, will unilaterally close the connection.
You will then be able to reconnect. When you do, you will see that the messages marked for deletion are still there. Send the DELE command again for one or more messages, then send the QUIT message. This time the server will break the connection, though the Multiuser Xtra instance may not know this. Try logging on again. This time you should be able to connect immediately. The messages you flagged for deletion will be gone.
Optional POP3 Commands (top)
The TOP command (only 3 characters) returns the header information for a given message. You can send an optional integer parameter: this will return the given number of lines of the message itself along with the header.
The UIDL command returns a Unique ID Listing string for the chosen message.
The APOP command can be used to set up an encrypted connection. This requires special settings on the server, and so the email.dir movie above does not handle this.
Longer messages (top)
Long messages are divided into smaller packets before being sent. The end of a message is indicated by the string CRLF.CRLF: carriage-return-line-feed-dot-carriage-return-line-feed. Until this string has been received, the email client should consider all incoming data to be part of the same message. Since the message itself may include the string CRLF.CRLF, such a string is modified before being sent. The email client should recognise the modification and rectify it.
If you try sending a long message to your test mailbox, you will see it coming back as separate packets in the console field, each packet starting with the "=> " string. This string is added by the movie to show where each incoming packet starts. It is not part of the message itself.
Using the Multiuser Xtra (top)
The email.dir movie is extremely simple. Each button contains a Cast Member Script which reacts to a mouseUp. The only other script is a Movie Script containing three short handlers.
On startMovie, a global instance of the Multiuser Xtra is created, and a default call back handler is declared:
global gMUInstance
on startMovie
gMUInstance = xtra("Multiuser").new()
gMUInstance.setNetMessageHandler(#messageReceived, 0, "", "", 1)
end startMovie
Communicating with a server occurs in an asynchronous fashion: you send a message and then wait for the reply. The reply may be almost instantaneous or it may take several seconds. The Multiuser Xtra receives replies as they come in, then calls the appropriate handler defined via the setNetMessageHandler()
method. The parameters used above are the simplest. They tell the Multiuser Xtra instance to call the on messageReceived()
handler in a Movie Script, regardless of who sends the message or what it's about, and to pass the message itself as a parameter of the call.
After passing through the Multiuser Xtra instance, an incoming message will be a list with the following properties (plus a number of others):
[#errorCode: 0, #content: <the string packet sent from the server>]
If the errorCode is not zero then an error will have occurred. In this context, this is most likely to happen if you try to log on to an invalid server. The error code in question will be a very large negative number. You can use the getNetErrorString()
method to provide a human-readable explanation of the error.
If no error occurs, the incoming message is displayed in the console, preceded by the characters "=> ".
on messageReceived(me, incomingMessage)
if incomingMessage.errorCode then
beep
put incomingMessage
put gMUInstance.getNetErrorString(incomingMessage.errorCode)
else
put RETURN&"=> "&incomingMessage.content after field("Console")
end if
end messageReceived
Connecting to a POP3 server (top)
A click on the Connect button uses the instance of the Multiuser Xtra to open a connection:
on mouseUp
global gMUInstance
ip = field("IP address")
port = 110
-- "user", "pass" and "pop3" are arbitrary values which are not
-- sent to the distant pop3 server
gMUInstance.connectToNetServer("user", "pass", ip, port, "pop3", 1)
messageSent("Connecting to port "&port&" of "&ip)
end mouseUp
In this case, the connectToNetServer()
method requires 6 parameters, but only 3 are pertinent. The POP3 server treats user names and passwords separately from opening the connection, and has no use for a movieID parameter. In the handler above, the string values will be ignored, but the Multiuser Xtra instance will indicate an error if the strings are missing.
The ip address and port number parameters are essential, and the 1 in the sixth parameter indicates that the connection is to be made in text mode. There are a number of different types of syntax that you can use with the connectToNetServer()
method: if you use a different syntax, you may need to use the symbol #text instead of the integer 1.
The POP3 server should reply "+OK".
The messageSent()
handler simply displays the essence of the outgoing message in the console:
on messageSent(outgoingMessage)
if charToNum(the last char of outgoingMessage) = 10 then
delete the last char of outgoingMessage
end if
put "<= "&outgoingMessage after field("Console")
end messageSent
Sending commands to the POP3 server (top)
Once the connection is established, you can send commands to the server. These commands will be ignored unless they end with the CRLF character pair. Here is the handler from the USER button:
on mouseUp
global gMUInstance
content = "USER "&field("user")&RETURN&numToChar(10)
errorCode = gMUInstance.sendNetMessage("system", " ", content)
if errorCode then
errorString = gMUInstance.getNetErrorString(errorCode)
messageSent(errorString)
else
messageSent(content)
end if
end mouseUp
The messages are sent to the server itself, not to any other users who may be connected to the server. The recipient of the sendNetMessage()
method must therefore be "system"
. The subject of the message is unimportant (I use " " above), and the command itself, along with the CRLF pair is sent as the content of the message.
Some commands take an alternative syntax. For example, the LIST command may take an optional integer message number as a parameter, so the command must be formatted correctly:
messageNumber = field("list message number")
if messageNumber = "" then
content = "LIST"&RETURN&numToChar(10)
else
content = "LIST "&messageNumber&RETURN&numToChar(10)
end if
errorCode = gMUInstance.sendNetMessage(" ", " ", content)
Apart from that, the code is very straightforward.
Dealing with packets (top)
The only remaining difficulty is to concatenate the incoming packets correctly. The handler below is not included in the bare-bones pop3.dir movie. It seeks out the character string that identifies the end of a message and removes it. If the termination string is not found in the packet, the property moreToCome
is set to TRUE
to indicate that the incoming message continues in the next packet.
property moreToCome
on stripTerminationOctets(me, messageData) ---------------------------
-- Extract from the document http://www.faqs.org/rfcs/rfc1939.html
--------------------------------------------------------------------
-- When all lines of the response have been sent, a final line is
-- sent, consisting of a termination octet (decimal code 046, ".")
-- and a CRLF pair. If any line of the multi-line response begins
-- with the termination octet, the line is "byte-stuffed" by pre-
-- pending the termination octet to that line of the response.
-- Hence a multi-line response is terminated with the five octets
-- "CRLF.CRLF". When examining a multi-line response, the client
-- checks to see if the line begins with the termination octet. If
-- so and if octets other than CRLF follow, the first octet of the
-- line (the termination octet) is stripped away.
--------------------------------------------------------------------
lineCount = messageData.line.count
lineFeed = numToChar(10)
repeat with i = 1 to lineCount
theLine = messageData.line[i]
if theLine.char[1] = linefeed then
-- Delete the line feed
delete messageData.line[i].char[1]
if theLine.char[2] = "." then -- termination octet
if theLine.char.count = 2 then -- the next char is CR
if messageData.line[i + 1]Char[1] = numToChar(10) then
-- CRLF.CRLF: We have reached the end of the message
moreToCome = FALSE
return messageData.line[1..i - 1]
end if
else
-- Remove the byte-stuffed character
delete messageData.line[i]Char[1]
end if
end if
end if
end repeat
-- The "CRLF.CRLF" termination block was not encountered
moreToCome = TRUE
return messageData
end stripTerminationOctets
You can find a movie that uses this handler here.
Conclusion
Building an email client in Director is easy... at least insofar as the communications with POP3 server are concerned. What makes one email client better than another is the way in which it allows you to classify your incoming and outgoing mail, set up filters, display images and html, connect to URLs and so on. With the numerous free and inexpensive email clients available, there is no obvious need to write a powerful email client from scratch. However, if for a specific project you need to take complete control of one specific aspect of email communication, Director and the Multiuser Xtra give you the means to do so.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.