Articles Archive
Articles Search
Director Wiki
 

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 (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:

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:

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:

  1. Send one or more messages to the mailbox you are using for your tests
  2. Log on with the email.dir movie, using the appropriate parameters
  3. Use the DELE command to flag one or more messages for deletion
  4. Click on Disconnect without sending a QUIT message
  5. 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.

James Newton started working with Director 5 in 1997. He wrote many of the behaviors that ship with Director since version 7. James lives in Dunoon, near Glasgow, Scotland. His company, OpenSpark Interactive, is responsible for marketing PimZ OSControl Xtra. When not coding he can be found racing his classic Flying Fifteen around the Holy Loch, making things with his four children, or catching escaped hamsters.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.