A Little Rant
I love ThunderBird, I really do. I only have one problem with it. I have not yet found an e-mail client as good as thunderbird, and every e-mail client that I have entertained have all come short of one specific attribute that I really like. You can set up just about any mail client to check your inbox for new messages at a given length of time, which is great, but the restriction that you have to leave the e-mail client open the whole time, gets under my skin.
I hate having a cluttered taskbar, with things open that don't really need to be. At least with the system tray, it's in a way, tucked aside and out of the way of other running tasks. Why should I be obligated to have my e-mail client cluttering up my task bar while I'm writing code, or surfing the web? The simple answer is... I shouldn't.
The first step is making sure we can make our program talk to the mail server. We are going to use POP3 protocol, but, before we can start making our program get on the internet, we need a special control, called a socket. There are a couple of these, one which comes with windows, and is available to VB. This control doesn't offer as much power, as many options or as much flexibility as the control that I am going to use in this tutorial. The socket control that I will be using is
Now, As stated previously, the socket allows us to send and recieve data from the internet. It's our gateway to programming with a network. Even though we can connect to a server with this control... we certainly have no idea what to say to the POP3 server when we get connected, or what it will say to us. In order to resolve this issue, we have a wealth of protocol information available to us, in what is known as an RFC. The RFC that is relevant to the POP3 Mail Protocol is RFC 1725, and is available at: http://www.faqs.org/rfcs/rfc1725.html
Ok, We Know what we need to connect to the internet, and we now have the information at our fingertips on how to talk to a POP3 server. The next step is to create the interface, but since we want to make sure that we can talk to the server, and find out how many e-mails are waiting in our inbox, we'll use a real plain interface. This can be arranged in any way you'd like, but there are some things that we are going to need on the form. We'll Need :
- 4 Text Boxes (and labels to identify them)
- 2 Buttons
- 1 SocketWrench Socket Control
My Form Looks Like This:
Coding The First Step
I've left the names to the label's and textbox's default, however, I did choose to name the socket control. I named it MailSock.
We Need To declare a few form wide variables. We know we'll need a variable to contain the username that the user typed, and we'll need for the password too. Then, We'll need variables to contain the information that we read from the socket, and write to the socket. We'll also need a variable that helps us keep track of what command we are on in the POP3 Protocol (What Part of the conversation we are in with the server). So, Let's Put This In our declaration section of the form:
' /* Declare All Of Our Variables */ Public Uname As String Public Pass As String Public strBuffer As String Public toSend As String Public Lastcmd As String
The First Thing that any program does, is start up, so we'll start there. In our Form Load Procedure, We Are Going To Need To get our socket control set up. I'm not going to go into great detail about this procedure, as the entire project is commented line for line, but here is the form load procedure:
' /* Set The Socket To Automatically Convert Names To IP's */ MailSock.AutoResolve = True ' /* Blocking Stops Program Execution Until The Entire Socket Buffer Is Filled Up */ ' /* We Don't Want To Do That */ MailSock.Blocking = False ' /* We Can Accept Strings (POP3's Protocol Works on Strings) */ MailSock.Binary = False ' /* Set Our Protocol IP */ MailSock.Protocol = 0 ' /* Set The Socket Connection Timeout To 10 Second (10000 milliseconds) */ MailSock.Timeout = 10000
The User Doesn't Have to check their mail. They can always quit the program, and as programmers, we need to make sure that option is available. So The next thing we'll make sure that is setup is the Quit Button. There isn't a whole lot that needs to be in this button, but we need to make sure that all of our forms get unloaded.. which also helps destroy any objects that are still not set to nothing. So, We Can stick this in our program's quit button:
' /* For All The Forms That We Have */ For Each XFrm In Forms ' /* Unload Current Form In Collection */ Unload XFrm Next XFrm ' /* Code Should Never Reach Here (But Just In Case) */ End
As The code above shows, this button will loop through all of the forms in the forms collection, and unload them one by one. This is a graceful method for terminating a VB Program. The unload allows a huge number of internal things to happen with the program, and with VB itself (if it's still being run from the IDE). There may only be one form right now, but in the future, we may need another ;). Anyway, the form will eventually unload. It's a real good idea to make sure that our socket disconnects from the server (if it's still connected) when our program starts shutting down. This is how we play nice with the server. So, in the form's unload event, let's stick this in there:
' /* If We Are Closing The Program, We Should Disconnect From The Server */ MailSock.Disconnect
Well, We can't hope that our users will hit quit every time, so it's time to start coding the "check mail" button. There's a couple of things that go on this subroutine, that we should note. Firstly, we need to make sure that all the textbox's have information in them. We can't connect to a server that we don't know the name or IP address of. We also need to know what port number POP3 is running on for that server. By default it's 110, but some whacko might have felt the urge to change it, and we need to be mindful of that. Also, As far as I know, they all require usernames and passwords. So we have to make sure they entered that information. Then we need to assign the information they put in the box's for server name and port number to our socket control MailSock. We Set Our Uname and Pass variables to what they entered for their username and password.... and we try to connect. Here Is The Code For that button:
' /* Check If They Enter A Server To Connect To */ If Text1.Text = "" Then ' /* If Not, Let Them Know It */ MsgBox "Please Enter A Host/IP Of The POP3 Server" ' /* Leave This Sub-Routine */ Exit Sub End If ' /* Check If They Entered A Port Number (110 default) */ If Text2.Text = "" Then ' /* If No Port Specified, Let Them Know It */ MsgBox "Please Enter The Port Of The POP3 Server" ' /* Leave This Sub-Routine */ Exit Sub End If ' /* Check If They Enter A Username */ If Text3.Text = "" Then ' /* Let Them Know */ MsgBox "Please Enter A Username For This Host" ' /* Yada, Yada */ Exit Sub End If ' /* Lastly, We Need A Password */ If Text4.Text = "" Then ' /* Can't Log In Without A Password */ MsgBox "Please Enter A Password For This Host" ' /* Quit This Sub-Routine */ Exit Sub End If ' /* Set Our Socket To The Specified Server To Connect To */ MailSock.HostName = Text1.Text ' /* Set Our Socket To The Port Specified */ MailSock.RemotePort = Val(Text2.Text) ' /* Set The Uname Variable To The Username Entered In The Textbox */ Uname = Text3.Text ' /* Set The Pass Variable To The Password Entered In The Textbox */ Pass = Text4.Text ' /* Try To Connect To The Specified Server On The Specified Port Number */ MailSock.Connect
Now, In a perfect world the server or host that we are trying to connect to, would be functional, and always available. Well, I happen to know from years of experience that this world is not perfect... and when it comes to servers, protocols, and writing code, there is no such thing as perfection. With that in mind, we know that our socket will not always make a successful connection. In our form load, we set a timeout for the socket. After that amount of time, the socket will fire an error event, and we can alert the user that the server is down, or refusing connections. We put that in the LastError event of the socket control (for catalyst's socket). Here is that procedure:
Private Sub MailSock_LastError(ErrorCode As Integer, ErrorString As String, Response As Integer) ' /* Print The Error Code And String To The VB IDE, Does Nothing when compiled */ Debug.Print "Error " & ErrorCode & " (" & ErrorString & ")" ' /* If The Error Code Is 24061, The Connection is refused on port 110 */ If ErrorCode = "24061" Then ' /* Alert The User */ MsgBox "Connection Refused" Else ' /* This is a catch all of error codes... There are literally hundreds */ ' /* of them... I don't want to search for them all :) */ MsgBox "Connection TimeOut" End If ' /* Even Though We Aren't Connected, We Want To Make Certain */ MailSock.Disconnect End Sub
In the whacky event that we are able to reach the server, and the server has the server running, we'll get a connection. We Aren't doing anything really majore in this event (the connect event). Ultimately, we are only going to set a variable that kicks off our conversation with the server. This goes in the "connect" event.
Private Sub MailSock_Connect() ' /* Set Lastcmd to "start" */ Lastcmd = "start" End Sub
The Last event that we have to deal with, regarding this particular piece (Step 1) of code is the read event. When the server sends information to us through the socket, we have to know that this happened. When our computer receives information from the server the socket fires an event that tells us that information has arrived. In SocketWrench, this event is read. The first thing we do, is read the information from the socket with the read method (don't confuse that with the read event... the read event fires when data is ready to be read... the read method reads the data from the socket, and stores it into a variable). We Store the information in one of our previously declared variables (strBuffer). Then, we analyze the Lastcmd variable to see where we are in our conversation with the server. A typical conversation with a POP3 server goes something like this:
+OK Solid POP3 server ready <-- server starts the convo
USER username <-- We Send This To The Server
+OK username accepted <--Server accepts username
PASS password <-- We Send it the password
+OK authentication successful <-- Server Accepts Password
STAT <-- We Request Status (how many e-mails)
+OK 2 3504 <-- Server Tells Us How many e-mails
QUIT <-- Tell The Server Good Bye
+OK session ended <-- Server Says Bye
Now, This is only the conversation for what we are doing. After stat, you could actually have it send you the e-mail text (the header information, subject, and the actual body of the e-mail). However, our goal for the program, is just to see if we have mail, and if so, how many. Due to this, I have left out viewing and deleting e-mails from the inbox. Our two biggest problems are walking through this conversation (which is a problem because we have to remember where we left off), and parsing apart the information that the server sends back to us. The Lastcmd variable, gets set to each step of the process. So, we know if Lastcmd is equal to "start" we need to send the first thing... which is USER username. If Lastcmd is equal to "user", then we have sent the username, and need to send "PASS password", and so forth until our conversation is complete. Here is the source for the read event:
Private Sub MailSock_Read(DataLength As Integer, IsUrgent As Integer) ' /* Read In Data From The Server */ MailSock.Read strBuffer, DataLength ' /* Analyze The Lastcmd Variable */ Select Case Lastcmd ' /* If It's Start (Which Get Set On Socket Connect) */ Case "start" ' /* Set toSend Variable To Contain The String Needed To Send The Username To The Server */ toSend = "USER " & Uname & vbNewLine ' /* Actually Send It */ MailSock.Write toSend, Len(toSend) ' /* The Last Command We Sent Was USER */ Lastcmd = "user" ' /* If Lastcmd is "user" we have already told the server the username so */ Case "user" ' /* Set Up The Variable To Contain The Password (with a newline) */ toSend = "PASS " & Pass & vbNewLine ' /* Actually Send The Password (with PASS First) */ MailSock.Write toSend, Len(toSend) ' /* The Last Command We Sent Was "pass" Lastcmd = "pass" ' /* If Lastcmd is "pass" then we have sent the username and password */ Case "pass" ' /* If The Login Was Succesfull (+OK) */ If Left(strBuffer, 3) = "+OK" Then ' /* Send The STAT Command, Which Tells Us How Many Messages We Have */ toSend = "STAT" & vbNewLine ' /* Actually Send The Command */ MailSock.Write toSend, Len(toSend) ' /* The Last command Sent was "stat" */ Lastcmd = "stat" Else ' /* Password Failure? */ MsgBox "Authentication Failure" ' /* Disconnect From The Server */ MailSock.Disconnect ' /* Leave This Sub-Routine */ Exit Sub End If ' /* If The Last Command We Sent Was stat */ Case "stat" ' /* STAT Command Got A Reply Starting With +OK */ If Left(strBuffer, 3) = "+OK" Then ' /* Split The Servers Response By Space */ Retval = Split(strBuffer, " ") ' /* Set MailCount (The Number Of Messages We Have) */ mailcount = Retval(1) ' /* If It's Not Equal To Zero (We Have Mail) */ If mailcount <> 0 Then ' /* If There Is 1 Message */ If mailcount = 1 Then ' /* Tell Them There is A Message */ MsgBox "You Have A Message" Else ' /* There Is More Than 1 Message So */ MsgBox "You Have Messages" & mailcount & " Waiting" End If Else ' /* No Messages Found On The Server */ MsgBox "No Mail Available" End If ' /* Disconnect From The Server */ MailSock.Disconnect End If End Select End Sub
You'll Notice that all responses from the server start with +OK (unless something has gone wrong, such as authentication failure). Because of this, it's easy to do a left of strbuffer, and look for +OK, if it's there... we know the last command we sent was accepted. The tricky part comes after we send stat. We really need to rip apart that string to get the number of e-mails in our inbox. So, we use the split command. This command looks for a specific character (in our case, space), and stores everything else in elements of an array. Since we know there are only 3 peices to the string we are ripping apart (+OK, emailcount, octets). So, Array(0) will be +OK, while Array(1) will be the number of e-mails waiting for us. In order to avoid too much confusion, we use a new variable, MailCount, To store that value. Then we see if there is 1 e-mail, or more than 1, and respond with msgbox's accordingly.
Step 1 Conclusion
That About wraps up Part 1. We now have the ability, confidence, and know-how to connect to a POP3 server with a socket, and hold a conversation using it's protocol found in an RFC. In Step 2, we'll put together a program that hides, or sits in the system tray... and will check our mail for us at specific intervals, and display one of those silly notification windows by the system tray when we get mail.